From 3fe7d7f31e7aec30f6e520991915f0be015479a3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 13 May 2016 16:54:16 -0700 Subject: [PATCH] Add create and start command for container lifecycle Signed-off-by: Michael Crosby --- create.go | 72 ++++++++++++++++++++++++++ libcontainer/container.go | 11 ++++ libcontainer/container_linux.go | 33 ++++++++---- libcontainer/factory_linux.go | 6 ++- libcontainer/init_linux.go | 2 +- libcontainer/integration/utils_test.go | 3 ++ libcontainer/setns_init_linux.go | 5 +- libcontainer/standard_init_linux.go | 22 ++++++-- libcontainer/state_linux.go | 34 +++++++++--- main.go | 2 + run.go | 8 ++- start.go | 27 ++++++++++ utils_linux.go | 13 ++++- 13 files changed, 209 insertions(+), 29 deletions(-) create mode 100644 create.go create mode 100644 start.go diff --git a/create.go b/create.go new file mode 100644 index 00000000000..1b1c79c83a6 --- /dev/null +++ b/create.go @@ -0,0 +1,72 @@ +package main + +import ( + "os" + + "github.com/codegangsta/cli" +) + +var createCommand = cli.Command{ + Name: "create", + Usage: "create a container", + ArgsUsage: ` + +Where "" is your name for the instance of the container that you +are starting. The name you provide for the container instance must be unique on +your host.`, + Description: `The create command creates an instance of a container for a bundle. The bundle +is a directory with a specification file named "` + specConfig + `" and a root +filesystem. + +The specification file includes an args parameter. The args parameter is used +to specify command(s) that get run when the container is started. To change the +command(s) that get executed on start, edit the args parameter of the spec. See +"runc spec --help" for more explanation.`, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "bundle, b", + Value: "", + Usage: `path to the root of the bundle directory, defaults to the current directory`, + }, + cli.StringFlag{ + Name: "console", + Value: "", + Usage: "specify the pty slave path for use with the container", + }, + cli.StringFlag{ + Name: "pid-file", + Value: "", + Usage: "specify the file to write the process id to", + }, + cli.BoolFlag{ + Name: "no-pivot", + Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk", + }, + }, + Action: func(context *cli.Context) { + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + fatal(err) + } + } + spec, err := loadSpec(specConfig) + if err != nil { + fatal(err) + } + notifySocket := os.Getenv("NOTIFY_SOCKET") + if notifySocket != "" { + setupSdNotify(spec, notifySocket) + } + if os.Geteuid() != 0 { + fatalf("runc should be run as root") + } + status, err := startContainer(context, spec, true) + if err != nil { + fatal(err) + } + // exit with the container's exit status so any external supervisor is + // notified of the exit with the correct exit status. + os.Exit(status) + }, +} diff --git a/libcontainer/container.go b/libcontainer/container.go index 8d282a1e424..c394c40e24c 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -29,6 +29,13 @@ const ( // Destroyed is the status that denotes the container does not exist. Destroyed + + // Stopped is the status that denotes the container does not have a created or running process. + Stopped + + // Initialized is the status where the container has all the namespaces created but the user + // process has not been start. + Initialized ) func (s Status) String() string { @@ -43,6 +50,10 @@ func (s Status) String() string { return "paused" case Destroyed: return "destroyed" + case Stopped: + return "stopped" + case Initialized: + return "initialized" default: return "unknown" } diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 082e6c4abe2..16f1385cbd5 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -195,7 +195,6 @@ func (c *linuxContainer) Start(process *Process) error { } // generate a timestamp indicating when the container was started c.created = time.Now().UTC() - c.state = &runningState{ c: c, } @@ -1034,31 +1033,47 @@ func (c *linuxContainer) refreshState() error { if paused { return c.state.transition(&pausedState{c: c}) } - running, err := c.isRunning() + t, err := c.runType() if err != nil { return err } - if running { + switch t { + case Initialized: + return c.state.transition(&initializedState{c: c}) + case Running: return c.state.transition(&runningState{c: c}) } return c.state.transition(&stoppedState{c: c}) } -func (c *linuxContainer) isRunning() (bool, error) { +func (c *linuxContainer) runType() (Status, error) { if c.initProcess == nil { - return false, nil + return Stopped, nil } + pid := c.initProcess.pid() // return Running if the init process is alive - if err := syscall.Kill(c.initProcess.pid(), 0); err != nil { + if err := syscall.Kill(pid, 0); err != nil { if err == syscall.ESRCH { // It means the process does not exist anymore, could happen when the // process exited just when we call the function, we should not return // error in this case. - return false, nil + return Stopped, nil + } + return Stopped, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", pid) + } + // check if the process that is running is the init process or the user's process. + // this is the difference between the container Running and Created. + environ, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/environ", pid)) + if err != nil { + return Stopped, newSystemErrorWithCausef(err, "reading /proc/%d/environ", pid) + } + check := []byte("_LIBCONTAINER") + for _, v := range bytes.Split(environ, []byte("\x00")) { + if bytes.Contains(v, check) { + return Initialized, nil } - return false, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", c.initProcess.pid()) } - return true, nil + return Running, nil } func (c *linuxContainer) isPaused() (bool, error) { diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index d58c5a55954..95a784d051e 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/exec" + "os/signal" "path/filepath" "regexp" "runtime/debug" @@ -219,6 +220,9 @@ func (l *LinuxFactory) Type() string { // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // This is a low level implementation detail of the reexec and should not be consumed externally func (l *LinuxFactory) StartInitialization() (err error) { + // start the signal handler as soon as we can + s := make(chan os.Signal, 1024) + signal.Notify(s, syscall.SIGCONT) fdStr := os.Getenv("_LIBCONTAINER_INITPIPE") pipefd, err := strconv.Atoi(fdStr) if err != nil { @@ -260,7 +264,7 @@ func (l *LinuxFactory) StartInitialization() (err error) { if err != nil { return err } - return i.Init() + return i.Init(s) } func (l *LinuxFactory) loadState(root string) (*State, error) { diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index 0bde656e292..8e345f0c4f9 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -61,7 +61,7 @@ type initConfig struct { } type initer interface { - Init() error + Init(s chan os.Signal) error } func newContainerInit(t initType, pipe *os.File) (initer, error) { diff --git a/libcontainer/integration/utils_test.go b/libcontainer/integration/utils_test.go index e2ca10e0082..e77587f6dd0 100644 --- a/libcontainer/integration/utils_test.go +++ b/libcontainer/integration/utils_test.go @@ -127,6 +127,9 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe if err != nil { return buffers, -1, err } + if err := container.Signal(syscall.SIGCONT); err != nil { + return buffers, -1, err + } ps, err := process.Wait() if err != nil { return buffers, -1, err diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go index b1a198fd13b..388cd81f125 100644 --- a/libcontainer/setns_init_linux.go +++ b/libcontainer/setns_init_linux.go @@ -5,6 +5,7 @@ package libcontainer import ( "fmt" "os" + "os/signal" "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/keys" @@ -23,7 +24,7 @@ func (l *linuxSetnsInit) getSessionRingName() string { return fmt.Sprintf("_ses.%s", l.config.ContainerId) } -func (l *linuxSetnsInit) Init() error { +func (l *linuxSetnsInit) Init(s chan os.Signal) error { // do not inherit the parent's session keyring if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil { return err @@ -49,5 +50,7 @@ func (l *linuxSetnsInit) Init() error { return err } } + signal.Stop(s) + close(s) return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) } diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 5809b4de14a..a319ea8441a 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -6,6 +6,8 @@ import ( "fmt" "io" "os" + "os/exec" + "os/signal" "syscall" "github.com/opencontainers/runc/libcontainer/apparmor" @@ -17,7 +19,7 @@ import ( ) type linuxStandardInit struct { - pipe io.ReadWriter + pipe io.ReadWriteCloser parentPid int config *initConfig } @@ -42,7 +44,7 @@ func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) { // the kernel const PR_SET_NO_NEW_PRIVS = 0x26 -func (l *linuxStandardInit) Init() error { +func (l *linuxStandardInit) Init(s chan os.Signal) error { ringname, keepperms, newperms := l.getSessionRingParams() // do not inherit the parent's session keyring @@ -150,6 +152,18 @@ func (l *linuxStandardInit) Init() error { return err } } - - return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) + // check for the arg before waiting to make sure it exists and it is returned + // as a create time error + name, err := exec.LookPath(l.config.Args[0]) + if err != nil { + return err + } + // close the pipe to signal that we have completed our init + l.pipe.Close() + // wait for the signal to exec the users process + <-s + // clean up the signal handler + signal.Stop(s) + close(s) + return syscall.Exec(name, l.config.Args[0:], os.Environ()) } diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go index d2618f69b25..cd045b8ae80 100644 --- a/libcontainer/state_linux.go +++ b/libcontainer/state_linux.go @@ -110,11 +110,11 @@ func (r *runningState) status() Status { func (r *runningState) transition(s containerState) error { switch s.(type) { case *stoppedState: - running, err := r.c.isRunning() + t, err := r.c.runType() if err != nil { return err } - if running { + if t == Running { return newGenericError(fmt.Errorf("container still running"), ContainerNotStopped) } r.c.state = s @@ -129,16 +129,38 @@ func (r *runningState) transition(s containerState) error { } func (r *runningState) destroy() error { - running, err := r.c.isRunning() + t, err := r.c.runType() if err != nil { return err } - if running { + if t == Running { return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped) } return destroy(r.c) } +type initializedState struct { + c *linuxContainer +} + +func (i *initializedState) status() Status { + return Initialized +} + +func (i *initializedState) transition(s containerState) error { + switch s.(type) { + case *runningState: + i.c.state = s + case *initializedState: + return nil + } + return newStateTransitionError(i, s) +} + +func (i *initializedState) destroy() error { + return destroy(i.c) +} + // pausedState represents a container that is currently pause. It cannot be destroyed in a // paused state and must transition back to running first. type pausedState struct { @@ -161,11 +183,11 @@ func (p *pausedState) transition(s containerState) error { } func (p *pausedState) destroy() error { - isRunning, err := p.c.isRunning() + t, err := p.c.runType() if err != nil { return err } - if !isRunning { + if t != Running && t != Created { if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil { return err } diff --git a/main.go b/main.go index 79561957ad8..11d3f624fdc 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,7 @@ func main() { } app.Commands = []cli.Command{ checkpointCommand, + createCommand, deleteCommand, eventsCommand, execCommand, @@ -98,6 +99,7 @@ func main() { resumeCommand, runCommand, specCommand, + startCommand, stateCommand, updateCommand, } diff --git a/run.go b/run.go index d999f49c93f..81832b2dcc9 100644 --- a/run.go +++ b/run.go @@ -68,17 +68,14 @@ command(s) that get executed on start, edit the args parameter of the spec. See if err != nil { return err } - notifySocket := os.Getenv("NOTIFY_SOCKET") if notifySocket != "" { setupSdNotify(spec, notifySocket) } - if os.Geteuid() != 0 { return fmt.Errorf("runc should be run as root") } - - status, err := startContainer(context, spec) + status, err := startContainer(context, spec, false) if err == nil { // exit with the container's exit status so any external supervisor is // notified of the exit with the correct exit status. @@ -88,7 +85,7 @@ command(s) that get executed on start, edit the args parameter of the spec. See }, } -func startContainer(context *cli.Context, spec *specs.Spec) (int, error) { +func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, error) { id := context.Args().First() if id == "" { return -1, errEmptyID @@ -111,6 +108,7 @@ func startContainer(context *cli.Context, spec *specs.Spec) (int, error) { console: context.String("console"), detach: detach, pidFile: context.String("pid-file"), + create: create, } return r.run(&spec.Process) } diff --git a/start.go b/start.go new file mode 100644 index 00000000000..eb794371c4d --- /dev/null +++ b/start.go @@ -0,0 +1,27 @@ +package main + +import ( + "syscall" + + "github.com/codegangsta/cli" +) + +var startCommand = cli.Command{ + Name: "start", + Usage: "start signals a created container to execute the users defined process", + ArgsUsage: ` + +Where "" is your name for the instance of the container that you +are starting. The name you provide for the container instance must be unique on +your host.`, + Description: `The start command signals the container to start the user's defined process.`, + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + if err := container.Signal(syscall.SIGCONT); err != nil { + fatal(err) + } + }, +} diff --git a/utils_linux.go b/utils_linux.go index c71df0d5483..810d5d2627a 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -198,6 +198,7 @@ type runner struct { pidFile string console string container libcontainer.Container + create bool } func (r *runner) run(config *specs.Process) (int, error) { @@ -220,7 +221,7 @@ func (r *runner) run(config *specs.Process) (int, error) { r.destroy() return -1, err } - tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach) + tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach || r.create) if err != nil { r.destroy() return -1, err @@ -245,7 +246,15 @@ func (r *runner) run(config *specs.Process) (int, error) { return -1, err } } - if r.detach { + if !r.create { + if err := process.Signal(syscall.SIGCONT); err != nil { + r.terminate(process) + r.destroy() + tty.Close() + return -1, err + } + } + if r.detach || r.create { tty.Close() return 0, nil }