diff options
Diffstat (limited to 'runsc/sandbox')
-rw-r--r-- | runsc/sandbox/BUILD | 25 | ||||
-rw-r--r-- | runsc/sandbox/hook.go | 111 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 430 | ||||
-rw-r--r-- | runsc/sandbox/sandbox_test.go | 665 | ||||
-rw-r--r-- | runsc/sandbox/status.go | 56 |
5 files changed, 95 insertions, 1192 deletions
diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD index bdd95903e..e89b19552 100644 --- a/runsc/sandbox/BUILD +++ b/runsc/sandbox/BUILD @@ -1,16 +1,14 @@ package(licenses = ["notice"]) # Apache 2.0 -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "sandbox", srcs = [ "console.go", - "hook.go", "namespace.go", "network.go", "sandbox.go", - "status.go", ], importpath = "gvisor.googlesource.com/gvisor/runsc/sandbox", visibility = [ @@ -30,24 +28,3 @@ go_library( "@org_golang_x_sys//unix:go_default_library", ], ) - -go_test( - name = "sandbox_test", - size = "small", - srcs = ["sandbox_test.go"], - pure = "on", - rundir = ".", - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/kernel/auth", - "//pkg/unet", - "//runsc/boot", - "//runsc/cmd", - "//runsc/sandbox", - "@com_github_google_subcommands//:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/sandbox/hook.go b/runsc/sandbox/hook.go deleted file mode 100644 index 40b064cdc..000000000 --- a/runsc/sandbox/hook.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sandbox - -import ( - "bytes" - "encoding/json" - "fmt" - "os/exec" - "path/filepath" - "strings" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.googlesource.com/gvisor/pkg/log" -) - -// This file implements hooks as defined in OCI spec: -// https://github.com/opencontainers/runtime-spec/blob/master/config.md#toc22 -// -// "hooks":{ -// "prestart":[{ -// "path":"/usr/bin/dockerd", -// "args":[ -// "libnetwork-setkey", "arg2", -// ] -// }] -// }, - -// executeHooksBestEffort executes hooks and logs warning in case they fail. -// Runs all hooks, always. -func executeHooksBestEffort(hooks []specs.Hook, s specs.State) { - for _, h := range hooks { - if err := executeHook(h, s); err != nil { - log.Warningf("Failure to execute hook %+v, err: %v", h, err) - } - } -} - -// executeHooks executes hooks until the first one fails or they all execute. -func executeHooks(hooks []specs.Hook, s specs.State) error { - for _, h := range hooks { - if err := executeHook(h, s); err != nil { - return err - } - } - return nil -} - -func executeHook(h specs.Hook, s specs.State) error { - log.Debugf("Executing hook %+v, state: %+v", h, s) - - if strings.TrimSpace(h.Path) == "" { - return fmt.Errorf("empty path for hook") - } - if !filepath.IsAbs(h.Path) { - return fmt.Errorf("path for hook is not absolute: %q", h.Path) - } - - b, err := json.Marshal(s) - if err != nil { - return err - } - var stdout, stderr bytes.Buffer - cmd := exec.Cmd{ - Path: h.Path, - Args: h.Args, - Env: h.Env, - Stdin: bytes.NewReader(b), - Stdout: &stdout, - Stderr: &stderr, - } - if err := cmd.Start(); err != nil { - return err - } - - c := make(chan error, 1) - go func() { - c <- cmd.Wait() - }() - - var timer <-chan time.Time - if h.Timeout != nil { - timer = time.After(time.Duration(*h.Timeout) * time.Second) - } - select { - case err := <-c: - if err != nil { - return fmt.Errorf("failure executing hook %q, err: %v\nstdout: %s\nstderr: %s", h.Path, err, stdout.String(), stderr.String()) - } - case <-timer: - cmd.Process.Kill() - cmd.Wait() - return fmt.Errorf("timeout executing hook %q\nstdout: %s\nstderr: %s", h.Path, stdout.String(), stderr.String()) - } - - log.Debugf("Execute hook %q success!", h.Path) - return nil -} diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 34bd6ea67..5dfa4cf0b 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -16,13 +16,9 @@ package sandbox import ( - "encoding/json" "fmt" - "io/ioutil" "os" "os/exec" - "path/filepath" - "regexp" "strconv" "syscall" "time" @@ -38,308 +34,110 @@ import ( "gvisor.googlesource.com/gvisor/runsc/specutils" ) -// metadataFilename is the name of the metadata file relative to sandboxRoot -// that holds sandbox metadata. -const metadataFilename = "meta.json" - -// See libcontainer/factory_linux.go -var idRegex = regexp.MustCompile(`^[\w+-\.]+$`) - -// validateID validates the sandbox id. -func validateID(id string) error { - if !idRegex.MatchString(id) { - return fmt.Errorf("invalid sandbox id: %v", id) - } - return nil -} - -func validateSpec(spec *specs.Spec) error { - if spec.Process.SelinuxLabel != "" { - return fmt.Errorf("SELinux is not supported: %s", spec.Process.SelinuxLabel) - } - - // Docker uses AppArmor by default, so just log that it's being ignored. - if spec.Process.ApparmorProfile != "" { - log.Warningf("AppArmor profile %q is being ignored", spec.Process.ApparmorProfile) - } - // TODO: Apply seccomp to application inside sandbox. - if spec.Linux != nil && spec.Linux.Seccomp != nil { - log.Warningf("Seccomp spec is being ignored") - } - return nil -} - -// Sandbox wraps a child sandbox process, and is responsible for saving and -// loading sandbox metadata to disk. -// -// Within a root directory, we maintain subdirectories for each sandbox named -// with the sandbox id. The sandbox metadata is is stored as json within the -// sandbox directory in a file named "meta.json". This metadata format is -// defined by us, and is not part of the OCI spec. -// -// Sandboxes must write this metadata file after any change to their internal -// state. The entire sandbox directory is deleted when the sandbox is -// destroyed. +// Sandbox wraps a sandbox process. // -// TODO: Protect against concurrent changes to the sandbox metadata -// file. +// It is used to start/stop sandbox process (and associated processes like +// gofers), as well as for running and manipulating containers inside a running +// sandbox. type Sandbox struct { - // ID is the sandbox ID. + // ID is the id of the sandbox. By convention, this is the same ID as + // the first container run in the sandbox. ID string `json:"id"` - // Spec is the OCI runtime spec that configures this sandbox. - Spec *specs.Spec `json:"spec"` - - // BundleDir is the directory containing the sandbox bundle. - BundleDir string `json:"bundleDir"` - - // SandboxRoot is the directory containing the sandbox metadata file. - SandboxRoot string `json:"sandboxRoot"` - - // CreatedAt is the time the sandbox was created. - CreatedAt time.Time `json:"createdAt"` - - // Owner is the sandbox owner. - Owner string `json:"owner"` - - // ConsoleSocket is the path to a unix domain socket that will receive - // the console FD. It is only used during create, so we don't need to - // store it in the metadata. - ConsoleSocket string `json:"-"` - - // Pid is the pid of the running sandbox. Only valid if Status is - // Created or Running. + // Pid is the pid of the running sandbox. May be 0 is the sandbox is + // not running. Pid int `json:"pid"` - // GoferPid is the pid of the gofer running along side the sandbox. May be 0 - // if the gofer has been killed or it's not being used. + // GoferPid is the pid of the gofer running along side the sandbox. May + // be 0 if the gofer has been killed or it's not being used. GoferPid int `json:"goferPid"` - - // Status is the current sandbox Status. - Status Status `json:"status"` } -// Create creates the sandbox subprocess and writes the metadata file. Args -// are additional arguments that will be passed to the sandbox process. -func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, pidFile string, args []string) (*Sandbox, error) { - log.Debugf("Create sandbox %q in root dir: %s", id, conf.RootDir) - if err := validateID(id); err != nil { - return nil, err - } - if err := validateSpec(spec); err != nil { - return nil, err - } - - sandboxRoot := filepath.Join(conf.RootDir, id) - if exists(sandboxRoot) { - return nil, fmt.Errorf("sandbox with id %q already exists: %q ", id, sandboxRoot) - } +// Create creates the sandbox process. +func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket string) (*Sandbox, error) { + s := &Sandbox{ID: id} - s := &Sandbox{ - ID: id, - Spec: spec, - ConsoleSocket: consoleSocket, - BundleDir: bundleDir, - SandboxRoot: sandboxRoot, - Status: Creating, - Owner: os.Getenv("USER"), - } - - // Create sandbox process. If anything errors between now and the end of this - // function, we MUST clean up all sandbox resources. - if err := s.createProcesses(conf, args); err != nil { - s.Destroy() + binPath, err := specutils.BinPath() + if err != nil { return nil, err } - // Wait for the control server to come up (or timeout). The sandbox is - // not "created" until that happens. - if err := s.waitForCreated(10 * time.Second); err != nil { - s.Destroy() + // Create the gofer process. + ioFiles, err := s.createGoferProcess(spec, conf, bundleDir, binPath) + if err != nil { return nil, err } - s.Status = Created - s.CreatedAt = time.Now() - - // Save the metadata file. - if err := s.save(); err != nil { - s.Destroy() + // Create the sandbox process. + if err := s.createSandboxProcess(spec, conf, bundleDir, consoleSocket, binPath, ioFiles); err != nil { return nil, err } - // Write the pid file. Containerd consideres the create complete after - // this file is created, so it must be the last thing we do. - if pidFile != "" { - if err := ioutil.WriteFile(pidFile, []byte(strconv.Itoa(s.Pid)), 0644); err != nil { - s.Destroy() - return nil, fmt.Errorf("error writing pid file: %v", err) - } - } - - return s, nil -} - -// Run is a helper that calls Create + Start + Wait. -func Run(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, pidFile string, args []string) (syscall.WaitStatus, error) { - s, err := Create(id, spec, conf, bundleDir, consoleSocket, pidFile, args) - if err != nil { - return 0, fmt.Errorf("error creating sandbox: %v", err) - } - if err := s.Start(conf); err != nil { - return 0, fmt.Errorf("error starting sandbox: %v", err) - } - return s.Wait() -} - -// Load loads a sandbox from with the given id from a metadata file. -func Load(rootDir, id string) (*Sandbox, error) { - log.Debugf("Load sandbox %q %q", rootDir, id) - if err := validateID(id); err != nil { + // Wait for the control server to come up (or timeout). + if err := s.waitForCreated(10 * time.Second); err != nil { return nil, err } - sandboxRoot := filepath.Join(rootDir, id) - if !exists(sandboxRoot) { - return nil, fmt.Errorf("sandbox with id %q does not exist", id) - } - metaFile := filepath.Join(sandboxRoot, metadataFilename) - if !exists(metaFile) { - return nil, fmt.Errorf("sandbox with id %q does not have metadata file %q", id, metaFile) - } - metaBytes, err := ioutil.ReadFile(metaFile) - if err != nil { - return nil, fmt.Errorf("error reading sandbox metadata file %q: %v", metaFile, err) - } - var s Sandbox - if err := json.Unmarshal(metaBytes, &s); err != nil { - return nil, fmt.Errorf("error unmarshaling sandbox metadata from %q: %v", metaFile, err) - } - - // If the status is "Running" or "Created", check that the process - // still exists, and set it to Stopped if it does not. - // - // This is inherently racey. - if s.Status == Running || s.Status == Created { - // Send signal 0 to check if process exists. - if err := s.Signal(0); err != nil { - // Process no longer exists. - s.Status = Stopped - s.Pid = 0 - } - } - return &s, nil -} - -// List returns all sandbox ids in the given root directory. -func List(rootDir string) ([]string, error) { - log.Debugf("List sandboxes %q", rootDir) - fs, err := ioutil.ReadDir(rootDir) - if err != nil { - return nil, fmt.Errorf("ReadDir(%s) failed: %v", rootDir, err) - } - var out []string - for _, f := range fs { - out = append(out, f.Name()) - } - return out, nil -} - -// State returns the metadata of the sandbox. -func (s *Sandbox) State() specs.State { - return specs.State{ - Version: specs.Version, - ID: s.ID, - Status: s.Status.String(), - Pid: s.Pid, - Bundle: s.BundleDir, - } + return s, nil } // Start starts running the containerized process inside the sandbox. -func (s *Sandbox) Start(conf *boot.Config) error { +func (s *Sandbox) Start(cid string, spec *specs.Spec, conf *boot.Config) error { log.Debugf("Start sandbox %q, pid: %d", s.ID, s.Pid) - if s.Status != Created { - return fmt.Errorf("cannot start container in state %s", s.Status) - } - - // "If any prestart hook fails, the runtime MUST generate an error, - // stop and destroy the container". - if s.Spec.Hooks != nil { - if err := executeHooks(s.Spec.Hooks.Prestart, s.State()); err != nil { - s.Destroy() - return err - } - } - - c, err := s.connect() + conn, err := s.connect() if err != nil { - s.Destroy() return err } - defer c.Close() + defer conn.Close() // Configure the network. - if err := setupNetwork(c, s.Pid, s.Spec, conf); err != nil { - s.Destroy() + if err := setupNetwork(conn, s.Pid, spec, conf); err != nil { return fmt.Errorf("error setting up network: %v", err) } // Send a message to the sandbox control server to start the // application. - if err := c.Call(boot.ApplicationStart, nil, nil); err != nil { - s.Destroy() - return fmt.Errorf("error starting application %v: %v", s.Spec.Process.Args, err) - } - - // "If any poststart hook fails, the runtime MUST log a warning, but - // the remaining hooks and lifecycle continue as if the hook had - // succeeded". - if s.Spec.Hooks != nil { - executeHooksBestEffort(s.Spec.Hooks.Poststart, s.State()) + // + // TODO: Pass in the container id (cid) here. The sandbox + // should start only that container. + if err := conn.Call(boot.ApplicationStart, nil, nil); err != nil { + return fmt.Errorf("error starting application %v: %v", spec.Process.Args, err) } - s.Status = Running - return s.save() + return nil } -// Processes retrieves the list of processes and associated metadata inside a -// sandbox. -func (s *Sandbox) Processes() ([]*control.Process, error) { - if s.Status != Running { - return nil, fmt.Errorf("cannot get processes of container %q because it isn't running. It is in state %v", s.ID, s.Status) - } - - c, err := s.connect() +// Processes retrieves the list of processes and associated metadata for a +// given container in this sandbox. +func (s *Sandbox) Processes(cid string) ([]*control.Process, error) { + conn, err := s.connect() if err != nil { return nil, err } - defer c.Close() + defer conn.Close() var pl []*control.Process - if err := c.Call(boot.ApplicationProcesses, nil, &pl); err != nil { + // TODO: Pass in the container id (cid) here. The sandbox + // should return process info for only that container. + if err := conn.Call(boot.ApplicationProcesses, nil, &pl); err != nil { return nil, fmt.Errorf("error retrieving process data from sandbox: %v", err) } return pl, nil } -// Execute runs the specified command in the sandbox. -func (s *Sandbox) Execute(e *control.ExecArgs) (syscall.WaitStatus, error) { - log.Debugf("Execute in sandbox %q, pid: %d, args: %+v", s.ID, s.Pid, e) - if s.Status != Created && s.Status != Running { - return 0, fmt.Errorf("cannot exec in container in state %s", s.Status) - } - - log.Debugf("Connecting to sandbox...") - c, err := s.connect() +// Execute runs the specified command in the container. +func (s *Sandbox) Execute(cid string, e *control.ExecArgs) (syscall.WaitStatus, error) { + conn, err := s.connect() if err != nil { return 0, fmt.Errorf("error connecting to control server at pid %d: %v", s.Pid, err) } - defer c.Close() + defer conn.Close() // Send a message to the sandbox control server to start the application. var waitStatus uint32 - if err := c.Call(boot.ApplicationExecute, e, &waitStatus); err != nil { + // TODO: Pass in the container id (cid) here. The sandbox + // should execute in the context of that container. + if err := conn.Call(boot.ApplicationExecute, e, &waitStatus); err != nil { return 0, fmt.Errorf("error executing in sandbox: %v", err) } @@ -347,60 +145,45 @@ func (s *Sandbox) Execute(e *control.ExecArgs) (syscall.WaitStatus, error) { } // Event retrieves stats about the sandbox such as memory and CPU utilization. -func (s *Sandbox) Event() (*boot.Event, error) { - if s.Status != Running && s.Status != Created { - return nil, fmt.Errorf("cannot get events for container in state: %s", s.Status) - } - - c, err := s.connect() +func (s *Sandbox) Event(cid string) (*boot.Event, error) { + conn, err := s.connect() if err != nil { return nil, err } - defer c.Close() + defer conn.Close() var e boot.Event - if err := c.Call(boot.ApplicationEvent, nil, &e); err != nil { + // TODO: Pass in the container id (cid) here. The sandbox + // should return events only for that container. + if err := conn.Call(boot.ApplicationEvent, nil, &e); err != nil { return nil, fmt.Errorf("error retrieving event data from sandbox: %v", err) } - e.ID = s.ID + e.ID = cid return &e, nil } func (s *Sandbox) connect() (*urpc.Client, error) { log.Debugf("Connecting to sandbox...") - c, err := client.ConnectTo(boot.ControlSocketAddr(s.ID)) + conn, err := client.ConnectTo(boot.ControlSocketAddr(s.ID)) if err != nil { return nil, fmt.Errorf("error connecting to control server at pid %d: %v", s.Pid, err) } - return c, nil -} - -func (s *Sandbox) createProcesses(conf *boot.Config, args []string) error { - binPath, err := specutils.BinPath() - if err != nil { - return err - } - - ioFiles, err := s.createGoferProcess(conf, binPath, args) - if err != nil { - return err - } - return s.createSandboxProcess(conf, binPath, args, ioFiles) + return conn, nil } -func (s *Sandbox) createGoferProcess(conf *boot.Config, binPath string, commonArgs []string) ([]*os.File, error) { +func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir, binPath string) ([]*os.File, error) { if conf.FileAccess != boot.FileAccessProxy { // Don't start a gofer. The sandbox will access host FS directly. return nil, nil } - var args []string - args = append(args, commonArgs...) - args = append(args, "gofer", "--bundle", s.BundleDir) + // Start with the general config flags. + args := conf.ToFlags() + args = append(args, "gofer", "--bundle", bundleDir) - // Start with root mount and then add any other additional mount. + // Add root mount and then add any other additional mounts. mountCount := 1 - for _, m := range s.Spec.Mounts { + for _, m := range spec.Mounts { if specutils.Is9PMount(m) { mountCount++ } @@ -429,8 +212,8 @@ func (s *Sandbox) createGoferProcess(conf *boot.Config, binPath string, commonAr // Setup any uid/gid mappings, and create or join the configured user // namespace so the gofer's view of the filesystem aligns with the // users in the sandbox. - setUIDGIDMappings(cmd, s.Spec) - nss := filterNS([]specs.LinuxNamespaceType{specs.UserNamespace}, s.Spec) + setUIDGIDMappings(cmd, spec) + nss := filterNS([]specs.LinuxNamespaceType{specs.UserNamespace}, spec) // Start the gofer in the given namespace. log.Debugf("Starting gofer: %s %v", binPath, args) @@ -444,7 +227,7 @@ func (s *Sandbox) createGoferProcess(conf *boot.Config, binPath string, commonAr // createSandboxProcess starts the sandbox as a subprocess by running the "boot" // command, passing in the bundle dir. -func (s *Sandbox) createSandboxProcess(conf *boot.Config, binPath string, commonArgs []string, ioFiles []*os.File) error { +func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, binPath string, ioFiles []*os.File) error { // nextFD is used to get unused FDs that we can pass to the sandbox. It // starts at 3 because 0, 1, and 2 are taken by stdin/out/err. nextFD := 3 @@ -457,13 +240,13 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, binPath string, common return fmt.Errorf("error creating control server socket for sandbox %q: %v", s.ID, err) } - consoleEnabled := s.ConsoleSocket != "" + consoleEnabled := consoleSocket != "" - cmd := exec.Command(binPath, commonArgs...) + cmd := exec.Command(binPath, conf.ToFlags()...) cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.Args = append(cmd.Args, "boot", - "--bundle", s.BundleDir, + "--bundle", bundleDir, "--controller-fd="+strconv.Itoa(nextFD), fmt.Sprintf("--console=%t", consoleEnabled)) nextFD++ @@ -485,9 +268,9 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, binPath string, common if consoleEnabled { // setupConsole will send the master on the socket, and return // the slave. - tty, err := setupConsole(s.ConsoleSocket) + tty, err := setupConsole(consoleSocket) if err != nil { - return fmt.Errorf("error setting up control socket %q: %v", s.ConsoleSocket, err) + return fmt.Errorf("error setting up control socket %q: %v", consoleSocket, err) } defer tty.Close() @@ -535,7 +318,7 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, binPath string, common // Joins the network namespace if network is enabled. the sandbox talks // directly to the host network, which may have been configured in the // namespace. - if ns, ok := getNS(specs.NetworkNamespace, s.Spec); ok && conf.Network != boot.NetworkNone { + if ns, ok := getNS(specs.NetworkNamespace, spec); ok && conf.Network != boot.NetworkNone { log.Infof("Sandbox will be started in the container's network namespace: %+v", ns) nss = append(nss, ns) } else { @@ -549,10 +332,10 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, binPath string, common // - Gofer: when using a Gofer, the sandbox process can run isolated in an // empty namespace. if conf.Network == boot.NetworkHost || conf.FileAccess == boot.FileAccessDirect { - if userns, ok := getNS(specs.UserNamespace, s.Spec); ok { + if userns, ok := getNS(specs.UserNamespace, spec); ok { log.Infof("Sandbox will be started in container's user namespace: %+v", userns) nss = append(nss, userns) - setUIDGIDMappings(cmd, s.Spec) + setUIDGIDMappings(cmd, spec) } else { log.Infof("Sandbox will be started in the current user namespace") } @@ -596,8 +379,10 @@ func (s *Sandbox) waitForCreated(timeout time.Duration) error { } // Wait waits for the containerized process to exit, and returns its WaitStatus. -func (s *Sandbox) Wait() (syscall.WaitStatus, error) { - log.Debugf("Wait on sandbox %q with pid %d", s.ID, s.Pid) +func (s *Sandbox) Wait(cid string) (syscall.WaitStatus, error) { + // TODO: This waits on the sandbox process. We need a way + // to wait on an individual container in the sandbox. + p, err := os.FindProcess(s.Pid) if err != nil { // "On Unix systems, FindProcess always succeeds and returns a @@ -611,6 +396,13 @@ func (s *Sandbox) Wait() (syscall.WaitStatus, error) { return ps.Sys().(syscall.WaitStatus), nil } +// Stop stops the container in the sandbox. +func (s *Sandbox) Stop(cid string) error { + // TODO: This should stop the container with the given ID + // in the sandbox. + return nil +} + // Destroy frees all resources associated with the sandbox. func (s *Sandbox) Destroy() error { log.Debugf("Destroy sandbox %q", s.ID) @@ -625,60 +417,26 @@ func (s *Sandbox) Destroy() error { sendSignal(s.GoferPid, unix.SIGKILL) s.GoferPid = 0 } - if err := os.RemoveAll(s.SandboxRoot); err != nil { - log.Warningf("Failed to delete sandbox root directory %q, err: %v", s.SandboxRoot, err) - } - - // "If any poststop hook fails, the runtime MUST log a warning, but the - // remaining hooks and lifecycle continue as if the hook had succeeded". - if s.Spec.Hooks != nil && (s.Status == Created || s.Status == Running) { - executeHooksBestEffort(s.Spec.Hooks.Poststop, s.State()) - } - s.Status = Stopped return nil } -// Signal sends the signal to the sandbox. -func (s *Sandbox) Signal(sig syscall.Signal) error { +// Signal sends the signal to a container in the sandbox. +func (s *Sandbox) Signal(cid string, sig syscall.Signal) error { log.Debugf("Signal sandbox %q", s.ID) - if s.Status == Stopped { - log.Warningf("sandbox %q not running, not sending signal %v to pid %d", s.ID, sig, s.Pid) - return nil - } + + // TODO: This sends a signal to the sandbox process, which + // will be forwarded to the first process in the sandbox. We need a way + // to send a signal to any container in the sandbox. + // to wait on an individual container in the sandbox. + return sendSignal(s.Pid, sig) } +// sendSignal sends a signal to the sandbox process. func sendSignal(pid int, sig syscall.Signal) error { if err := syscall.Kill(pid, sig); err != nil { return fmt.Errorf("error sending signal %d to pid %d: %v", sig, pid, err) } return nil } - -// save saves the sandbox metadata to a file. -func (s *Sandbox) save() error { - log.Debugf("Save sandbox %q", s.ID) - if err := os.MkdirAll(s.SandboxRoot, 0711); err != nil { - return fmt.Errorf("error creating sandbox root directory %q: %v", s.SandboxRoot, err) - } - meta, err := json.Marshal(s) - if err != nil { - return fmt.Errorf("error marshaling sandbox metadata: %v", err) - } - metaFile := filepath.Join(s.SandboxRoot, metadataFilename) - if err := ioutil.WriteFile(metaFile, meta, 0640); err != nil { - return fmt.Errorf("error writing sandbox metadata: %v", err) - } - return nil -} - -// exists returns true if the given file exists. -func exists(f string) bool { - if _, err := os.Stat(f); err == nil { - return true - } else if !os.IsNotExist(err) { - log.Warningf("error checking for file %q: %v", f, err) - } - return false -} diff --git a/runsc/sandbox/sandbox_test.go b/runsc/sandbox/sandbox_test.go deleted file mode 100644 index 1fac38a29..000000000 --- a/runsc/sandbox/sandbox_test.go +++ /dev/null @@ -1,665 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sandbox_test - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "os/signal" - "path/filepath" - "reflect" - "strings" - "syscall" - "testing" - "time" - - "context" - "flag" - "github.com/google/subcommands" - specs "github.com/opencontainers/runtime-spec/specs-go" - "golang.org/x/sys/unix" - "gvisor.googlesource.com/gvisor/pkg/abi/linux" - "gvisor.googlesource.com/gvisor/pkg/log" - "gvisor.googlesource.com/gvisor/pkg/sentry/control" - "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth" - "gvisor.googlesource.com/gvisor/pkg/unet" - "gvisor.googlesource.com/gvisor/runsc/boot" - "gvisor.googlesource.com/gvisor/runsc/cmd" - "gvisor.googlesource.com/gvisor/runsc/sandbox" -) - -func init() { - log.SetLevel(log.Debug) -} - -// writeSpec writes the spec to disk in the given directory. -func writeSpec(dir string, spec *specs.Spec) error { - b, err := json.Marshal(spec) - if err != nil { - return err - } - return ioutil.WriteFile(filepath.Join(dir, "config.json"), b, 0755) -} - -// newSpecWithArgs creates a simple spec with the given args suitable for use -// in tests. -func newSpecWithArgs(args ...string) *specs.Spec { - spec := &specs.Spec{ - // The host filesystem root is the sandbox root. - Root: &specs.Root{ - Path: "/", - Readonly: true, - }, - Process: &specs.Process{ - Args: args, - Env: []string{ - "PATH=" + os.Getenv("PATH"), - }, - }, - } - return spec -} - -// shutdownSignal will be sent to the sandbox in order to shut down cleanly. -const shutdownSignal = syscall.SIGUSR2 - -// setupSandbox creates a bundle and root dir for the sandbox, generates a test -// config, and writes the spec to config.json in the bundle dir. -func setupSandbox(spec *specs.Spec) (rootDir, bundleDir string, conf *boot.Config, err error) { - rootDir, err = ioutil.TempDir("", "sandboxes") - if err != nil { - return "", "", nil, fmt.Errorf("error creating root dir: %v", err) - } - - bundleDir, err = ioutil.TempDir("", "bundle") - if err != nil { - return "", "", nil, fmt.Errorf("error creating bundle dir: %v", err) - } - - if err = writeSpec(bundleDir, spec); err != nil { - return "", "", nil, fmt.Errorf("error writing spec: %v", err) - } - - conf = &boot.Config{ - RootDir: rootDir, - Network: boot.NetworkNone, - } - - return rootDir, bundleDir, conf, nil -} - -// uniqueSandboxID generates a unique sandbox id for each test. -// -// The sandbox id is used to create an abstract unix domain socket, which must -// be unique. While the sandbox forbids creating two sandboxes with the same -// name, sometimes between test runs the socket does not get cleaned up quickly -// enough, causing sandbox creation to fail. -func uniqueSandboxID() string { - return fmt.Sprintf("test-sandbox-%d", time.Now().UnixNano()) -} - -// waitForProcessList waits for the given process list to show up in the sandbox. -func waitForProcessList(s *sandbox.Sandbox, expected []*control.Process) error { - var got []*control.Process - for start := time.Now(); time.Now().Sub(start) < 10*time.Second; { - var err error - got, err := s.Processes() - if err != nil { - return fmt.Errorf("error getting process data from sandbox: %v", err) - } - if procListsEqual(got, expected) { - return nil - } - // Process might not have started, try again... - time.Sleep(10 * time.Millisecond) - } - return fmt.Errorf("sandbox got process list: %s, want: %s", procListToString(got), procListToString(expected)) -} - -// TestLifecycle tests the basic Create/Start/Signal/Destroy sandbox lifecycle. -// It verifies after each step that the sandbox can be loaded from disk, and -// has the correct status. -func TestLifecycle(t *testing.T) { - // The sandbox will just sleep for a long time. We will kill it before - // it finishes sleeping. - spec := newSpecWithArgs("sleep", "100") - - rootDir, bundleDir, conf, err := setupSandbox(spec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // expectedPL lists the expected process state of the sandbox. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - } - // Create the sandbox. - id := uniqueSandboxID() - if _, err := sandbox.Create(id, spec, conf, bundleDir, "", "", nil); err != nil { - t.Fatalf("error creating sandbox: %v", err) - } - // Load the sandbox from disk and check the status. - s, err := sandbox.Load(rootDir, id) - if err != nil { - t.Fatalf("error loading sandbox: %v", err) - } - if got, want := s.Status, sandbox.Created; got != want { - t.Errorf("sandbox status got %v, want %v", got, want) - } - - // List should return the sandbox id. - ids, err := sandbox.List(rootDir) - if err != nil { - t.Fatalf("error listing sandboxes: %v", err) - } - if got, want := ids, []string{id}; !reflect.DeepEqual(got, want) { - t.Errorf("sandbox list got %v, want %v", got, want) - } - - // Start the sandbox. - if err := s.Start(conf); err != nil { - t.Fatalf("error starting sandbox: %v", err) - } - // Load the sandbox from disk and check the status. - s, err = sandbox.Load(rootDir, id) - if err != nil { - t.Fatalf("error loading sandbox: %v", err) - } - if got, want := s.Status, sandbox.Running; got != want { - t.Errorf("sandbox status got %v, want %v", got, want) - } - - // Verify that "sleep 100" is running. - if err := waitForProcessList(s, expectedPL); err != nil { - t.Error(err) - } - - // Send the sandbox a signal, which we catch and use to cleanly - // shutdown. - if err := s.Signal(shutdownSignal); err != nil { - t.Fatalf("error sending signal %v to sandbox: %v", shutdownSignal, err) - } - // Wait for it to die. - if _, err := s.Wait(); err != nil { - t.Fatalf("error waiting on sandbox: %v", err) - } - // Load the sandbox from disk and check the status. - s, err = sandbox.Load(rootDir, id) - if err != nil { - t.Fatalf("error loading sandbox: %v", err) - } - if got, want := s.Status, sandbox.Stopped; got != want { - t.Errorf("sandbox status got %v, want %v", got, want) - } - - // Destroy the sandbox. - if err := s.Destroy(); err != nil { - t.Fatalf("error destroying sandbox: %v", err) - } - - // List should not return the sandbox id. - ids, err = sandbox.List(rootDir) - if err != nil { - t.Fatalf("error listing sandboxes: %v", err) - } - if len(ids) != 0 { - t.Errorf("expected sandbox list to be empty, but got %v", ids) - } - - // Loading the sandbox by id should fail. - if _, err = sandbox.Load(rootDir, id); err == nil { - t.Errorf("expected loading destroyed sandbox to fail, but it did not") - } -} - -// Test the we can execute the application with different path formats. -func TestExePath(t *testing.T) { - for _, test := range []struct { - path string - success bool - }{ - {path: "true", success: true}, - {path: "bin/true", success: true}, - {path: "/bin/true", success: true}, - {path: "thisfiledoesntexit", success: false}, - {path: "bin/thisfiledoesntexit", success: false}, - {path: "/bin/thisfiledoesntexit", success: false}, - } { - spec := newSpecWithArgs(test.path) - rootDir, bundleDir, conf, err := setupSandbox(spec) - if err != nil { - t.Fatalf("exec: %s, error setting up sandbox: %v", test.path, err) - } - - ws, err := sandbox.Run(uniqueSandboxID(), spec, conf, bundleDir, "", "", nil) - - os.RemoveAll(rootDir) - os.RemoveAll(bundleDir) - - if test.success { - if err != nil { - t.Errorf("exec: %s, error running sandbox: %v", test.path, err) - } - if ws.ExitStatus() != 0 { - t.Errorf("exec: %s, got exit status %v want %v", test.path, ws.ExitStatus(), 0) - } - } else { - if err == nil { - t.Errorf("exec: %s, got: no error, want: error", test.path) - } - } - } -} - -// Test the we can retrieve the application exit status from the sandbox. -func TestAppExitStatus(t *testing.T) { - // First sandbox will succeed. - succSpec := newSpecWithArgs("true") - - rootDir, bundleDir, conf, err := setupSandbox(succSpec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - ws, err := sandbox.Run(uniqueSandboxID(), succSpec, conf, bundleDir, "", "", nil) - if err != nil { - t.Fatalf("error running sandbox: %v", err) - } - if ws.ExitStatus() != 0 { - t.Errorf("got exit status %v want %v", ws.ExitStatus(), 0) - } - - // Second sandbox exits with non-zero status. - wantStatus := 123 - errSpec := newSpecWithArgs("bash", "-c", fmt.Sprintf("exit %d", wantStatus)) - - rootDir2, bundleDir2, conf, err := setupSandbox(errSpec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir2) - defer os.RemoveAll(bundleDir2) - - ws, err = sandbox.Run(uniqueSandboxID(), succSpec, conf, bundleDir2, "", "", nil) - if err != nil { - t.Fatalf("error running sandbox: %v", err) - } - if ws.ExitStatus() != wantStatus { - t.Errorf("got exit status %v want %v", ws.ExitStatus(), wantStatus) - } -} - -// TestExec verifies that a sandbox can exec a new program. -func TestExec(t *testing.T) { - const uid = 343 - spec := newSpecWithArgs("sleep", "100") - - rootDir, bundleDir, conf, err := setupSandbox(spec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the sandbox. - s, err := sandbox.Create(uniqueSandboxID(), spec, conf, bundleDir, "", "", nil) - if err != nil { - t.Fatalf("error creating sandbox: %v", err) - } - defer s.Destroy() - if err := s.Start(conf); err != nil { - t.Fatalf("error starting sandbox: %v", err) - } - - // expectedPL lists the expected process state of the sandbox. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - { - UID: uid, - PID: 2, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - } - - // Verify that "sleep 100" is running. - if err := waitForProcessList(s, expectedPL[:1]); err != nil { - t.Error(err) - } - - execArgs := control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"sleep", "5"}, - Envv: []string{"PATH=" + os.Getenv("PATH")}, - WorkingDirectory: "/", - KUID: uid, - } - - // Verify that "sleep 100" and "sleep 5" are running after exec. - // First, start running exec (whick blocks). - status := make(chan error, 1) - go func() { - exitStatus, err := s.Execute(&execArgs) - if err != nil { - status <- err - } else if exitStatus != 0 { - status <- fmt.Errorf("failed with exit status: %v", exitStatus) - } else { - status <- nil - } - }() - - if err := waitForProcessList(s, expectedPL); err != nil { - t.Fatal(err) - } - - // Ensure that exec finished without error. - select { - case <-time.After(10 * time.Second): - t.Fatalf("sandbox timed out waiting for exec to finish.") - case st := <-status: - if st != nil { - t.Errorf("sandbox failed to exec %v: %v", execArgs, err) - } - } -} - -// TestCapabilities verifies that: -// - Running exec as non-root UID and GID will result in an error (because the -// executable file can't be read). -// - Running exec as non-root with CAP_DAC_OVERRIDE succeeds because it skips -// this check. -func TestCapabilities(t *testing.T) { - const uid = 343 - const gid = 2401 - spec := newSpecWithArgs("sleep", "100") - - // We generate files in the host temporary directory. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: os.TempDir(), - Source: os.TempDir(), - Type: "bind", - }) - - rootDir, bundleDir, conf, err := setupSandbox(spec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the sandbox. - s, err := sandbox.Create(uniqueSandboxID(), spec, conf, bundleDir, "", "", nil) - if err != nil { - t.Fatalf("error creating sandbox: %v", err) - } - defer s.Destroy() - if err := s.Start(conf); err != nil { - t.Fatalf("error starting sandbox: %v", err) - } - - // expectedPL lists the expected process state of the sandbox. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - { - UID: uid, - PID: 2, - PPID: 0, - C: 0, - Cmd: "exe", - }, - } - if err := waitForProcessList(s, expectedPL[:1]); err != nil { - t.Fatalf("Failed to wait for sleep to start, err: %v", err) - } - - // Create an executable that can't be run with the specified UID:GID. - // This shouldn't be callable within the sandbox until we add the - // CAP_DAC_OVERRIDE capability to skip the access check. - exePath := filepath.Join(rootDir, "exe") - if err := ioutil.WriteFile(exePath, []byte("#!/bin/sh\necho hello"), 0770); err != nil { - t.Fatalf("couldn't create executable: %v", err) - } - defer os.Remove(exePath) - - // Need to traverse the intermediate directory. - os.Chmod(rootDir, 0755) - - execArgs := control.ExecArgs{ - Filename: exePath, - Argv: []string{exePath}, - Envv: []string{"PATH=" + os.Getenv("PATH")}, - WorkingDirectory: "/", - KUID: uid, - KGID: gid, - Capabilities: &auth.TaskCapabilities{}, - } - - // "exe" should fail because we don't have the necessary permissions. - if _, err := s.Execute(&execArgs); err == nil { - t.Fatalf("sandbox executed without error, but an error was expected") - } - - // Now we run with the capability enabled and should succeed. - execArgs.Capabilities = &auth.TaskCapabilities{ - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - } - // "exe" should not fail this time. - if _, err := s.Execute(&execArgs); err != nil { - t.Fatalf("sandbox failed to exec %v: %v", execArgs, err) - } -} - -// Test that an tty FD is sent over the console socket if one is provided. -func TestConsoleSocket(t *testing.T) { - spec := newSpecWithArgs("true") - rootDir, bundleDir, conf, err := setupSandbox(spec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create a named socket and start listening. We use a relative path - // to avoid overflowing the unix path length limit (108 chars). - socketPath := filepath.Join(bundleDir, "socket") - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("error getting cwd: %v", err) - } - socketRelPath, err := filepath.Rel(cwd, socketPath) - if err != nil { - t.Fatalf("error getting relative path for %q from cwd %q: %v", socketPath, cwd, err) - } - if len(socketRelPath) > len(socketPath) { - socketRelPath = socketPath - } - srv, err := unet.BindAndListen(socketRelPath, false) - if err != nil { - t.Fatalf("error binding and listening to socket %q: %v", socketPath, err) - } - defer os.Remove(socketPath) - - // Create the sandbox and pass the socket name. - id := uniqueSandboxID() - s, err := sandbox.Create(id, spec, conf, bundleDir, socketRelPath, "", nil) - if err != nil { - t.Fatalf("error creating sandbox: %v", err) - } - - // Open the othe end of the socket. - sock, err := srv.Accept() - if err != nil { - t.Fatalf("error accepting socket connection: %v", err) - } - - // Allow 3 fds to be received. We only expect 1. - r := sock.Reader(true /* blocking */) - r.EnableFDs(1) - - // The socket is closed right after sending the FD, so EOF is - // an allowed error. - b := [][]byte{{}} - if _, err := r.ReadVec(b); err != nil && err != io.EOF { - t.Fatalf("error reading from socket connection: %v", err) - } - - // We should have gotten a control message. - fds, err := r.ExtractFDs() - if err != nil { - t.Fatalf("error extracting fds from socket connection: %v", err) - } - if len(fds) != 1 { - t.Fatalf("got %d fds from socket, wanted 1", len(fds)) - } - - // Verify that the fd is a terminal. - if _, err := unix.IoctlGetTermios(fds[0], unix.TCGETS); err != nil { - t.Errorf("fd is not a terminal (ioctl TGGETS got %v)", err) - } - - // Shut it down. - if err := s.Destroy(); err != nil { - t.Fatalf("error destroying sandbox: %v", err) - } - - // Close socket. - if err := srv.Close(); err != nil { - t.Fatalf("error destroying sandbox: %v", err) - } -} - -func TestSpecUnsupported(t *testing.T) { - spec := newSpecWithArgs("/bin/true") - spec.Process.SelinuxLabel = "somelabel" - - // These are normally set by docker and will just cause warnings to be logged. - spec.Process.ApparmorProfile = "someprofile" - spec.Linux = &specs.Linux{Seccomp: &specs.LinuxSeccomp{}} - - rootDir, bundleDir, conf, err := setupSandbox(spec) - if err != nil { - t.Fatalf("error setting up sandbox: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - id := uniqueSandboxID() - _, err = sandbox.Create(id, spec, conf, bundleDir, "", "", nil) - if err == nil || !strings.Contains(err.Error(), "is not supported") { - t.Errorf("sandbox.Create() wrong error, got: %v, want: *is not supported, spec.Process: %+v", err, spec.Process) - } -} - -// procListsEqual is used to check whether 2 Process lists are equal for all -// implemented fields. -func procListsEqual(got, want []*control.Process) bool { - if len(got) != len(want) { - return false - } - for i := range got { - pd1 := got[i] - pd2 := want[i] - // Zero out unimplemented and timing dependant fields. - pd1.Time, pd2.Time = "", "" - pd1.STime, pd2.STime = "", "" - pd1.C, pd2.C = 0, 0 - if *pd1 != *pd2 { - return false - } - } - return true -} - -func procListToString(pl []*control.Process) string { - strs := make([]string, 0, len(pl)) - for _, p := range pl { - strs = append(strs, fmt.Sprintf("%+v", p)) - } - return fmt.Sprintf("[%s]", strings.Join(strs, ",")) -} - -// TestMain acts like runsc if it is called with the "boot" argument, otherwise -// it just runs the tests. This is required because creating a sandbox will -// call "/proc/self/exe boot". Normally /proc/self/exe is the runsc binary, -// but for tests we have to fake it. -func TestMain(m *testing.M) { - // exit writes coverage data before exiting. - exit := func(status int) { - os.Exit(status) - } - - if !flag.Parsed() { - flag.Parse() - } - - // If we are passed one of the commands then run it. - subcommands.Register(new(cmd.Boot), "boot") - subcommands.Register(new(cmd.Gofer), "gofer") - switch flag.Arg(0) { - case "boot", "gofer": - // Run the command in a goroutine so we can block the main - // thread waiting for shutdownSignal. - go func() { - conf := &boot.Config{ - RootDir: "unused-root-dir", - Network: boot.NetworkNone, - } - var ws syscall.WaitStatus - subcmdCode := subcommands.Execute(context.Background(), conf, &ws) - if subcmdCode != subcommands.ExitSuccess { - panic(fmt.Sprintf("command failed to execute, err: %v", subcmdCode)) - } - // Sandbox exited normally. Shut down this process. - os.Exit(ws.ExitStatus()) - }() - - // Shutdown cleanly when the shutdownSignal is received. This - // allows us to write coverage data before exiting. - sigc := make(chan os.Signal, 1) - signal.Notify(sigc, shutdownSignal) - <-sigc - exit(0) - default: - // Otherwise run the tests. - exit(m.Run()) - } -} diff --git a/runsc/sandbox/status.go b/runsc/sandbox/status.go deleted file mode 100644 index 6fc936aba..000000000 --- a/runsc/sandbox/status.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sandbox - -// Status enumerates sandbox statuses. The statuses and their semantics are -// part of the runtime CLI spec. -// -// TODO: Get precise about the transitions between statuses. -type Status int - -const ( - // Creating indicates "the container is being created". - Creating Status = iota - - // Created indicates "the runtime has finished the create operation and - // the container process has neither exited nor executed the - // user-specified program". - Created - - // Running indicates "the container process has executed the - // user-specified program but has not exited". - Running - - // Stopped indicates "the container process has exited". - Stopped -) - -// String converts a Status to a string. These strings are part of the runtime -// CLI spec and should not be changed. -func (s Status) String() string { - switch s { - case Creating: - return "creating" - case Created: - return "created" - case Running: - return "running" - case Stopped: - return "stopped" - default: - return "unknown" - } - -} |