diff --git a/daemon/daemon.go b/daemon/daemon.go index 041319da81..3d45c71bf8 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -19,7 +19,7 @@ import ( "time" "github.com/Sirupsen/logrus" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/docker/docker/api" "github.com/docker/docker/api/types" containertypes "github.com/docker/docker/api/types/container" diff --git a/hack/dockerfile/binaries-commits b/hack/dockerfile/binaries-commits index 5c110b7065..8770d77ead 100644 --- a/hack/dockerfile/binaries-commits +++ b/hack/dockerfile/binaries-commits @@ -4,7 +4,7 @@ TOMLV_COMMIT=9baf8a8a9f2ed20a8e54160840c492f937eeaf9a # When updating RUNC_COMMIT, also update runc in vendor.conf accordingly RUNC_COMMIT=992a5be178a62e026f4069f443c6164912adbf09 -CONTAINERD_COMMIT=8ef7df579710405c4bb6e0812495671002ce08e0 +CONTAINERD_COMMIT=3addd840653146c90a254301d6c3a663c7fd6429 TINI_COMMIT=949e6facb77383876aeff8a6944dde66b3089574 LIBNETWORK_COMMIT=7b2b1feb1de4817d522cc372af149ff48d25028e VNDR_COMMIT=c56e082291115e369f77601f9c071dd0b87c7120 diff --git a/libcontainerd/client_linux.go b/libcontainerd/client_linux.go index ba52221239..a6986b5200 100644 --- a/libcontainerd/client_linux.go +++ b/libcontainerd/client_linux.go @@ -9,7 +9,8 @@ import ( "time" "github.com/Sirupsen/logrus" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" + containerd_runtime_types "github.com/containerd/containerd/runtime" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/mount" "github.com/golang/protobuf/ptypes" @@ -467,7 +468,7 @@ func (clnt *client) Restore(containerID string, attachStdio StdioCallback, optio cont, err := clnt.getContainerdContainer(containerID) // Get its last event ev, eerr := clnt.getContainerLastEvent(containerID) - if err != nil || cont.Status == "Stopped" { + if err != nil || containerd_runtime_types.State(cont.Status) == containerd_runtime_types.Stopped { if err != nil { logrus.Warnf("libcontainerd: failed to retrieve container %s state: %v", containerID, err) } diff --git a/libcontainerd/client_unix.go b/libcontainerd/client_unix.go index 21e8fea666..906026024d 100644 --- a/libcontainerd/client_unix.go +++ b/libcontainerd/client_unix.go @@ -11,7 +11,7 @@ import ( "sync" "github.com/Sirupsen/logrus" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/docker/docker/pkg/idtools" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/net/context" diff --git a/libcontainerd/container_unix.go b/libcontainerd/container_unix.go index 61bab145f2..f2413b2e3f 100644 --- a/libcontainerd/container_unix.go +++ b/libcontainerd/container_unix.go @@ -13,7 +13,7 @@ import ( "time" "github.com/Sirupsen/logrus" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/docker/docker/pkg/ioutils" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/tonistiigi/fifo" diff --git a/libcontainerd/process_unix.go b/libcontainerd/process_unix.go index 506fca6e11..3b54e325b5 100644 --- a/libcontainerd/process_unix.go +++ b/libcontainerd/process_unix.go @@ -10,7 +10,7 @@ import ( goruntime "runtime" "strings" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/tonistiigi/fifo" "golang.org/x/net/context" "golang.org/x/sys/unix" diff --git a/libcontainerd/remote_unix.go b/libcontainerd/remote_unix.go index eebbc886c6..abdb662c9f 100644 --- a/libcontainerd/remote_unix.go +++ b/libcontainerd/remote_unix.go @@ -19,7 +19,7 @@ import ( "time" "github.com/Sirupsen/logrus" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/docker/docker/pkg/locker" "github.com/docker/docker/pkg/system" "github.com/golang/protobuf/ptypes" diff --git a/libcontainerd/types.go b/libcontainerd/types.go index 3d981e3371..c7ade6b188 100644 --- a/libcontainerd/types.go +++ b/libcontainerd/types.go @@ -3,7 +3,7 @@ package libcontainerd import ( "io" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/net/context" ) diff --git a/libcontainerd/types_linux.go b/libcontainerd/types_linux.go index cffae99638..4f0635835f 100644 --- a/libcontainerd/types_linux.go +++ b/libcontainerd/types_linux.go @@ -1,7 +1,7 @@ package libcontainerd import ( - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/opencontainers/runtime-spec/specs-go" ) diff --git a/libcontainerd/types_solaris.go b/libcontainerd/types_solaris.go index dbafef669f..2ab18eb0da 100644 --- a/libcontainerd/types_solaris.go +++ b/libcontainerd/types_solaris.go @@ -1,7 +1,7 @@ package libcontainerd import ( - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/opencontainers/runtime-spec/specs-go" ) diff --git a/libcontainerd/utils_linux.go b/libcontainerd/utils_linux.go index 55a49baa0a..5fd5bf6de1 100644 --- a/libcontainerd/utils_linux.go +++ b/libcontainerd/utils_linux.go @@ -3,7 +3,7 @@ package libcontainerd import ( "syscall" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/opencontainers/runtime-spec/specs-go" ) diff --git a/libcontainerd/utils_solaris.go b/libcontainerd/utils_solaris.go index 49632b45e5..10ae599808 100644 --- a/libcontainerd/utils_solaris.go +++ b/libcontainerd/utils_solaris.go @@ -3,7 +3,7 @@ package libcontainerd import ( "syscall" - containerd "github.com/docker/containerd/api/grpc/types" + containerd "github.com/containerd/containerd/api/grpc/types" "github.com/opencontainers/runtime-spec/specs-go" ) diff --git a/vendor.conf b/vendor.conf index 0f07743e09..a33afddce4 100644 --- a/vendor.conf +++ b/vendor.conf @@ -19,6 +19,7 @@ github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987 +github.com/pmezard/go-difflib v1.0.0 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 github.com/imdario/mergo 0.2.1 @@ -60,7 +61,7 @@ google.golang.org/grpc v1.0.4 github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f # When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly -github.com/opencontainers/runc b6b70e53451794e8333e9b602cc096b47a20bd0f +github.com/opencontainers/runc 992a5be178a62e026f4069f443c6164912adbf09 github.com/opencontainers/runtime-spec v1.0.0-rc5 # specs github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe @@ -102,7 +103,7 @@ google.golang.org/genproto b3e7c2fb04031add52c4817f53f43757ccbf9c18 github.com/docker/docker-credential-helpers v0.5.0 # containerd -github.com/docker/containerd 8ef7df579710405c4bb6e0812495671002ce08e0 +github.com/containerd/containerd 3addd840653146c90a254301d6c3a663c7fd6429 github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 # cluster diff --git a/vendor/github.com/docker/containerd/LICENSE.code b/vendor/github.com/containerd/containerd/LICENSE.code similarity index 100% rename from vendor/github.com/docker/containerd/LICENSE.code rename to vendor/github.com/containerd/containerd/LICENSE.code diff --git a/vendor/github.com/docker/containerd/LICENSE.docs b/vendor/github.com/containerd/containerd/LICENSE.docs similarity index 100% rename from vendor/github.com/docker/containerd/LICENSE.docs rename to vendor/github.com/containerd/containerd/LICENSE.docs diff --git a/vendor/github.com/docker/containerd/NOTICE b/vendor/github.com/containerd/containerd/NOTICE similarity index 100% rename from vendor/github.com/docker/containerd/NOTICE rename to vendor/github.com/containerd/containerd/NOTICE diff --git a/vendor/github.com/docker/containerd/README.md b/vendor/github.com/containerd/containerd/README.md similarity index 100% rename from vendor/github.com/docker/containerd/README.md rename to vendor/github.com/containerd/containerd/README.md diff --git a/vendor/github.com/docker/containerd/api/grpc/types/api.pb.go b/vendor/github.com/containerd/containerd/api/grpc/types/api.pb.go similarity index 100% rename from vendor/github.com/docker/containerd/api/grpc/types/api.pb.go rename to vendor/github.com/containerd/containerd/api/grpc/types/api.pb.go diff --git a/vendor/github.com/docker/containerd/api/grpc/types/api.proto b/vendor/github.com/containerd/containerd/api/grpc/types/api.proto similarity index 100% rename from vendor/github.com/docker/containerd/api/grpc/types/api.proto rename to vendor/github.com/containerd/containerd/api/grpc/types/api.proto diff --git a/vendor/github.com/containerd/containerd/osutils/fds.go b/vendor/github.com/containerd/containerd/osutils/fds.go new file mode 100644 index 0000000000..98fc9305e4 --- /dev/null +++ b/vendor/github.com/containerd/containerd/osutils/fds.go @@ -0,0 +1,18 @@ +// +build !windows,!darwin + +package osutils + +import ( + "io/ioutil" + "path/filepath" + "strconv" +) + +// GetOpenFds returns the number of open fds for the process provided by pid +func GetOpenFds(pid int) (int, error) { + dirs, err := ioutil.ReadDir(filepath.Join("/proc", strconv.Itoa(pid), "fd")) + if err != nil { + return -1, err + } + return len(dirs), nil +} diff --git a/vendor/github.com/containerd/containerd/osutils/pdeathsig_linux.go b/vendor/github.com/containerd/containerd/osutils/pdeathsig_linux.go new file mode 100644 index 0000000000..5310f2e264 --- /dev/null +++ b/vendor/github.com/containerd/containerd/osutils/pdeathsig_linux.go @@ -0,0 +1,15 @@ +// +build !solaris + +package osutils + +import ( + "syscall" +) + +// SetPDeathSig sets the parent death signal to SIGKILL so that if the +// shim dies the container process also dies. +func SetPDeathSig() *syscall.SysProcAttr { + return &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + } +} diff --git a/vendor/github.com/containerd/containerd/osutils/pdeathsig_solaris.go b/vendor/github.com/containerd/containerd/osutils/pdeathsig_solaris.go new file mode 100644 index 0000000000..512e24b5d1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/osutils/pdeathsig_solaris.go @@ -0,0 +1,8 @@ +// +build solaris + +package osutils + +// SetPDeathSig is a no-op on Solaris as Pdeathsig is not defined. +func SetPDeathSig() *syscall.SysProcAttr { + return nil +} diff --git a/vendor/github.com/containerd/containerd/osutils/prctl.go b/vendor/github.com/containerd/containerd/osutils/prctl.go new file mode 100644 index 0000000000..1d6e251e13 --- /dev/null +++ b/vendor/github.com/containerd/containerd/osutils/prctl.go @@ -0,0 +1,48 @@ +// +build linux + +// Package osutils provide access to the Get Child and Set Child prctl +// flags. +// See http://man7.org/linux/man-pages/man2/prctl.2.html +package osutils + +import ( + "syscall" + "unsafe" +) + +// PR_SET_CHILD_SUBREAPER allows setting the child subreaper. +// If arg2 is nonzero, set the "child subreaper" attribute of the +// calling process; if arg2 is zero, unset the attribute. When a +// process is marked as a child subreaper, all of the children +// that it creates, and their descendants, will be marked as +// having a subreaper. In effect, a subreaper fulfills the role +// of init(1) for its descendant processes. Upon termination of +// a process that is orphaned (i.e., its immediate parent has +// already terminated) and marked as having a subreaper, the +// nearest still living ancestor subreaper will receive a SIGCHLD +// signal and be able to wait(2) on the process to discover its +// termination status. +const prSetChildSubreaper = 36 + +// PR_GET_CHILD_SUBREAPER allows retrieving the current child +// subreaper. +// Return the "child subreaper" setting of the caller, in the +// location pointed to by (int *) arg2. +const prGetChildSubreaper = 37 + +// GetSubreaper returns the subreaper setting for the calling process +func GetSubreaper() (int, error) { + var i uintptr + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, prGetChildSubreaper, uintptr(unsafe.Pointer(&i)), 0); err != 0 { + return -1, err + } + return int(i), nil +} + +// SetSubreaper sets the value i as the subreaper setting for the calling process +func SetSubreaper(i int) error { + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, prSetChildSubreaper, uintptr(i), 0); err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/osutils/prctl_solaris.go b/vendor/github.com/containerd/containerd/osutils/prctl_solaris.go new file mode 100644 index 0000000000..84da5f9f55 --- /dev/null +++ b/vendor/github.com/containerd/containerd/osutils/prctl_solaris.go @@ -0,0 +1,19 @@ +// +build solaris + +package osutils + +import ( + "errors" +) + +//Solaris TODO + +// GetSubreaper returns the subreaper setting for the calling process +func GetSubreaper() (int, error) { + return 0, errors.New("osutils GetSubreaper not implemented on Solaris") +} + +// SetSubreaper sets the value i as the subreaper setting for the calling process +func SetSubreaper(i int) error { + return nil +} diff --git a/vendor/github.com/containerd/containerd/osutils/reaper.go b/vendor/github.com/containerd/containerd/osutils/reaper.go new file mode 100644 index 0000000000..6a80335af6 --- /dev/null +++ b/vendor/github.com/containerd/containerd/osutils/reaper.go @@ -0,0 +1,51 @@ +// +build !windows + +package osutils + +import "syscall" + +// Exit is the wait4 information from an exited process +type Exit struct { + Pid int + Status int +} + +// Reap reaps all child processes for the calling process and returns their +// exit information +func Reap(wait bool) (exits []Exit, err error) { + var ( + ws syscall.WaitStatus + rus syscall.Rusage + ) + flag := syscall.WNOHANG + if wait { + flag = 0 + } + for { + pid, err := syscall.Wait4(-1, &ws, flag, &rus) + if err != nil { + if err == syscall.ECHILD { + return exits, nil + } + return exits, err + } + if pid <= 0 { + return exits, nil + } + exits = append(exits, Exit{ + Pid: pid, + Status: exitStatus(ws), + }) + } +} + +const exitSignalOffset = 128 + +// exitStatus returns the correct exit status for a process based on if it +// was signaled or exited cleanly +func exitStatus(status syscall.WaitStatus) int { + if status.Signaled() { + return exitSignalOffset + int(status.Signal()) + } + return status.ExitStatus() +} diff --git a/vendor/github.com/containerd/containerd/runtime/container.go b/vendor/github.com/containerd/containerd/runtime/container.go new file mode 100644 index 0000000000..43351cab17 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/container.go @@ -0,0 +1,749 @@ +package runtime + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/Sirupsen/logrus" + "github.com/containerd/containerd/specs" + ocs "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/net/context" + "golang.org/x/sys/unix" +) + +// Container defines the operations allowed on a container +type Container interface { + // ID returns the container ID + ID() string + // Path returns the path to the bundle + Path() string + // Start starts the init process of the container + Start(ctx context.Context, checkpointPath string, s Stdio) (Process, error) + // Exec starts another process in an existing container + Exec(context.Context, string, specs.ProcessSpec, Stdio) (Process, error) + // Delete removes the container's state and any resources + Delete() error + // Processes returns all the containers processes that have been added + Processes() ([]Process, error) + // State returns the containers runtime state + State() State + // Resume resumes a paused container + Resume() error + // Pause pauses a running container + Pause() error + // RemoveProcess removes the specified process from the container + RemoveProcess(string) error + // Checkpoints returns all the checkpoints for a container + Checkpoints(checkpointDir string) ([]Checkpoint, error) + // Checkpoint creates a new checkpoint + Checkpoint(checkpoint Checkpoint, checkpointDir string) error + // DeleteCheckpoint deletes the checkpoint for the provided name + DeleteCheckpoint(name string, checkpointDir string) error + // Labels are user provided labels for the container + Labels() []string + // Pids returns all pids inside the container + Pids() ([]int, error) + // Stats returns realtime container stats and resource information + Stats() (*Stat, error) + // Name or path of the OCI compliant runtime used to execute the container + Runtime() string + // OOM signals the channel if the container received an OOM notification + OOM() (OOM, error) + // UpdateResource updates the containers resources to new values + UpdateResources(*Resource) error + + // Status return the current status of the container. + Status() (State, error) +} + +// OOM wraps a container OOM. +type OOM interface { + io.Closer + FD() int + ContainerID() string + Flush() + Removed() bool +} + +// Stdio holds the path to the 3 pipes used for the standard ios. +type Stdio struct { + Stdin string + Stdout string + Stderr string +} + +// NewStdio wraps the given standard io path into an Stdio struct. +// If a given parameter is the empty string, it is replaced by "/dev/null" +func NewStdio(stdin, stdout, stderr string) Stdio { + for _, s := range []*string{ + &stdin, &stdout, &stderr, + } { + if *s == "" { + *s = "/dev/null" + } + } + return Stdio{ + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + } +} + +// ContainerOpts keeps the options passed at container creation +type ContainerOpts struct { + Root string + ID string + Bundle string + Runtime string + RuntimeArgs []string + Shim string + Labels []string + NoPivotRoot bool + Timeout time.Duration +} + +// New returns a new container +func New(opts ContainerOpts) (Container, error) { + c := &container{ + root: opts.Root, + id: opts.ID, + bundle: opts.Bundle, + labels: opts.Labels, + processes: make(map[string]*process), + runtime: opts.Runtime, + runtimeArgs: opts.RuntimeArgs, + shim: opts.Shim, + noPivotRoot: opts.NoPivotRoot, + timeout: opts.Timeout, + } + if err := os.Mkdir(filepath.Join(c.root, c.id), 0755); err != nil { + return nil, err + } + f, err := os.Create(filepath.Join(c.root, c.id, StateFile)) + if err != nil { + return nil, err + } + defer f.Close() + if err := json.NewEncoder(f).Encode(state{ + Bundle: c.bundle, + Labels: c.labels, + Runtime: c.runtime, + RuntimeArgs: c.runtimeArgs, + Shim: c.shim, + NoPivotRoot: opts.NoPivotRoot, + }); err != nil { + return nil, err + } + return c, nil +} + +// Load return a new container from the matchin state file on disk. +func Load(root, id, shimName string, timeout time.Duration) (Container, error) { + var s state + f, err := os.Open(filepath.Join(root, id, StateFile)) + if err != nil { + return nil, err + } + defer f.Close() + if err := json.NewDecoder(f).Decode(&s); err != nil { + return nil, err + } + c := &container{ + root: root, + id: id, + bundle: s.Bundle, + labels: s.Labels, + runtime: s.Runtime, + runtimeArgs: s.RuntimeArgs, + shim: s.Shim, + noPivotRoot: s.NoPivotRoot, + processes: make(map[string]*process), + timeout: timeout, + } + + if c.shim == "" { + c.shim = shimName + } + + dirs, err := ioutil.ReadDir(filepath.Join(root, id)) + if err != nil { + return nil, err + } + for _, d := range dirs { + if !d.IsDir() { + continue + } + pid := d.Name() + s, err := readProcessState(filepath.Join(root, id, pid)) + if err != nil { + return nil, err + } + p, err := loadProcess(filepath.Join(root, id, pid), pid, c, s) + if err != nil { + logrus.WithField("id", id).WithField("pid", pid).Debugf("containerd: error loading process %s", err) + continue + } + c.processes[pid] = p + } + + _, err = os.Stat(c.bundle) + if err != nil && !os.IsExist(err) { + for key, p := range c.processes { + if key == InitProcessID { + p.Delete() + break + } + } + return nil, fmt.Errorf("bundle dir %s don't exist", c.bundle) + } + return c, nil +} + +func readProcessState(dir string) (*ProcessState, error) { + f, err := os.Open(filepath.Join(dir, "process.json")) + if err != nil { + return nil, err + } + defer f.Close() + var s ProcessState + if err := json.NewDecoder(f).Decode(&s); err != nil { + return nil, err + } + return &s, nil +} + +type container struct { + // path to store runtime state information + root string + id string + bundle string + runtime string + runtimeArgs []string + shim string + processes map[string]*process + labels []string + oomFds []int + noPivotRoot bool + timeout time.Duration +} + +func (c *container) ID() string { + return c.id +} + +func (c *container) Path() string { + return c.bundle +} + +func (c *container) Labels() []string { + return c.labels +} + +func (c *container) readSpec() (*specs.Spec, error) { + var spec specs.Spec + f, err := os.Open(filepath.Join(c.bundle, "config.json")) + if err != nil { + return nil, err + } + defer f.Close() + if err := json.NewDecoder(f).Decode(&spec); err != nil { + return nil, err + } + return &spec, nil +} + +func (c *container) Delete() error { + var err error + args := append(c.runtimeArgs, "delete", c.id) + if b, derr := exec.Command(c.runtime, args...).CombinedOutput(); derr != nil && !strings.Contains(string(b), "does not exist") { + err = fmt.Errorf("%s: %q", derr, string(b)) + } + if rerr := os.RemoveAll(filepath.Join(c.root, c.id)); rerr != nil { + if err != nil { + err = fmt.Errorf("%s; failed to remove %s: %s", err, filepath.Join(c.root, c.id), rerr) + } else { + err = rerr + } + } + return err +} + +func (c *container) Processes() ([]Process, error) { + out := []Process{} + for _, p := range c.processes { + out = append(out, p) + } + return out, nil +} + +func (c *container) RemoveProcess(pid string) error { + delete(c.processes, pid) + return os.RemoveAll(filepath.Join(c.root, c.id, pid)) +} + +func (c *container) State() State { + proc := c.processes[InitProcessID] + if proc == nil { + return Stopped + } + return proc.State() +} + +func (c *container) Runtime() string { + return c.runtime +} + +func (c *container) Pause() error { + args := c.runtimeArgs + args = append(args, "pause", c.id) + b, err := exec.Command(c.runtime, args...).CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %q", err.Error(), string(b)) + } + return nil +} + +func (c *container) Resume() error { + args := c.runtimeArgs + args = append(args, "resume", c.id) + b, err := exec.Command(c.runtime, args...).CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %q", err.Error(), string(b)) + } + return nil +} + +func (c *container) Checkpoints(checkpointDir string) ([]Checkpoint, error) { + if checkpointDir == "" { + checkpointDir = filepath.Join(c.bundle, "checkpoints") + } + + dirs, err := ioutil.ReadDir(checkpointDir) + if err != nil { + return nil, err + } + var out []Checkpoint + for _, d := range dirs { + if !d.IsDir() { + continue + } + path := filepath.Join(checkpointDir, d.Name(), "config.json") + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var cpt Checkpoint + if err := json.Unmarshal(data, &cpt); err != nil { + return nil, err + } + out = append(out, cpt) + } + return out, nil +} + +func (c *container) Checkpoint(cpt Checkpoint, checkpointDir string) error { + if checkpointDir == "" { + checkpointDir = filepath.Join(c.bundle, "checkpoints") + } + + if err := os.MkdirAll(checkpointDir, 0755); err != nil { + return err + } + + path := filepath.Join(checkpointDir, cpt.Name) + if err := os.Mkdir(path, 0755); err != nil { + return err + } + f, err := os.Create(filepath.Join(path, "config.json")) + if err != nil { + return err + } + cpt.Created = time.Now() + err = json.NewEncoder(f).Encode(cpt) + f.Close() + if err != nil { + return err + } + args := []string{ + "checkpoint", + "--image-path", path, + "--work-path", filepath.Join(path, "criu.work"), + } + add := func(flags ...string) { + args = append(args, flags...) + } + add(c.runtimeArgs...) + if !cpt.Exit { + add("--leave-running") + } + if cpt.Shell { + add("--shell-job") + } + if cpt.TCP { + add("--tcp-established") + } + if cpt.UnixSockets { + add("--ext-unix-sk") + } + for _, ns := range cpt.EmptyNS { + add("--empty-ns", ns) + } + add(c.id) + out, err := exec.Command(c.runtime, args...).CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %q", err.Error(), string(out)) + } + return err +} + +func (c *container) DeleteCheckpoint(name string, checkpointDir string) error { + if checkpointDir == "" { + checkpointDir = filepath.Join(c.bundle, "checkpoints") + } + return os.RemoveAll(filepath.Join(checkpointDir, name)) +} + +func (c *container) Start(ctx context.Context, checkpointPath string, s Stdio) (Process, error) { + processRoot := filepath.Join(c.root, c.id, InitProcessID) + if err := os.Mkdir(processRoot, 0755); err != nil { + return nil, err + } + cmd := exec.Command(c.shim, + c.id, c.bundle, c.runtime, + ) + cmd.Dir = processRoot + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + spec, err := c.readSpec() + if err != nil { + return nil, err + } + config := &processConfig{ + checkpoint: checkpointPath, + root: processRoot, + id: InitProcessID, + c: c, + stdio: s, + spec: spec, + processSpec: specs.ProcessSpec(spec.Process), + } + p, err := newProcess(config) + if err != nil { + return nil, err + } + if err := c.createCmd(ctx, InitProcessID, cmd, p); err != nil { + return nil, err + } + return p, nil +} + +func (c *container) Exec(ctx context.Context, pid string, pspec specs.ProcessSpec, s Stdio) (pp Process, err error) { + processRoot := filepath.Join(c.root, c.id, pid) + if err := os.Mkdir(processRoot, 0755); err != nil { + return nil, err + } + defer func() { + if err != nil { + c.RemoveProcess(pid) + } + }() + cmd := exec.Command(c.shim, + c.id, c.bundle, c.runtime, + ) + cmd.Dir = processRoot + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + spec, err := c.readSpec() + if err != nil { + return nil, err + } + config := &processConfig{ + exec: true, + id: pid, + root: processRoot, + c: c, + processSpec: pspec, + spec: spec, + stdio: s, + } + p, err := newProcess(config) + if err != nil { + return nil, err + } + if err := c.createCmd(ctx, pid, cmd, p); err != nil { + return nil, err + } + return p, nil +} + +func (c *container) createCmd(ctx context.Context, pid string, cmd *exec.Cmd, p *process) error { + p.cmd = cmd + if err := cmd.Start(); err != nil { + close(p.cmdDoneCh) + if exErr, ok := err.(*exec.Error); ok { + if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist { + return fmt.Errorf("%s not installed on system", c.shim) + } + } + return err + } + // We need the pid file to have been written to run + defer func() { + go func() { + err := p.cmd.Wait() + if err == nil { + p.cmdSuccess = true + } + + if same, err := p.isSameProcess(); same && p.pid > 0 { + // The process changed its PR_SET_PDEATHSIG, so force + // kill it + logrus.Infof("containerd: %s:%s (pid %v) has become an orphan, killing it", p.container.id, p.id, p.pid) + err = unix.Kill(p.pid, syscall.SIGKILL) + if err != nil && err != syscall.ESRCH { + logrus.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err) + } else { + for { + err = unix.Kill(p.pid, 0) + if err != nil { + break + } + time.Sleep(5 * time.Millisecond) + } + } + } + close(p.cmdDoneCh) + }() + }() + + ch := make(chan error) + go func() { + if err := c.waitForCreate(p, cmd); err != nil { + ch <- err + return + } + c.processes[pid] = p + ch <- nil + }() + select { + case <-ctx.Done(): + cmd.Process.Kill() + cmd.Wait() + <-ch + return ctx.Err() + case err := <-ch: + return err + } + return nil +} + +func hostIDFromMap(id uint32, mp []ocs.LinuxIDMapping) int { + for _, m := range mp { + if (id >= m.ContainerID) && (id <= (m.ContainerID + m.Size - 1)) { + return int(m.HostID + (id - m.ContainerID)) + } + } + return 0 +} + +func (c *container) Stats() (*Stat, error) { + now := time.Now() + args := c.runtimeArgs + args = append(args, "events", "--stats", c.id) + out, err := exec.Command(c.runtime, args...).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("%s: %q", err.Error(), out) + } + s := struct { + Data *Stat `json:"data"` + }{} + if err := json.Unmarshal(out, &s); err != nil { + return nil, err + } + s.Data.Timestamp = now + return s.Data, nil +} + +// Status implements the runtime Container interface. +func (c *container) Status() (State, error) { + args := c.runtimeArgs + args = append(args, "state", c.id) + + out, err := exec.Command(c.runtime, args...).CombinedOutput() + if err != nil { + return "", fmt.Errorf("%s: %q", err.Error(), out) + } + + // We only require the runtime json output to have a top level Status field. + var s struct { + Status State `json:"status"` + } + if err := json.Unmarshal(out, &s); err != nil { + return "", err + } + return s.Status, nil +} + +func (c *container) writeEventFD(root string, cfd, efd int) error { + f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0) + if err != nil { + return err + } + defer f.Close() + _, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd)) + return err +} + +type waitArgs struct { + pid int + err error +} + +func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error { + wc := make(chan error, 1) + go func() { + for { + if _, err := p.getPidFromFile(); err != nil { + if os.IsNotExist(err) || err == errInvalidPidInt || err == errContainerNotFound { + alive, err := isAlive(cmd) + if err != nil { + wc <- err + return + } + if !alive { + // runc could have failed to run the container so lets get the error + // out of the logs or the shim could have encountered an error + messages, err := readLogMessages(filepath.Join(p.root, "shim-log.json")) + if err != nil { + wc <- err + return + } + for _, m := range messages { + if m.Level == "error" { + wc <- fmt.Errorf("shim error: %v", m.Msg) + return + } + } + // no errors reported back from shim, check for runc/runtime errors + messages, err = readLogMessages(filepath.Join(p.root, "log.json")) + if err != nil { + if os.IsNotExist(err) { + err = ErrContainerNotStarted + } + wc <- err + return + } + for _, m := range messages { + if m.Level == "error" { + wc <- fmt.Errorf("oci runtime error: %v", m.Msg) + return + } + } + wc <- ErrContainerNotStarted + return + } + time.Sleep(15 * time.Millisecond) + continue + } + wc <- err + return + } + // the pid file was read successfully + wc <- nil + return + } + }() + select { + case err := <-wc: + if err != nil { + return err + } + err = p.saveStartTime() + if err != nil && !os.IsNotExist(err) { + logrus.Warnf("containerd: unable to save %s:%s starttime: %v", p.container.id, p.id, err) + } + return nil + case <-time.After(c.timeout): + cmd.Process.Kill() + cmd.Wait() + return ErrContainerStartTimeout + } +} + +// isAlive checks if the shim that launched the container is still alive +func isAlive(cmd *exec.Cmd) (bool, error) { + if _, err := syscall.Wait4(cmd.Process.Pid, nil, syscall.WNOHANG, nil); err == nil { + return true, nil + } + if err := syscall.Kill(cmd.Process.Pid, 0); err != nil { + if err == syscall.ESRCH { + return false, nil + } + return false, err + } + return true, nil +} + +type oom struct { + id string + root string + eventfd int +} + +func (o *oom) ContainerID() string { + return o.id +} + +func (o *oom) FD() int { + return o.eventfd +} + +func (o *oom) Flush() { + buf := make([]byte, 8) + syscall.Read(o.eventfd, buf) +} + +func (o *oom) Removed() bool { + _, err := os.Lstat(filepath.Join(o.root, "cgroup.event_control")) + return os.IsNotExist(err) +} + +func (o *oom) Close() error { + return syscall.Close(o.eventfd) +} + +type message struct { + Level string `json:"level"` + Msg string `json:"msg"` +} + +func readLogMessages(path string) ([]message, error) { + var out []message + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + dec := json.NewDecoder(f) + for { + var m message + if err := dec.Decode(&m); err != nil { + if err == io.EOF { + break + } + return nil, err + } + out = append(out, m) + } + return out, nil +} diff --git a/vendor/github.com/containerd/containerd/runtime/container_linux.go b/vendor/github.com/containerd/containerd/runtime/container_linux.go new file mode 100644 index 0000000000..9f3526a104 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/container_linux.go @@ -0,0 +1,190 @@ +package runtime + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "github.com/containerd/containerd/specs" + ocs "github.com/opencontainers/runtime-spec/specs-go" +) + +func findCgroupMountpointAndRoot(pid int, subsystem string) (string, string, error) { + f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid)) + if err != nil { + return "", "", err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + txt := scanner.Text() + fields := strings.Split(txt, " ") + for _, opt := range strings.Split(fields[len(fields)-1], ",") { + if opt == subsystem { + return fields[4], fields[3], nil + } + } + } + if err := scanner.Err(); err != nil { + return "", "", err + } + + return "", "", fmt.Errorf("cgroup path for %s not found", subsystem) +} + +func parseCgroupFile(path string) (map[string]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + s := bufio.NewScanner(f) + cgroups := make(map[string]string) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := s.Text() + parts := strings.Split(text, ":") + + for _, subs := range strings.Split(parts[1], ",") { + cgroups[subs] = parts[2] + } + } + return cgroups, nil +} + +func (c *container) OOM() (OOM, error) { + p := c.processes[InitProcessID] + if p == nil { + return nil, fmt.Errorf("no init process found") + } + + mountpoint, hostRoot, err := findCgroupMountpointAndRoot(os.Getpid(), "memory") + if err != nil { + return nil, err + } + + cgroups, err := parseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", p.pid)) + if err != nil { + return nil, err + } + + root, ok := cgroups["memory"] + if !ok { + return nil, fmt.Errorf("no memory cgroup for container %s", c.ID()) + } + + // Take care of the case were we're running inside a container + // ourself + root = strings.TrimPrefix(root, hostRoot) + + return c.getMemoryEventFD(filepath.Join(mountpoint, root)) +} + +func (c *container) Pids() ([]int, error) { + var pids []int + args := c.runtimeArgs + args = append(args, "ps", "--format=json", c.id) + out, err := exec.Command(c.runtime, args...).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("%s: %q", err.Error(), out) + } + if err := json.Unmarshal(out, &pids); err != nil { + return nil, err + } + return pids, nil +} + +func u64Ptr(i uint64) *uint64 { return &i } +func i64Ptr(i int64) *int64 { return &i } + +func (c *container) UpdateResources(r *Resource) error { + sr := ocs.LinuxResources{ + Memory: &ocs.LinuxMemory{ + Limit: u64Ptr(uint64(r.Memory)), + Reservation: u64Ptr(uint64(r.MemoryReservation)), + Swap: u64Ptr(uint64(r.MemorySwap)), + Kernel: u64Ptr(uint64(r.KernelMemory)), + KernelTCP: u64Ptr(uint64(r.KernelTCPMemory)), + }, + CPU: &ocs.LinuxCPU{ + Shares: u64Ptr(uint64(r.CPUShares)), + Quota: i64Ptr(int64(r.CPUQuota)), + Period: u64Ptr(uint64(r.CPUPeriod)), + Cpus: r.CpusetCpus, + Mems: r.CpusetMems, + }, + BlockIO: &ocs.LinuxBlockIO{ + Weight: &r.BlkioWeight, + }, + Pids: &ocs.LinuxPids{ + Limit: r.PidsLimit, + }, + } + + srStr := bytes.NewBuffer(nil) + if err := json.NewEncoder(srStr).Encode(&sr); err != nil { + return err + } + + args := c.runtimeArgs + args = append(args, "update", "-r", "-", c.id) + cmd := exec.Command(c.runtime, args...) + cmd.Stdin = srStr + b, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf(string(b)) + } + return nil +} + +func getRootIDs(s *specs.Spec) (int, int, error) { + if s == nil { + return 0, 0, nil + } + var hasUserns bool + for _, ns := range s.Linux.Namespaces { + if ns.Type == ocs.UserNamespace { + hasUserns = true + break + } + } + if !hasUserns { + return 0, 0, nil + } + uid := hostIDFromMap(0, s.Linux.UIDMappings) + gid := hostIDFromMap(0, s.Linux.GIDMappings) + return uid, gid, nil +} + +func (c *container) getMemoryEventFD(root string) (*oom, error) { + f, err := os.Open(filepath.Join(root, "memory.oom_control")) + if err != nil { + return nil, err + } + defer f.Close() + fd, _, serr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0) + if serr != 0 { + return nil, serr + } + if err := c.writeEventFD(root, int(f.Fd()), int(fd)); err != nil { + syscall.Close(int(fd)) + return nil, err + } + return &oom{ + root: root, + id: c.id, + eventfd: int(fd), + }, nil +} diff --git a/vendor/github.com/containerd/containerd/runtime/container_solaris.go b/vendor/github.com/containerd/containerd/runtime/container_solaris.go new file mode 100644 index 0000000000..7d9c5385b9 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/container_solaris.go @@ -0,0 +1,48 @@ +package runtime + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" + + "github.com/containerd/containerd/specs" + ocs "github.com/opencontainers/runtime-spec/specs-go" +) + +func getRootIDs(s *specs.Spec) (int, int, error) { + return 0, 0, nil +} + +func (c *container) OOM() (OOM, error) { + return nil, nil +} + +func (c *container) Pids() ([]int, error) { + var pids []int + + // TODO: This could be racy. Needs more investigation. + //we get this information from runz state + cmd := exec.Command(c.runtime, "state", c.id) + outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer) + cmd.Stdout, cmd.Stderr = outBuf, errBuf + + if err := cmd.Run(); err != nil { + if strings.Contains(errBuf.String(), "Container not found") { + return nil, errContainerNotFound + } + return nil, fmt.Errorf("Error is: %+v\n", err) + } + response := ocs.State{} + decoder := json.NewDecoder(outBuf) + if err := decoder.Decode(&response); err != nil { + return nil, fmt.Errorf("unable to decode json response: %+v", err) + } + pids = append(pids, response.Pid) + return pids, nil +} + +func (c *container) UpdateResources(r *Resource) error { + return nil +} diff --git a/vendor/github.com/containerd/containerd/runtime/process.go b/vendor/github.com/containerd/containerd/runtime/process.go new file mode 100644 index 0000000000..22d0192780 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/process.go @@ -0,0 +1,476 @@ +package runtime + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/Sirupsen/logrus" + "github.com/containerd/containerd/osutils" + "github.com/containerd/containerd/specs" + "golang.org/x/sys/unix" +) + +// Process holds the operation allowed on a container's process +type Process interface { + io.Closer + + // ID of the process. + // This is either "init" when it is the container's init process or + // it is a user provided id for the process similar to the container id + ID() string + // Start unblocks the associated container init process. + // This should only be called on the process with ID "init" + Start() error + CloseStdin() error + Resize(int, int) error + // ExitFD returns the fd the provides an event when the process exits + ExitFD() int + // ExitStatus returns the exit status of the process or an error if it + // has not exited + ExitStatus() (uint32, error) + // Spec returns the process spec that created the process + Spec() specs.ProcessSpec + // Signal sends the provided signal to the process + Signal(os.Signal) error + // Container returns the container that the process belongs to + Container() Container + // Stdio of the container + Stdio() Stdio + // SystemPid is the pid on the system + SystemPid() int + // State returns if the process is running or not + State() State + // Wait reaps the shim process if avaliable + Wait() +} + +type processConfig struct { + id string + root string + processSpec specs.ProcessSpec + spec *specs.Spec + c *container + stdio Stdio + exec bool + checkpoint string +} + +func newProcess(config *processConfig) (*process, error) { + p := &process{ + root: config.root, + id: config.id, + container: config.c, + spec: config.processSpec, + stdio: config.stdio, + cmdDoneCh: make(chan struct{}), + state: Running, + } + uid, gid, err := getRootIDs(config.spec) + if err != nil { + return nil, err + } + f, err := os.Create(filepath.Join(config.root, "process.json")) + if err != nil { + return nil, err + } + defer f.Close() + + ps := ProcessState{ + ProcessSpec: config.processSpec, + Exec: config.exec, + PlatformProcessState: PlatformProcessState{ + Checkpoint: config.checkpoint, + RootUID: uid, + RootGID: gid, + }, + Stdin: config.stdio.Stdin, + Stdout: config.stdio.Stdout, + Stderr: config.stdio.Stderr, + RuntimeArgs: config.c.runtimeArgs, + NoPivotRoot: config.c.noPivotRoot, + } + + if err := json.NewEncoder(f).Encode(ps); err != nil { + return nil, err + } + exit, err := getExitPipe(filepath.Join(config.root, ExitFile)) + if err != nil { + return nil, err + } + control, err := getControlPipe(filepath.Join(config.root, ControlFile)) + if err != nil { + return nil, err + } + p.exitPipe = exit + p.controlPipe = control + return p, nil +} + +func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) { + p := &process{ + root: root, + id: id, + container: c, + spec: s.ProcessSpec, + stdio: Stdio{ + Stdin: s.Stdin, + Stdout: s.Stdout, + Stderr: s.Stderr, + }, + state: Stopped, + } + + startTime, err := ioutil.ReadFile(filepath.Join(p.root, StartTimeFile)) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + p.startTime = string(startTime) + + if _, err := p.getPidFromFile(); err != nil { + return nil, err + } + if _, err := p.ExitStatus(); err != nil { + if err == ErrProcessNotExited { + exit, err := getExitPipe(filepath.Join(root, ExitFile)) + if err != nil { + return nil, err + } + p.exitPipe = exit + + control, err := getControlPipe(filepath.Join(root, ControlFile)) + if err != nil { + return nil, err + } + p.controlPipe = control + + p.state = Running + return p, nil + } + return nil, err + } + return p, nil +} + +func readProcStatField(pid int, field int) (string, error) { + data, err := ioutil.ReadFile(filepath.Join(string(filepath.Separator), "proc", strconv.Itoa(pid), "stat")) + if err != nil { + return "", err + } + + if field > 2 { + // First, split out the name since he could contains spaces. + parts := strings.Split(string(data), ") ") + // Now split out the rest, we end up with 2 fields less + parts = strings.Split(parts[1], " ") + return parts[field-2-1], nil // field count start at 1 in manual + } + + parts := strings.Split(string(data), " (") + + if field == 1 { + return parts[0], nil + } + + parts = strings.Split(parts[1], ") ") + return parts[0], nil +} + +type process struct { + root string + id string + pid int + exitPipe *os.File + controlPipe *os.File + container *container + spec specs.ProcessSpec + stdio Stdio + cmd *exec.Cmd + cmdSuccess bool + cmdDoneCh chan struct{} + state State + stateLock sync.Mutex + startTime string +} + +func (p *process) ID() string { + return p.id +} + +func (p *process) Container() Container { + return p.container +} + +func (p *process) SystemPid() int { + return p.pid +} + +// ExitFD returns the fd of the exit pipe +func (p *process) ExitFD() int { + return int(p.exitPipe.Fd()) +} + +func (p *process) CloseStdin() error { + _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0) + return err +} + +func (p *process) Resize(w, h int) error { + _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h) + return err +} + +func (p *process) updateExitStatusFile(status uint32) (uint32, error) { + p.stateLock.Lock() + p.state = Stopped + p.stateLock.Unlock() + err := ioutil.WriteFile(filepath.Join(p.root, ExitStatusFile), []byte(fmt.Sprintf("%u", status)), 0644) + return status, err +} + +func (p *process) handleSigkilledShim(rst uint32, rerr error) (uint32, error) { + if p.cmd == nil || p.cmd.Process == nil { + e := unix.Kill(p.pid, 0) + if e == syscall.ESRCH { + logrus.Warnf("containerd: %s:%s (pid %d) does not exist", p.container.id, p.id, p.pid) + // The process died while containerd was down (probably of + // SIGKILL, but no way to be sure) + return p.updateExitStatusFile(UnknownStatus) + } + + // If it's not the same process, just mark it stopped and set + // the status to the UnknownStatus value (i.e. 255) + if same, err := p.isSameProcess(); !same { + logrus.Warnf("containerd: %s:%s (pid %d) is not the same process anymore (%v)", p.container.id, p.id, p.pid, err) + // Create the file so we get the exit event generated once monitor kicks in + // without having to go through all this process again + return p.updateExitStatusFile(UnknownStatus) + } + + ppid, err := readProcStatField(p.pid, 4) + if err != nil { + return rst, fmt.Errorf("could not check process ppid: %v (%v)", err, rerr) + } + if ppid == "1" { + logrus.Warnf("containerd: %s:%s shim died, killing associated process", p.container.id, p.id) + unix.Kill(p.pid, syscall.SIGKILL) + if err != nil && err != syscall.ESRCH { + return UnknownStatus, fmt.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err) + } + + // wait for the process to die + for { + e := unix.Kill(p.pid, 0) + if e == syscall.ESRCH { + break + } + time.Sleep(5 * time.Millisecond) + } + // Create the file so we get the exit event generated once monitor kicks in + // without having to go through all this process again + return p.updateExitStatusFile(128 + uint32(syscall.SIGKILL)) + } + + return rst, rerr + } + + // Possible that the shim was SIGKILLED + e := unix.Kill(p.cmd.Process.Pid, 0) + if e != syscall.ESRCH { + return rst, rerr + } + + // Ensure we got the shim ProcessState + <-p.cmdDoneCh + + shimStatus := p.cmd.ProcessState.Sys().(syscall.WaitStatus) + if shimStatus.Signaled() && shimStatus.Signal() == syscall.SIGKILL { + logrus.Debugf("containerd: ExitStatus(container: %s, process: %s): shim was SIGKILL'ed reaping its child with pid %d", p.container.id, p.id, p.pid) + + rerr = nil + rst = 128 + uint32(shimStatus.Signal()) + + p.stateLock.Lock() + p.state = Stopped + p.stateLock.Unlock() + } + + return rst, rerr +} + +func (p *process) ExitStatus() (rst uint32, rerr error) { + data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile)) + defer func() { + if rerr != nil { + rst, rerr = p.handleSigkilledShim(rst, rerr) + } + }() + if err != nil { + if os.IsNotExist(err) { + return UnknownStatus, ErrProcessNotExited + } + return UnknownStatus, err + } + if len(data) == 0 { + return UnknownStatus, ErrProcessNotExited + } + p.stateLock.Lock() + p.state = Stopped + p.stateLock.Unlock() + + i, err := strconv.ParseUint(string(data), 10, 32) + return uint32(i), err +} + +func (p *process) Spec() specs.ProcessSpec { + return p.spec +} + +func (p *process) Stdio() Stdio { + return p.stdio +} + +// Close closes any open files and/or resouces on the process +func (p *process) Close() error { + err := p.exitPipe.Close() + if cerr := p.controlPipe.Close(); err == nil { + err = cerr + } + return err +} + +func (p *process) State() State { + p.stateLock.Lock() + defer p.stateLock.Unlock() + return p.state +} + +func (p *process) readStartTime() (string, error) { + return readProcStatField(p.pid, 22) +} + +func (p *process) saveStartTime() error { + startTime, err := p.readStartTime() + if err != nil { + return err + } + + p.startTime = startTime + return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644) +} + +func (p *process) isSameProcess() (bool, error) { + if p.pid == 0 { + _, err := p.getPidFromFile() + if err != nil { + return false, err + } + } + + // for backward compat assume it's the same if startTime wasn't set + if p.startTime == "" { + // Sometimes the process dies before we can get the starttime, + // check that the process actually exists + if err := unix.Kill(p.pid, 0); err != syscall.ESRCH { + return true, nil + } + return false, nil + } + + startTime, err := p.readStartTime() + if err != nil { + return false, err + } + + return startTime == p.startTime, nil +} + +// Wait will reap the shim process +func (p *process) Wait() { + if p.cmdDoneCh != nil { + <-p.cmdDoneCh + } +} + +func getExitPipe(path string) (*os.File, error) { + if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + // add NONBLOCK in case the other side has already closed or else + // this function would never return + return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) +} + +func getControlPipe(path string) (*os.File, error) { + if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0) +} + +// Signal sends the provided signal to the process +func (p *process) Signal(s os.Signal) error { + return syscall.Kill(p.pid, s.(syscall.Signal)) +} + +// Start unblocks the associated container init process. +// This should only be called on the process with ID "init" +func (p *process) Start() error { + if p.ID() == InitProcessID { + var ( + errC = make(chan error, 1) + args = append(p.container.runtimeArgs, "start", p.container.id) + cmd = exec.Command(p.container.runtime, args...) + ) + go func() { + out, err := cmd.CombinedOutput() + if err != nil { + errC <- fmt.Errorf("%s: %q", err.Error(), out) + } + errC <- nil + }() + select { + case err := <-errC: + if err != nil { + return err + } + case <-p.cmdDoneCh: + if !p.cmdSuccess { + if cmd.Process != nil { + cmd.Process.Kill() + } + cmd.Wait() + return ErrShimExited + } + err := <-errC + if err != nil { + return err + } + } + } + return nil +} + +// Delete delete any resources held by the container +func (p *process) Delete() error { + var ( + args = append(p.container.runtimeArgs, "delete", "-f", p.container.id) + cmd = exec.Command(p.container.runtime, args...) + ) + + cmd.SysProcAttr = osutils.SetPDeathSig() + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %v", out, err) + } + return nil +} diff --git a/vendor/github.com/containerd/containerd/runtime/process_linux.go b/vendor/github.com/containerd/containerd/runtime/process_linux.go new file mode 100644 index 0000000000..d14c4d8d82 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/process_linux.go @@ -0,0 +1,22 @@ +// +build linux + +package runtime + +import ( + "io/ioutil" + "path/filepath" + "strconv" +) + +func (p *process) getPidFromFile() (int, error) { + data, err := ioutil.ReadFile(filepath.Join(p.root, "pid")) + if err != nil { + return -1, err + } + i, err := strconv.Atoi(string(data)) + if err != nil { + return -1, errInvalidPidInt + } + p.pid = i + return i, nil +} diff --git a/vendor/github.com/containerd/containerd/runtime/process_solaris.go b/vendor/github.com/containerd/containerd/runtime/process_solaris.go new file mode 100644 index 0000000000..8159f30507 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/process_solaris.go @@ -0,0 +1,34 @@ +// +build solaris + +package runtime + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + + runtimespec "github.com/opencontainers/runtime-spec/specs-go" +) + +// On Solaris we already have a state file maintained by the framework. +// This is read by runz state. We just call that instead of maintaining +// a separate file. +func (p *process) getPidFromFile() (int, error) { + //we get this information from runz state + cmd := exec.Command("runc", "state", p.container.ID()) + outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer) + cmd.Stdout, cmd.Stderr = outBuf, errBuf + + if err := cmd.Run(); err != nil { + // TODO: Improve logic + return -1, errContainerNotFound + } + response := runtimespec.State{} + decoder := json.NewDecoder(outBuf) + if err := decoder.Decode(&response); err != nil { + return -1, fmt.Errorf("unable to decode json response: %+v", err) + } + p.pid = response.Pid + return p.pid, nil +} diff --git a/vendor/github.com/containerd/containerd/runtime/runtime.go b/vendor/github.com/containerd/containerd/runtime/runtime.go new file mode 100644 index 0000000000..eaba45285d --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/runtime.go @@ -0,0 +1,132 @@ +package runtime + +import ( + "errors" + "time" + + "github.com/containerd/containerd/specs" +) + +var ( + // ErrContainerExited is returned when access to an exited + // container is attempted + ErrContainerExited = errors.New("containerd: container has exited") + // ErrProcessNotExited is returned when trying to retrieve the exit + // status of an alive process + ErrProcessNotExited = errors.New("containerd: process has not exited") + // ErrContainerNotStarted is returned when a container fails to + // start without error from the shim or the OCI runtime + ErrContainerNotStarted = errors.New("containerd: container not started") + // ErrContainerStartTimeout is returned if a container takes too + // long to start + ErrContainerStartTimeout = errors.New("containerd: container did not start before the specified timeout") + // ErrShimExited is returned if the shim or the contianer's init process + // exits before completing + ErrShimExited = errors.New("containerd: shim exited before container process was started") + + errNoPidFile = errors.New("containerd: no process pid file found") + errInvalidPidInt = errors.New("containerd: process pid is invalid") + errContainerNotFound = errors.New("containerd: container not found") + errNotImplemented = errors.New("containerd: not implemented") +) + +const ( + // ExitFile holds the name of the pipe used to monitor process + // exit + ExitFile = "exit" + // ExitStatusFile holds the name of the file where the container + // exit code is to be written + ExitStatusFile = "exitStatus" + // StateFile holds the name of the file where the container state + // is written + StateFile = "state.json" + // ControlFile holds the name of the pipe used to control the shim + ControlFile = "control" + // InitProcessID holds the special ID used for the very first + // container's process + InitProcessID = "init" + // StartTimeFile holds the name of the file in which the process + // start time is saved + StartTimeFile = "starttime" + + // UnknownStatus is the value returned when a process exit + // status cannot be determined + UnknownStatus = 255 +) + +// Checkpoint holds information regarding a container checkpoint +type Checkpoint struct { + // Timestamp is the time that checkpoint happened + Created time.Time `json:"created"` + // Name is the name of the checkpoint + Name string `json:"name"` + // TCP checkpoints open tcp connections + TCP bool `json:"tcp"` + // UnixSockets persists unix sockets in the checkpoint + UnixSockets bool `json:"unixSockets"` + // Shell persists tty sessions in the checkpoint + Shell bool `json:"shell"` + // Exit exits the container after the checkpoint is finished + Exit bool `json:"exit"` + // EmptyNS tells CRIU to omit a specified namespace + EmptyNS []string `json:"emptyNS,omitempty"` +} + +// PlatformProcessState container platform-specific fields in the ProcessState structure +type PlatformProcessState struct { + Checkpoint string `json:"checkpoint"` + RootUID int `json:"rootUID"` + RootGID int `json:"rootGID"` +} + +// State represents a container state +type State string + +// Resource regroups the various container limits that can be updated +type Resource struct { + CPUShares int64 + BlkioWeight uint16 + CPUPeriod int64 + CPUQuota int64 + CpusetCpus string + CpusetMems string + KernelMemory int64 + KernelTCPMemory int64 + Memory int64 + MemoryReservation int64 + MemorySwap int64 + PidsLimit int64 +} + +// Possible container states +const ( + Paused = State("paused") + Stopped = State("stopped") + Running = State("running") +) + +type state struct { + Bundle string `json:"bundle"` + Labels []string `json:"labels"` + Stdin string `json:"stdin"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + Runtime string `json:"runtime"` + RuntimeArgs []string `json:"runtimeArgs"` + Shim string `json:"shim"` + NoPivotRoot bool `json:"noPivotRoot"` +} + +// ProcessState holds the process OCI specs along with various fields +// required by containerd +type ProcessState struct { + specs.ProcessSpec + Exec bool `json:"exec"` + Stdin string `json:"containerdStdin"` + Stdout string `json:"containerdStdout"` + Stderr string `json:"containerdStderr"` + RuntimeArgs []string `json:"runtimeArgs"` + NoPivotRoot bool `json:"noPivotRoot"` + + PlatformProcessState +} diff --git a/vendor/github.com/containerd/containerd/runtime/stats.go b/vendor/github.com/containerd/containerd/runtime/stats.go new file mode 100644 index 0000000000..4cfa9e9040 --- /dev/null +++ b/vendor/github.com/containerd/containerd/runtime/stats.go @@ -0,0 +1,87 @@ +package runtime + +import "time" + +// Stat holds a container statistics +type Stat struct { + // Timestamp is the time that the statistics where collected + Timestamp time.Time + CPU CPU `json:"cpu"` + Memory Memory `json:"memory"` + Pids Pids `json:"pids"` + Blkio Blkio `json:"blkio"` + Hugetlb map[string]Hugetlb `json:"hugetlb"` +} + +// Hugetlb holds information regarding a container huge tlb usage +type Hugetlb struct { + Usage uint64 `json:"usage,omitempty"` + Max uint64 `json:"max,omitempty"` + Failcnt uint64 `json:"failcnt"` +} + +// BlkioEntry represents a single record for a Blkio stat +type BlkioEntry struct { + Major uint64 `json:"major,omitempty"` + Minor uint64 `json:"minor,omitempty"` + Op string `json:"op,omitempty"` + Value uint64 `json:"value,omitempty"` +} + +// Blkio regroups all the Blkio related stats +type Blkio struct { + IoServiceBytesRecursive []BlkioEntry `json:"ioServiceBytesRecursive,omitempty"` + IoServicedRecursive []BlkioEntry `json:"ioServicedRecursive,omitempty"` + IoQueuedRecursive []BlkioEntry `json:"ioQueueRecursive,omitempty"` + IoServiceTimeRecursive []BlkioEntry `json:"ioServiceTimeRecursive,omitempty"` + IoWaitTimeRecursive []BlkioEntry `json:"ioWaitTimeRecursive,omitempty"` + IoMergedRecursive []BlkioEntry `json:"ioMergedRecursive,omitempty"` + IoTimeRecursive []BlkioEntry `json:"ioTimeRecursive,omitempty"` + SectorsRecursive []BlkioEntry `json:"sectorsRecursive,omitempty"` +} + +// Pids holds the stat of the pid usage of the machine +type Pids struct { + Current uint64 `json:"current,omitempty"` + Limit uint64 `json:"limit,omitempty"` +} + +// Throttling holds a cpu throttling information +type Throttling struct { + Periods uint64 `json:"periods,omitempty"` + ThrottledPeriods uint64 `json:"throttledPeriods,omitempty"` + ThrottledTime uint64 `json:"throttledTime,omitempty"` +} + +// CPUUsage holds information regarding cpu usage +type CPUUsage struct { + // Units: nanoseconds. + Total uint64 `json:"total,omitempty"` + Percpu []uint64 `json:"percpu,omitempty"` + Kernel uint64 `json:"kernel"` + User uint64 `json:"user"` +} + +// CPU regroups both a CPU usage and throttling information +type CPU struct { + Usage CPUUsage `json:"usage,omitempty"` + Throttling Throttling `json:"throttling,omitempty"` +} + +// MemoryEntry regroups statistic about a given type of memory +type MemoryEntry struct { + Limit uint64 `json:"limit"` + Usage uint64 `json:"usage,omitempty"` + Max uint64 `json:"max,omitempty"` + Failcnt uint64 `json:"failcnt"` +} + +// Memory holds information regarding the different type of memories available +type Memory struct { + Cache uint64 `json:"cache,omitempty"` + Usage MemoryEntry `json:"usage,omitempty"` + Swap MemoryEntry `json:"swap,omitempty"` + Kernel MemoryEntry `json:"kernel,omitempty"` + KernelTCP MemoryEntry `json:"kernelTCP,omitempty"` + Raw map[string]uint64 `json:"raw,omitempty"` +} diff --git a/vendor/github.com/containerd/containerd/specs/spec_linux.go b/vendor/github.com/containerd/containerd/specs/spec_linux.go new file mode 100644 index 0000000000..0b31604750 --- /dev/null +++ b/vendor/github.com/containerd/containerd/specs/spec_linux.go @@ -0,0 +1,12 @@ +package specs + +import oci "github.com/opencontainers/runtime-spec/specs-go" + +type ( + // ProcessSpec aliases the platform process specs + ProcessSpec oci.Process + // Spec aliases the platform oci spec + Spec oci.Spec + // Rlimit aliases the platform resource limit + Rlimit oci.LinuxRlimit +) diff --git a/vendor/github.com/containerd/containerd/specs/spec_solaris.go b/vendor/github.com/containerd/containerd/specs/spec_solaris.go new file mode 100644 index 0000000000..1b60d3d36e --- /dev/null +++ b/vendor/github.com/containerd/containerd/specs/spec_solaris.go @@ -0,0 +1,10 @@ +package specs + +import ocs "github.com/opencontainers/runtime-spec/specs-go" + +type ( + // ProcessSpec aliases the platform process specs + ProcessSpec ocs.Process + // Spec aliases the platform oci spec + Spec ocs.Spec +) diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go index 8beba9d300..1f0b3ee285 100644 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go +++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go @@ -64,12 +64,12 @@ func IsNamespaceSupported(ns NamespaceType) bool { func NamespaceTypes() []NamespaceType { return []NamespaceType{ + NEWUSER, // Keep user NS always first, don't move it. + NEWIPC, + NEWUTS, NEWNET, NEWPID, NEWNS, - NEWUTS, - NEWIPC, - NEWUSER, } } diff --git a/vendor/github.com/pmezard/go-difflib/README.md b/vendor/github.com/pmezard/go-difflib/README.md new file mode 100644 index 0000000000..e87f307ed4 --- /dev/null +++ b/vendor/github.com/pmezard/go-difflib/README.md @@ -0,0 +1,50 @@ +go-difflib +========== + +[![Build Status](https://travis-ci.org/pmezard/go-difflib.png?branch=master)](https://travis-ci.org/pmezard/go-difflib) +[![GoDoc](https://godoc.org/github.com/pmezard/go-difflib/difflib?status.svg)](https://godoc.org/github.com/pmezard/go-difflib/difflib) + +Go-difflib is a partial port of python 3 difflib package. Its main goal +was to make unified and context diff available in pure Go, mostly for +testing purposes. + +The following class and functions (and related tests) have be ported: + +* `SequenceMatcher` +* `unified_diff()` +* `context_diff()` + +## Installation + +```bash +$ go get github.com/pmezard/go-difflib/difflib +``` + +### Quick Start + +Diffs are configured with Unified (or ContextDiff) structures, and can +be output to an io.Writer or returned as a string. + +```Go +diff := UnifiedDiff{ + A: difflib.SplitLines("foo\nbar\n"), + B: difflib.SplitLines("foo\nbaz\n"), + FromFile: "Original", + ToFile: "Current", + Context: 3, +} +text, _ := GetUnifiedDiffString(diff) +fmt.Printf(text) +``` + +would output: + +``` +--- Original ++++ Current +@@ -1,3 +1,3 @@ + foo +-bar ++baz +``` +