diff --git a/Makefile b/Makefile index 6ff7fc3a1..0852c71a6 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ unittest: runctestimage docker run -e TESTFLAGS -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_TEST_IMAGE) make localunittest localunittest: all - go test -tags "$(BUILDTAGS)" ${TESTFLAGS} -v ./... + go test -timeout 3m -tags "$(BUILDTAGS)" ${TESTFLAGS} -v ./... integration: runctestimage docker run -e TESTFLAGS -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_TEST_IMAGE) make localintegration diff --git a/create.go b/create.go new file mode 100644 index 000000000..6b754d8fd --- /dev/null +++ b/create.go @@ -0,0 +1,60 @@ +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) error { + spec, err := setupSpec(context) + if err != nil { + return err + } + status, err := startContainer(context, spec, true) + if err != nil { + return 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) + return nil + }, +} diff --git a/delete.go b/delete.go index 17d5c2fa7..81cb4cda6 100644 --- a/delete.go +++ b/delete.go @@ -3,8 +3,11 @@ package main import ( + "fmt" "os" "path/filepath" + "syscall" + "time" "github.com/codegangsta/cli" "github.com/opencontainers/runc/libcontainer" @@ -19,7 +22,7 @@ Where "" is the name for the instance of the container. EXAMPLE: For example, if the container id is "ubuntu01" and runc list currently shows the -status of "ubuntu01" as "destroyed" the following will delete resources held for +status of "ubuntu01" as "stopped" the following will delete resources held for "ubuntu01" removing "ubuntu01" from the runc list of containers: # runc delete ubuntu01`, @@ -36,7 +39,26 @@ status of "ubuntu01" as "destroyed" the following will delete resources held for } return nil } - destroy(container) + s, err := container.Status() + if err != nil { + return err + } + switch s { + case libcontainer.Stopped: + destroy(container) + case libcontainer.Created: + container.Signal(syscall.SIGKILL) + for i := 0; i < 100; i++ { + time.Sleep(100 * time.Millisecond) + if err := container.Signal(syscall.Signal(0)); err != nil { + destroy(container) + return nil + } + } + return fmt.Errorf("container init still running") + default: + return fmt.Errorf("cannot delete container that is not stopped: %s", s) + } return nil }, } diff --git a/events.go b/events.go index 9777e4826..2d7b48954 100644 --- a/events.go +++ b/events.go @@ -116,7 +116,8 @@ information is displayed once every 5 seconds.`, if err != nil { return err } - if status == libcontainer.Destroyed { + if status == libcontainer.Stopped { + fatalf("container with id %s is not running", container.ID()) return fmt.Errorf("container with id %s is not running", container.ID()) } var ( diff --git a/libcontainer/container.go b/libcontainer/container.go index 8d282a1e4..1dc283d8b 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -15,20 +15,16 @@ import ( type Status int const ( - // Created is the status that denotes the container exists but has not been run yet + // Created is the status that denotes the container exists but has not been run yet. Created Status = iota - // Running is the status that denotes the container exists and is running. Running - // Pausing is the status that denotes the container exists, it is in the process of being paused. Pausing - // Paused is the status that denotes the container exists, but all its processes are paused. Paused - - // 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 ) func (s Status) String() string { @@ -41,8 +37,8 @@ func (s Status) String() string { return "pausing" case Paused: return "paused" - case Destroyed: - return "destroyed" + case Stopped: + return "stopped" default: return "unknown" } @@ -80,13 +76,13 @@ type BaseContainer interface { // // errors: // ContainerDestroyed - Container no longer exists, - // Systemerror - System error. + // SystemError - System error. Status() (Status, error) // State returns the current container's state information. // // errors: - // Systemerror - System error. + // SystemError - System error. State() (*State, error) // Returns the current config of the container. @@ -96,7 +92,7 @@ type BaseContainer interface { // // errors: // ContainerDestroyed - Container no longer exists, - // Systemerror - System error. + // SystemError - System error. // // Some of the returned PIDs may no longer refer to processes in the Container, unless // the Container state is PAUSED in which case every PID in the slice is valid. @@ -106,7 +102,7 @@ type BaseContainer interface { // // errors: // ContainerDestroyed - Container no longer exists, - // Systemerror - System error. + // SystemError - System error. Stats() (*Stats, error) // Set resources of container as configured @@ -114,7 +110,7 @@ type BaseContainer interface { // We can use this to change resources when containers are running. // // errors: - // Systemerror - System error. + // SystemError - System error. Set(config configs.Config) error // Start a process inside the container. Returns error if process fails to @@ -124,21 +120,32 @@ type BaseContainer interface { // ContainerDestroyed - Container no longer exists, // ConfigInvalid - config is invalid, // ContainerPaused - Container is paused, - // Systemerror - System error. + // SystemError - System error. Start(process *Process) (err error) + // Run immediatly starts the process inside the conatiner. Returns error if process + // fails to start. It does not block waiting for a SIGCONT after start returns but + // sends the signal when the process has completed. + // + // errors: + // ContainerDestroyed - Container no longer exists, + // ConfigInvalid - config is invalid, + // ContainerPaused - Container is paused, + // SystemError - System error. + Run(process *Process) (err error) + // Destroys the container after killing all running processes. // // Any event registrations are removed before the container is destroyed. // No error is returned if the container is already destroyed. // // errors: - // Systemerror - System error. + // SystemError - System error. Destroy() error // Signal sends the provided signal code to the container's initial process. // // errors: - // Systemerror - System error. + // SystemError - System error. Signal(s os.Signal) error } diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 082e6c4ab..31a51c4f4 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -29,6 +29,10 @@ import ( const stdioFdCount = 3 +// InitContinueSignal is used to signal the container init process to +// start the users specified process after the container create has finished. +const InitContinueSignal = syscall.SIGCONT + type linuxContainer struct { id string root string @@ -181,8 +185,28 @@ func (c *linuxContainer) Start(process *Process) error { if err != nil { return err } - doInit := status == Destroyed - parent, err := c.newParentProcess(process, doInit) + return c.start(process, status == Stopped) +} + +func (c *linuxContainer) Run(process *Process) error { + c.m.Lock() + defer c.m.Unlock() + status, err := c.currentStatus() + if err != nil { + return err + } + isInit := status == Stopped + if err := c.start(process, isInit); err != nil { + return err + } + if isInit { + return process.ops.signal(InitContinueSignal) + } + return nil +} + +func (c *linuxContainer) start(process *Process, isInit bool) error { + parent, err := c.newParentProcess(process, isInit) if err != nil { return newSystemErrorWithCause(err, "creating new parent process") } @@ -195,11 +219,13 @@ 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, } - if doInit { + if isInit { + c.state = &createdState{ + c: c, + } if err := c.updateState(parent); err != nil { return err } @@ -371,15 +397,16 @@ func (c *linuxContainer) Pause() error { if err != nil { return err } - if status != Running { - return newGenericError(fmt.Errorf("container not running"), ContainerNotRunning) + switch status { + case Running, Created: + if err := c.cgroupManager.Freeze(configs.Frozen); err != nil { + return err + } + return c.state.transition(&pausedState{ + c: c, + }) } - if err := c.cgroupManager.Freeze(configs.Frozen); err != nil { - return err - } - return c.state.transition(&pausedState{ - c: c, - }) + return newGenericError(fmt.Errorf("container not running: %s", status), ContainerNotRunning) } func (c *linuxContainer) Resume() error { @@ -1034,31 +1061,45 @@ 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 Created: + return c.state.transition(&createdState{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 false, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", c.initProcess.pid()) + return Stopped, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", pid) } - return true, nil + // 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") + if bytes.Contains(environ, check) { + return Created, nil + } + return Running, nil } func (c *linuxContainer) isPaused() (bool, error) { diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index d58c5a559..1ac06fb0d 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" @@ -205,7 +206,7 @@ func (l *LinuxFactory) Load(id string) (Container, error) { root: containerRoot, created: state.Created, } - c.state = &createdState{c: c, s: Created} + c.state = &loadedState{c: c} if err := c.refreshState(); err != nil { return nil, err } @@ -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, 1) + signal.Notify(s, InitContinueSignal) 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 0bde656e2..8e345f0c4 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/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go index a71c172a3..7c5746b48 100644 --- a/libcontainer/integration/checkpoint_test.go +++ b/libcontainer/integration/checkpoint_test.go @@ -89,7 +89,7 @@ func TestCheckpoint(t *testing.T) { Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) stdinR.Close() defer stdinW.Close() if err != nil { diff --git a/libcontainer/integration/exec_test.go b/libcontainer/integration/exec_test.go index 45c64128a..6d2db4c72 100644 --- a/libcontainer/integration/exec_test.go +++ b/libcontainer/integration/exec_test.go @@ -241,7 +241,7 @@ func TestEnter(t *testing.T) { Stdin: stdinR, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -259,7 +259,7 @@ func TestEnter(t *testing.T) { pconfig2.Stdin = stdinR2 pconfig2.Stdout = &stdout2 - err = container.Start(&pconfig2) + err = container.Run(&pconfig2) stdinR2.Close() defer stdinW2.Close() ok(t, err) @@ -330,7 +330,7 @@ func TestProcessEnv(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -378,7 +378,7 @@ func TestProcessCaps(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -448,7 +448,7 @@ func TestAdditionalGroups(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -508,7 +508,7 @@ func testFreeze(t *testing.T, systemd bool) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(pconfig) + err = container.Run(pconfig) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -719,7 +719,7 @@ func TestContainerState(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(p) + err = container.Run(p) if err != nil { t.Fatal(err) } @@ -772,7 +772,7 @@ func TestPassExtraFiles(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&process) + err = container.Run(&process) if err != nil { t.Fatal(err) } @@ -853,7 +853,7 @@ func TestMountCmds(t *testing.T) { Args: []string{"sh", "-c", "env"}, Env: standardEnvironment, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) if err != nil { t.Fatal(err) } @@ -902,7 +902,7 @@ func TestSysctl(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -1042,7 +1042,7 @@ func TestOomScoreAdj(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -1114,7 +1114,7 @@ func TestHook(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -1231,7 +1231,7 @@ func TestRootfsPropagationSlaveMount(t *testing.T) { Stdin: stdinR, } - err = container.Start(pconfig) + err = container.Run(pconfig) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -1260,7 +1260,7 @@ func TestRootfsPropagationSlaveMount(t *testing.T) { Stdout: &stdout2, } - err = container.Start(pconfig2) + err = container.Run(pconfig2) stdinR2.Close() defer stdinW2.Close() ok(t, err) @@ -1348,7 +1348,7 @@ func TestRootfsPropagationSharedMount(t *testing.T) { Stdin: stdinR, } - err = container.Start(pconfig) + err = container.Run(pconfig) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -1380,7 +1380,7 @@ func TestRootfsPropagationSharedMount(t *testing.T) { Capabilities: processCaps, } - err = container.Start(pconfig2) + err = container.Run(pconfig2) stdinR2.Close() defer stdinW2.Close() ok(t, err) @@ -1452,7 +1452,7 @@ func TestInitJoinPID(t *testing.T) { Env: standardEnvironment, Stdin: stdinR1, } - err = container1.Start(init1) + err = container1.Run(init1) stdinR1.Close() defer stdinW1.Close() ok(t, err) @@ -1462,7 +1462,7 @@ func TestInitJoinPID(t *testing.T) { ok(t, err) pidns1 := state1.NamespacePaths[configs.NEWPID] - // Start a container inside the existing pidns but with different cgroups + // Run a container inside the existing pidns but with different cgroups config2 := newTemplateConfig(rootfs) config2.Namespaces.Add(configs.NEWPID, pidns1) config2.Cgroups.Path = "integration/test2" @@ -1478,7 +1478,7 @@ func TestInitJoinPID(t *testing.T) { Env: standardEnvironment, Stdin: stdinR2, } - err = container2.Start(init2) + err = container2.Run(init2) stdinR2.Close() defer stdinW2.Close() ok(t, err) @@ -1508,7 +1508,7 @@ func TestInitJoinPID(t *testing.T) { Env: standardEnvironment, Stdout: buffers.Stdout, } - err = container1.Start(ps) + err = container1.Run(ps) ok(t, err) waitProcess(ps, t) @@ -1557,7 +1557,7 @@ func TestInitJoinNetworkAndUser(t *testing.T) { Env: standardEnvironment, Stdin: stdinR1, } - err = container1.Start(init1) + err = container1.Run(init1) stdinR1.Close() defer stdinW1.Close() ok(t, err) @@ -1568,7 +1568,7 @@ func TestInitJoinNetworkAndUser(t *testing.T) { netns1 := state1.NamespacePaths[configs.NEWNET] userns1 := state1.NamespacePaths[configs.NEWUSER] - // Start a container inside the existing pidns but with different cgroups + // Run a container inside the existing pidns but with different cgroups rootfs2, err := newRootfs() ok(t, err) defer remove(rootfs2) @@ -1591,7 +1591,7 @@ func TestInitJoinNetworkAndUser(t *testing.T) { Env: standardEnvironment, Stdin: stdinR2, } - err = container2.Start(init2) + err = container2.Run(init2) stdinR2.Close() defer stdinW2.Close() ok(t, err) diff --git a/libcontainer/integration/execin_test.go b/libcontainer/integration/execin_test.go index cd101cc28..2568aaa26 100644 --- a/libcontainer/integration/execin_test.go +++ b/libcontainer/integration/execin_test.go @@ -36,7 +36,7 @@ func TestExecIn(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -51,7 +51,7 @@ func TestExecIn(t *testing.T) { Stderr: buffers.Stderr, } - err = container.Start(ps) + err = container.Run(ps) ok(t, err) waitProcess(ps, t) stdinW.Close() @@ -103,7 +103,7 @@ func testExecInRlimit(t *testing.T, userns bool) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -121,7 +121,7 @@ func testExecInRlimit(t *testing.T, userns bool) { {Type: syscall.RLIMIT_NOFILE, Hard: 1026, Soft: 1026}, }, } - err = container.Start(ps) + err = container.Run(ps) ok(t, err) waitProcess(ps, t) @@ -155,7 +155,7 @@ func TestExecInError(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer func() { stdinW.Close() @@ -173,7 +173,7 @@ func TestExecInError(t *testing.T) { Env: standardEnvironment, Stdout: &out, } - err = container.Start(unexistent) + err = container.Run(unexistent) if err == nil { t.Fatal("Should be an error") } @@ -207,7 +207,7 @@ func TestExecInTTY(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -225,7 +225,7 @@ func TestExecInTTY(t *testing.T) { close(copy) }() ok(t, err) - err = container.Start(ps) + err = container.Run(ps) ok(t, err) select { case <-time.After(5 * time.Second): @@ -264,7 +264,7 @@ func TestExecInEnvironment(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -283,7 +283,7 @@ func TestExecInEnvironment(t *testing.T) { Stdout: buffers.Stdout, Stderr: buffers.Stderr, } - err = container.Start(process2) + err = container.Run(process2) ok(t, err) waitProcess(process2, t) @@ -328,7 +328,7 @@ func TestExecinPassExtraFiles(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() if err != nil { @@ -346,7 +346,7 @@ func TestExecinPassExtraFiles(t *testing.T) { Stdin: nil, Stdout: &stdout, } - err = container.Start(inprocess) + err = container.Run(inprocess) if err != nil { t.Fatal(err) } @@ -401,7 +401,7 @@ func TestExecInOomScoreAdj(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -415,7 +415,7 @@ func TestExecInOomScoreAdj(t *testing.T) { Stdout: buffers.Stdout, Stderr: buffers.Stderr, } - err = container.Start(ps) + err = container.Run(ps) ok(t, err) waitProcess(ps, t) @@ -456,7 +456,7 @@ func TestExecInUserns(t *testing.T) { Env: standardEnvironment, Stdin: stdinR, } - err = container.Start(process) + err = container.Run(process) stdinR.Close() defer stdinW.Close() ok(t, err) @@ -476,7 +476,7 @@ func TestExecInUserns(t *testing.T) { Stdout: buffers.Stdout, Stderr: os.Stderr, } - err = container.Start(process2) + err = container.Run(process2) ok(t, err) waitProcess(process2, t) stdinW.Close() diff --git a/libcontainer/integration/seccomp_test.go b/libcontainer/integration/seccomp_test.go index 820773e64..95868d01c 100644 --- a/libcontainer/integration/seccomp_test.go +++ b/libcontainer/integration/seccomp_test.go @@ -50,7 +50,7 @@ func TestSeccompDenyGetcwd(t *testing.T) { Stderr: buffers.Stderr, } - err = container.Start(pwd) + err = container.Run(pwd) if err != nil { t.Fatal(err) } @@ -125,7 +125,7 @@ func TestSeccompPermitWriteConditional(t *testing.T) { Stderr: buffers.Stderr, } - err = container.Start(dmesg) + err = container.Run(dmesg) if err != nil { t.Fatal(err) } @@ -186,7 +186,7 @@ func TestSeccompDenyWriteConditional(t *testing.T) { Stderr: buffers.Stderr, } - err = container.Start(dmesg) + err = container.Run(dmesg) if err != nil { t.Fatal(err) } diff --git a/libcontainer/integration/template_test.go b/libcontainer/integration/template_test.go index f54a849ac..961656840 100644 --- a/libcontainer/integration/template_test.go +++ b/libcontainer/integration/template_test.go @@ -89,12 +89,15 @@ func newTemplateConfig(rootfs string) *configs.Config { Data: "mode=1777,size=65536k", Flags: defaultMountFlags, }, - { - Source: "mqueue", - Destination: "/dev/mqueue", - Device: "mqueue", - Flags: defaultMountFlags, - }, + /* + CI is broken on the debian based kernels with this + { + Source: "mqueue", + Destination: "/dev/mqueue", + Device: "mqueue", + Flags: defaultMountFlags, + }, + */ { Source: "sysfs", Destination: "/sys", diff --git a/libcontainer/integration/utils_test.go b/libcontainer/integration/utils_test.go index e2ca10e00..04532f662 100644 --- a/libcontainer/integration/utils_test.go +++ b/libcontainer/integration/utils_test.go @@ -123,7 +123,7 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe Stderr: buffers.Stderr, } - err = container.Start(process) + err = container.Run(process) if err != nil { return buffers, -1, err } diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go index 3a34b130a..fc0c4fd29 100644 --- a/libcontainer/process_linux.go +++ b/libcontainer/process_linux.go @@ -221,6 +221,7 @@ func (p *initProcess) execSetns() error { return err } p.cmd.Process = process + p.process.ops = p return nil } diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go index 825931a6f..f1aa330dd 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 @@ -47,5 +48,7 @@ func (l *linuxSetnsInit) Init() error { if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { 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 5809b4de1..a319ea844 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 d2618f69b..b98600745 100644 --- a/libcontainer/state_linux.go +++ b/libcontainer/state_linux.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "syscall" "github.com/Sirupsen/logrus" "github.com/opencontainers/runc/libcontainer/configs" @@ -77,7 +78,7 @@ type stoppedState struct { } func (b *stoppedState) status() Status { - return Destroyed + return Stopped } func (b *stoppedState) transition(s containerState) error { @@ -110,11 +111,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 +130,40 @@ 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 createdState struct { + c *linuxContainer +} + +func (i *createdState) status() Status { + return Created +} + +func (i *createdState) transition(s containerState) error { + switch s.(type) { + case *runningState, *pausedState, *stoppedState: + i.c.state = s + return nil + case *createdState: + return nil + } + return newStateTransitionError(i, s) +} + +func (i *createdState) destroy() error { + i.c.initProcess.signal(syscall.SIGKILL) + 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 +186,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 } @@ -204,23 +229,23 @@ func (r *restoredState) destroy() error { return destroy(r.c) } -// createdState is used whenever a container is restored, loaded, or setting additional +// loadedState is used whenever a container is restored, loaded, or setting additional // processes inside and it should not be destroyed when it is exiting. -type createdState struct { +type loadedState struct { c *linuxContainer s Status } -func (n *createdState) status() Status { +func (n *loadedState) status() Status { return n.s } -func (n *createdState) transition(s containerState) error { +func (n *loadedState) transition(s containerState) error { n.c.state = s return nil } -func (n *createdState) destroy() error { +func (n *loadedState) destroy() error { if err := n.c.refreshState(); err != nil { return err } diff --git a/libcontainer/state_linux_test.go b/libcontainer/state_linux_test.go index 417d9c22e..cadacbe07 100644 --- a/libcontainer/state_linux_test.go +++ b/libcontainer/state_linux_test.go @@ -6,10 +6,11 @@ import "testing" func TestStateStatus(t *testing.T) { states := map[containerState]Status{ - &stoppedState{}: Destroyed, + &stoppedState{}: Stopped, &runningState{}: Running, &restoredState{}: Running, &pausedState{}: Paused, + &createdState{}: Created, } for s, status := range states { if s.status() != status { diff --git a/main.go b/main.go index 73b98cd84..f7a0243f0 100644 --- a/main.go +++ b/main.go @@ -91,6 +91,7 @@ func main() { } app.Commands = []cli.Command{ checkpointCommand, + createCommand, deleteCommand, eventsCommand, execCommand, @@ -101,6 +102,7 @@ func main() { psCommand, restoreCommand, resumeCommand, + runCommand, specCommand, startCommand, stateCommand, diff --git a/main_unix.go b/main_unix.go index 7bbec9fa8..da14caba3 100644 --- a/main_unix.go +++ b/main_unix.go @@ -2,4 +2,32 @@ package main -import _ "github.com/opencontainers/runc/libcontainer/nsenter" +import ( + "os" + "runtime" + + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" + _ "github.com/opencontainers/runc/libcontainer/nsenter" +) + +func init() { + if len(os.Args) > 1 && os.Args[1] == "init" { + runtime.GOMAXPROCS(1) + runtime.LockOSThread() + } +} + +var initCommand = cli.Command{ + Name: "init", + Usage: `initialize the namespaces and launch the process (do not call it outside of runc)`, + Action: func(context *cli.Context) { + factory, _ := libcontainer.New("") + if err := factory.StartInitialization(); err != nil { + // as the error is sent back to the parent there is no need to log + // or write it to stderr because the parent process will handle this + os.Exit(1) + } + panic("libcontainer: container init failed to exec") + }, +} diff --git a/run.go b/run.go new file mode 100644 index 000000000..b69fcfa74 --- /dev/null +++ b/run.go @@ -0,0 +1,70 @@ +// +build linux + +package main + +import ( + "os" + + "github.com/codegangsta/cli" +) + +// default action is to start a container +var runCommand = cli.Command{ + Name: "run", + Usage: "create and run 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 run 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.BoolFlag{ + Name: "detach, d", + Usage: "detach from the container's process", + }, + cli.StringFlag{ + Name: "pid-file", + Value: "", + Usage: "specify the file to write the process id to", + }, + cli.BoolFlag{ + Name: "no-subreaper", + Usage: "disable the use of the subreaper used to reap reparented processes", + }, + 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) error { + spec, err := setupSpec(context) + if err != nil { + return err + } + 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. + os.Exit(status) + } + return err + }, +} diff --git a/script/test_Dockerfile b/script/test_Dockerfile index 6a173b980..2b1db320a 100644 --- a/script/test_Dockerfile +++ b/script/test_Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.5.3 +FROM golang:1.6.2 # libseccomp in jessie is not _quite_ new enough -- need backports version RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/backports.list diff --git a/start.go b/start.go index cf9392c2f..45b39147a 100644 --- a/start.go +++ b/start.go @@ -1,139 +1,39 @@ -// +build linux - package main import ( "fmt" - "os" - "runtime" "github.com/codegangsta/cli" - "github.com/coreos/go-systemd/activation" "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runtime-spec/specs-go" ) -// default action is to start a container var startCommand = cli.Command{ Name: "start", - Usage: "create and run a container", + Usage: "start signals a created container to execute the user 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 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.BoolFlag{ - Name: "detach,d", - Usage: "detach from the container's process", - }, - cli.StringFlag{ - Name: "pid-file", - Value: "", - Usage: "specify the file to write the process id to", - }, - cli.BoolFlag{ - Name: "no-subreaper", - Usage: "disable the use of the subreaper used to reap reparented processes", - }, - 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", - }, - }, + Description: `The start command signals the container to start the user's defined process.`, Action: func(context *cli.Context) error { - bundle := context.String("bundle") - if bundle != "" { - if err := os.Chdir(bundle); err != nil { - return err - } - } - spec, err := loadSpec(specConfig) + container, err := getContainer(context) if err != nil { return err } - - notifySocket := os.Getenv("NOTIFY_SOCKET") - if notifySocket != "" { - setupSdNotify(spec, notifySocket) + status, err := container.Status() + if err != nil { + return err } - - if os.Geteuid() != 0 { - return fmt.Errorf("runc should be run as root") + switch status { + case libcontainer.Created: + return container.Signal(libcontainer.InitContinueSignal) + case libcontainer.Stopped: + return fmt.Errorf("cannot start a container that has run and stopped") + case libcontainer.Running: + return fmt.Errorf("cannot start an already running container") + default: + return fmt.Errorf("cannot start a container in the %s state", status) } - - status, err := startContainer(context, spec) - if err == nil { - // exit with the container's exit status so any external supervisor is - // notified of the exit with the correct exit status. - os.Exit(status) - } - return err }, } - -func init() { - if len(os.Args) > 1 && os.Args[1] == "init" { - runtime.GOMAXPROCS(1) - runtime.LockOSThread() - } -} - -var initCommand = cli.Command{ - Name: "init", - Usage: `initialize the namespaces and launch the process (do not call it outside of runc)`, - Action: func(context *cli.Context) error { - factory, _ := libcontainer.New("") - if err := factory.StartInitialization(); err != nil { - // as the error is sent back to the parent there is no need to log - // or write it to stderr because the parent process will handle this - os.Exit(1) - } - panic("libcontainer: container init failed to exec") - }, -} - -func startContainer(context *cli.Context, spec *specs.Spec) (int, error) { - id := context.Args().First() - if id == "" { - return -1, errEmptyID - } - container, err := createContainer(context, id, spec) - if err != nil { - return -1, err - } - detach := context.Bool("detach") - // Support on-demand socket activation by passing file descriptors into the container init process. - listenFDs := []*os.File{} - if os.Getenv("LISTEN_FDS") != "" { - listenFDs = activation.Files(false) - } - r := &runner{ - enableSubreaper: !context.Bool("no-subreaper"), - shouldDestroy: true, - container: container, - listenFDs: listenFDs, - console: context.String("console"), - detach: detach, - pidFile: context.String("pid-file"), - } - return r.run(&spec.Process) -} diff --git a/tests/integration/cgroups.bats b/tests/integration/cgroups.bats index 0f98514fb..c415d5da6 100644 --- a/tests/integration/cgroups.bats +++ b/tests/integration/cgroups.bats @@ -47,8 +47,8 @@ EOF DATA=$(echo ${DATA} | sed 's/\n/\\n/g') sed -i "s/\(\"resources\": {\)/\1\n${DATA}/" ${BUSYBOX_BUNDLE}/config.json - # start a detached busybox to work with - runc start -d --console /dev/pts/ptmx test_cgroups_kmem + # run a detached busybox to work with + runc run -d --console /dev/pts/ptmx test_cgroups_kmem [ "$status" -eq 0 ] wait_for_container 15 1 test_cgroups_kmem @@ -64,8 +64,8 @@ EOF # Add cgroup path sed -i 's/\("linux": {\)/\1\n "cgroupsPath": "runc-cgroups-integration-test",/' ${BUSYBOX_BUNDLE}/config.json - # start a detached busybox to work with - runc start -d --console /dev/pts/ptmx test_cgroups_kmem + # run a detached busybox to work with + runc run -d --console /dev/pts/ptmx test_cgroups_kmem [ "$status" -eq 0 ] wait_for_container 15 1 test_cgroups_kmem diff --git a/tests/integration/checkpoint.bats b/tests/integration/checkpoint.bats index 97194d033..4a3693963 100644 --- a/tests/integration/checkpoint.bats +++ b/tests/integration/checkpoint.bats @@ -21,8 +21,8 @@ function teardown() { sed -i 's/"sh"/"sh","-c","while :; do date; sleep 1; done"/' config.json ( - # start busybox (not detached) - runc start test_busybox + # run busybox (not detached) + runc run test_busybox [ "$status" -eq 0 ] ) & diff --git a/tests/integration/create.bats b/tests/integration/create.bats new file mode 100644 index 000000000..0b6d91b7e --- /dev/null +++ b/tests/integration/create.bats @@ -0,0 +1,41 @@ +#!/usr/bin/env bats + +load helpers + +function setup() { + teardown_busybox + setup_busybox +} + +function teardown() { + teardown_busybox +} + +@test "runc create" { + runc create --console /dev/pts/ptmx test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox created + + # start the command + runc start test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox running +} + +@test "runc create exec" { + runc create --console /dev/pts/ptmx test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox created + + runc exec test_busybox true + [ "$status" -eq 0 ] + + # start the command + runc start test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox running +} diff --git a/tests/integration/debug.bats b/tests/integration/debug.bats index 5473b4b64..971bc7e1f 100644 --- a/tests/integration/debug.bats +++ b/tests/integration/debug.bats @@ -12,15 +12,15 @@ function teardown() { } @test "global --debug" { - # start hello-world - runc --debug start test_hello + # run hello-world + runc --debug run test_hello echo "${output}" [ "$status" -eq 0 ] } @test "global --debug to --log" { - # start hello-world - runc --log log.out --debug start test_hello + # run hello-world + runc --log log.out --debug run test_hello [ "$status" -eq 0 ] # check output does not include debug info @@ -36,8 +36,8 @@ function teardown() { } @test "global --debug to --log --log-format 'text'" { - # start hello-world - runc --log log.out --log-format "text" --debug start test_hello + # run hello-world + runc --log log.out --log-format "text" --debug run test_hello [ "$status" -eq 0 ] # check output does not include debug info @@ -53,8 +53,8 @@ function teardown() { } @test "global --debug to --log --log-format 'json'" { - # start hello-world - runc --log log.out --log-format "json" --debug start test_hello + # run hello-world + runc --log log.out --log-format "json" --debug run test_hello [ "$status" -eq 0 ] # check output does not include debug info diff --git a/tests/integration/delete.bats b/tests/integration/delete.bats index ec102b79c..89a60ebcb 100644 --- a/tests/integration/delete.bats +++ b/tests/integration/delete.bats @@ -12,8 +12,8 @@ function teardown() { } @test "runc delete" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -23,7 +23,7 @@ function teardown() { runc kill test_busybox KILL # wait for busybox to be in the destroyed state - retry 10 1 eval "__runc state test_busybox | grep -q 'destroyed'" + retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" # delete test_busybox runc delete test_busybox diff --git a/tests/integration/events.bats b/tests/integration/events.bats index 81d245c93..18855d5e7 100644 --- a/tests/integration/events.bats +++ b/tests/integration/events.bats @@ -12,8 +12,8 @@ function teardown() { } @test "events --stats" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -27,8 +27,8 @@ function teardown() { } @test "events --interval default " { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -54,8 +54,8 @@ function teardown() { } @test "events --interval 1s " { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -80,8 +80,8 @@ function teardown() { } @test "events --interval 100ms " { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state diff --git a/tests/integration/exec.bats b/tests/integration/exec.bats index a7fa9c549..4754da272 100644 --- a/tests/integration/exec.bats +++ b/tests/integration/exec.bats @@ -12,8 +12,8 @@ function teardown() { } @test "runc exec" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] wait_for_container 15 1 test_busybox @@ -25,8 +25,8 @@ function teardown() { } @test "runc exec --pid-file" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] wait_for_container 15 1 test_busybox diff --git a/tests/integration/help.bats b/tests/integration/help.bats index d7d873b35..61a62d8be 100644 --- a/tests/integration/help.bats +++ b/tests/integration/help.bats @@ -64,7 +64,11 @@ load helpers runc start -h [ "$status" -eq 0 ] [[ ${lines[1]} =~ runc\ start+ ]] - + + runc run -h + [ "$status" -eq 0 ] + [[ ${lines[1]} =~ runc\ run+ ]] + runc state -h [ "$status" -eq 0 ] [[ ${lines[1]} =~ runc\ state+ ]] diff --git a/tests/integration/helpers.bash b/tests/integration/helpers.bash index c2678890a..e61ad3011 100644 --- a/tests/integration/helpers.bash +++ b/tests/integration/helpers.bash @@ -153,7 +153,7 @@ function teardown_running_container() { runc list if [[ "${output}" == *"$1"* ]]; then runc kill $1 KILL - retry 10 1 eval "__runc state '$1' | grep -q 'destroyed'" + retry 10 1 eval "__runc state '$1' | grep -q 'stopped'" runc delete $1 fi } @@ -162,7 +162,7 @@ function teardown_running_container_inroot() { ROOT=$2 runc list if [[ "${output}" == *"$1"* ]]; then ROOT=$2 runc kill $1 KILL - retry 10 1 eval "ROOT='$2' __runc state '$1' | grep -q 'destroyed'" + retry 10 1 eval "ROOT='$2' __runc state '$1' | grep -q 'stopped'" ROOT=$2 runc delete $1 fi } diff --git a/tests/integration/kill.bats b/tests/integration/kill.bats index ccc035454..e0d89e947 100644 --- a/tests/integration/kill.bats +++ b/tests/integration/kill.bats @@ -14,8 +14,8 @@ function teardown() { @test "kill detached busybox" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -26,7 +26,7 @@ function teardown() { runc kill test_busybox KILL [ "$status" -eq 0 ] - retry 10 1 eval "__runc state test_busybox | grep -q 'destroyed'" + retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" runc delete test_busybox [ "$status" -eq 0 ] diff --git a/tests/integration/list.bats b/tests/integration/list.bats index 4caff04b1..c63a9aa45 100644 --- a/tests/integration/list.bats +++ b/tests/integration/list.bats @@ -18,16 +18,16 @@ function teardown() { } @test "list" { - # start a few busyboxes detached - ROOT=$HELLO_BUNDLE runc start -d --console /dev/pts/ptmx test_box1 + # run a few busyboxes detached + ROOT=$HELLO_BUNDLE runc run -d --console /dev/pts/ptmx test_box1 [ "$status" -eq 0 ] wait_for_container_inroot 15 1 test_box1 $HELLO_BUNDLE - ROOT=$HELLO_BUNDLE runc start -d --console /dev/pts/ptmx test_box2 + ROOT=$HELLO_BUNDLE runc run -d --console /dev/pts/ptmx test_box2 [ "$status" -eq 0 ] wait_for_container_inroot 15 1 test_box2 $HELLO_BUNDLE - ROOT=$HELLO_BUNDLE runc start -d --console /dev/pts/ptmx test_box3 + ROOT=$HELLO_BUNDLE runc run -d --console /dev/pts/ptmx test_box3 [ "$status" -eq 0 ] wait_for_container_inroot 15 1 test_box3 $HELLO_BUNDLE diff --git a/tests/integration/pause.bats b/tests/integration/pause.bats index 94ad3fc0c..ed12f4a67 100644 --- a/tests/integration/pause.bats +++ b/tests/integration/pause.bats @@ -12,8 +12,8 @@ function teardown() { } @test "runc pause and resume" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] wait_for_container 15 1 test_busybox diff --git a/tests/integration/root.bats b/tests/integration/root.bats index 8ee79996b..a2cb377cb 100644 --- a/tests/integration/root.bats +++ b/tests/integration/root.bats @@ -14,12 +14,12 @@ function teardown() { } @test "global --root" { - # start busybox detached using $HELLO_BUNDLE for state - ROOT=$HELLO_BUNDLE runc start -d --console /dev/pts/ptmx test_dotbox + # run busybox detached using $HELLO_BUNDLE for state + ROOT=$HELLO_BUNDLE runc run -d --console /dev/pts/ptmx test_dotbox [ "$status" -eq 0 ] - # start busybox detached in default root - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached in default root + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state of the busyboxes are only in their respective root path @@ -42,13 +42,13 @@ function teardown() { runc kill test_busybox KILL [ "$status" -eq 0 ] - retry 10 1 eval "__runc state test_busybox | grep -q 'destroyed'" + retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" runc delete test_busybox [ "$status" -eq 0 ] ROOT=$HELLO_BUNDLE runc kill test_dotbox KILL [ "$status" -eq 0 ] - retry 10 1 eval "ROOT='$HELLO_BUNDLE' __runc state test_dotbox | grep -q 'destroyed'" + retry 10 1 eval "ROOT='$HELLO_BUNDLE' __runc state test_dotbox | grep -q 'stopped'" ROOT=$HELLO_BUNDLE runc delete test_dotbox [ "$status" -eq 0 ] } diff --git a/tests/integration/spec.bats b/tests/integration/spec.bats index ef29710ad..c0c2537be 100644 --- a/tests/integration/spec.bats +++ b/tests/integration/spec.bats @@ -39,8 +39,8 @@ function teardown() { # change the default args parameter from sh to hello sed -i 's;"sh";"/hello";' config.json - # ensure the generated spec works by starting hello-world - runc start test_hello + # ensure the generated spec works by running hello-world + runc run test_hello [ "$status" -eq 0 ] } @@ -60,8 +60,8 @@ function teardown() { # change the default args parameter from sh to hello sed -i 's;"sh";"/hello";' "$HELLO_BUNDLE"/config.json - # ensure the generated spec works by starting hello-world - runc start --bundle "$HELLO_BUNDLE" test_hello + # ensure the generated spec works by running hello-world + runc run --bundle "$HELLO_BUNDLE" test_hello [ "$status" -eq 0 ] } diff --git a/tests/integration/start_detached.bats b/tests/integration/start_detached.bats index c88fbae11..e9df8e8b6 100644 --- a/tests/integration/start_detached.bats +++ b/tests/integration/start_detached.bats @@ -11,9 +11,9 @@ function teardown() { teardown_busybox } -@test "runc start detached" { - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox +@test "runc run detached" { + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -22,9 +22,9 @@ function teardown() { testcontainer test_busybox running } -@test "runc start detached --pid-file" { - # start busybox detached - runc start --pid-file pid.txt -d --console /dev/pts/ptmx test_busybox +@test "runc run detached --pid-file" { + # run busybox detached + runc run --pid-file pid.txt -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state diff --git a/tests/integration/start_hello.bats b/tests/integration/start_hello.bats index bba517712..cddc2be35 100644 --- a/tests/integration/start_hello.bats +++ b/tests/integration/start_hello.bats @@ -11,30 +11,30 @@ function teardown() { teardown_hello } -@test "runc start" { - # start hello-world - runc start test_hello +@test "runc run" { + # run hello-world + runc run test_hello [ "$status" -eq 0 ] # check expected output [[ "${output}" == *"Hello"* ]] } -@test "runc start with rootfs set to ." { +@test "runc run with rootfs set to ." { cp config.json rootfs/. rm config.json cd rootfs sed -i 's;"rootfs";".";' config.json - # start hello-world - runc start test_hello + # run hello-world + runc run test_hello [ "$status" -eq 0 ] [[ "${output}" == *"Hello"* ]] } -@test "runc start --pid-file" { - # start hello-world - runc start --pid-file pid.txt test_hello +@test "runc run --pid-file" { + # run hello-world + runc run --pid-file pid.txt test_hello [ "$status" -eq 0 ] [[ "${output}" == *"Hello"* ]] diff --git a/tests/integration/state.bats b/tests/integration/state.bats index 697bd4951..0073a2a39 100644 --- a/tests/integration/state.bats +++ b/tests/integration/state.bats @@ -15,8 +15,8 @@ function teardown() { runc state test_busybox [ "$status" -ne 0 ] - # start busybox detached - runc start -d --console /dev/pts/ptmx test_busybox + # run busybox detached + runc run -d --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] # check state @@ -40,7 +40,7 @@ function teardown() { runc kill test_busybox KILL # wait for busybox to be in the destroyed state - retry 10 1 eval "__runc state test_busybox | grep -q 'destroyed'" + retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" # delete test_busybox runc delete test_busybox diff --git a/tests/integration/update.bats b/tests/integration/update.bats index 0bb0d5b4c..2020127d7 100644 --- a/tests/integration/update.bats +++ b/tests/integration/update.bats @@ -48,8 +48,8 @@ function check_cgroup_value() { } @test "update" { - # start a few busyboxes detached - runc start -d --console /dev/pts/ptmx test_update + # run a few busyboxes detached + runc run -d --console /dev/pts/ptmx test_update [ "$status" -eq 0 ] wait_for_container 15 1 test_update diff --git a/utils.go b/utils.go index d1888a2e5..75f44d39b 100644 --- a/utils.go +++ b/utils.go @@ -5,6 +5,8 @@ import ( "os" "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/runtime-spec/specs-go" ) // fatal prints the error's details if it is a libcontainer specific error type @@ -15,3 +17,25 @@ func fatal(err error) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } + +// setupSpec performs inital setup based on the cli.Context for the container +func setupSpec(context *cli.Context) (*specs.Spec, error) { + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + return nil, err + } + } + spec, err := loadSpec(specConfig) + if err != nil { + return nil, err + } + notifySocket := os.Getenv("NOTIFY_SOCKET") + if notifySocket != "" { + setupSdNotify(spec, notifySocket) + } + if os.Geteuid() != 0 { + return nil, fmt.Errorf("runc should be run as root") + } + return spec, nil +} diff --git a/utils_linux.go b/utils_linux.go index c71df0d54..0fd1fbfad 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -11,6 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" + "github.com/coreos/go-systemd/activation" "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/specconv" @@ -198,6 +199,7 @@ type runner struct { pidFile string console string container libcontainer.Container + create bool } func (r *runner) run(config *specs.Process) (int, error) { @@ -220,7 +222,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 +247,15 @@ func (r *runner) run(config *specs.Process) (int, error) { return -1, err } } - if r.detach { + if !r.create { + if err := process.Signal(libcontainer.InitContinueSignal); err != nil { + r.terminate(process) + r.destroy() + tty.Close() + return -1, err + } + } + if r.detach || r.create { tty.Close() return 0, nil } @@ -281,3 +291,31 @@ func validateProcessSpec(spec *specs.Process) error { } return nil } + +func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, error) { + id := context.Args().First() + if id == "" { + return -1, errEmptyID + } + container, err := createContainer(context, id, spec) + if err != nil { + return -1, err + } + detach := context.Bool("detach") + // Support on-demand socket activation by passing file descriptors into the container init process. + listenFDs := []*os.File{} + if os.Getenv("LISTEN_FDS") != "" { + listenFDs = activation.Files(false) + } + r := &runner{ + enableSubreaper: !context.Bool("no-subreaper"), + shouldDestroy: true, + container: container, + listenFDs: listenFDs, + console: context.String("console"), + detach: detach, + pidFile: context.String("pid-file"), + create: create, + } + return r.run(&spec.Process) +}