From 75fb70be01f9c387a5d0d75841b48199a7989e9a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 13 May 2016 15:49:45 -0700 Subject: [PATCH 01/11] Rename start to run `runc run` is the command that will create and start a container in one single command. Signed-off-by: Michael Crosby --- main.go | 2 +- main_unix.go | 30 ++++++++++++++++++++++++++- start.go => run.go | 29 +++----------------------- tests/integration/cgroups.bats | 8 +++---- tests/integration/checkpoint.bats | 4 ++-- tests/integration/debug.bats | 16 +++++++------- tests/integration/delete.bats | 4 ++-- tests/integration/events.bats | 16 +++++++------- tests/integration/help.bats | 6 +++++- tests/integration/kill.bats | 4 ++-- tests/integration/list.bats | 8 +++---- tests/integration/pause.bats | 4 ++-- tests/integration/root.bats | 8 +++---- tests/integration/spec.bats | 8 +++---- tests/integration/start_detached.bats | 12 +++++------ tests/integration/start_hello.bats | 18 ++++++++-------- tests/integration/state.bats | 4 ++-- tests/integration/update.bats | 4 ++-- 18 files changed, 97 insertions(+), 88 deletions(-) rename start.go => run.go (79%) diff --git a/main.go b/main.go index 1b0df8a2c..79561957a 100644 --- a/main.go +++ b/main.go @@ -96,8 +96,8 @@ func main() { psCommand, restoreCommand, resumeCommand, + runCommand, specCommand, - startCommand, stateCommand, updateCommand, } 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/start.go b/run.go similarity index 79% rename from start.go rename to run.go index cf9392c2f..d999f49c9 100644 --- a/start.go +++ b/run.go @@ -5,24 +5,22 @@ 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", +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 start command creates an instance of a container for a bundle. The bundle + 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. @@ -90,27 +88,6 @@ command(s) that get executed on start, edit the args parameter of the spec. See }, } -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 == "" { 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/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..8e0e29484 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 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/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/kill.bats b/tests/integration/kill.bats index ccc035454..e688db006 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 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..f4a7ff0d2 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 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..582986d3a 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 diff --git a/tests/integration/update.bats b/tests/integration/update.bats index 991accd64..bd164d192 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 From 3fe7d7f31e7aec30f6e520991915f0be015479a3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 13 May 2016 16:54:16 -0700 Subject: [PATCH 02/11] 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 000000000..1b1c79c83 --- /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 8d282a1e4..c394c40e2 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 082e6c4ab..16f1385cb 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 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") + for _, v := range bytes.Split(environ, []byte("\x00")) { + if bytes.Contains(v, check) { + return Initialized, nil + } + } + return Running, nil } func (c *linuxContainer) isPaused() (bool, error) { diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index d58c5a559..95a784d05 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 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/utils_test.go b/libcontainer/integration/utils_test.go index e2ca10e00..e77587f6d 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 b1a198fd1..388cd81f1 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 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..cd045b8ae 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 79561957a..11d3f624f 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 d999f49c9..81832b2dc 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 000000000..eb794371c --- /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 c71df0d54..810d5d262 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 } From 30f1006b33c97527bca34a0e74bd8ccc44716b17 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 13 May 2016 17:01:12 -0700 Subject: [PATCH 03/11] Fix libcontainer states Move initialized to created and destoryed to stopped. Signed-off-by: Michael Crosby --- events.go | 3 ++- libcontainer/container.go | 13 +------------ libcontainer/container_linux.go | 8 ++++---- libcontainer/factory_linux.go | 2 +- libcontainer/state_linux.go | 24 ++++++++++++------------ 5 files changed, 20 insertions(+), 30 deletions(-) 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 c394c40e2..5e081da5c 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -15,7 +15,7 @@ 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. @@ -27,15 +27,8 @@ const ( // 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 - - // 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 { @@ -48,12 +41,8 @@ func (s Status) String() string { return "pausing" case Paused: 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 16f1385cb..81139c1ae 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -181,7 +181,7 @@ func (c *linuxContainer) Start(process *Process) error { if err != nil { return err } - doInit := status == Destroyed + doInit := status == Stopped parent, err := c.newParentProcess(process, doInit) if err != nil { return newSystemErrorWithCause(err, "creating new parent process") @@ -1038,8 +1038,8 @@ func (c *linuxContainer) refreshState() error { return err } switch t { - case Initialized: - return c.state.transition(&initializedState{c: c}) + case Created: + return c.state.transition(&createdState{c: c}) case Running: return c.state.transition(&runningState{c: c}) } @@ -1070,7 +1070,7 @@ func (c *linuxContainer) runType() (Status, error) { check := []byte("_LIBCONTAINER") for _, v := range bytes.Split(environ, []byte("\x00")) { if bytes.Contains(v, check) { - return Initialized, nil + return Created, nil } } return Running, nil diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index 95a784d05..cff080888 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -206,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 } diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go index cd045b8ae..790af0819 100644 --- a/libcontainer/state_linux.go +++ b/libcontainer/state_linux.go @@ -77,7 +77,7 @@ type stoppedState struct { } func (b *stoppedState) status() Status { - return Destroyed + return Stopped } func (b *stoppedState) transition(s containerState) error { @@ -139,25 +139,25 @@ func (r *runningState) destroy() error { return destroy(r.c) } -type initializedState struct { +type createdState struct { c *linuxContainer } -func (i *initializedState) status() Status { - return Initialized +func (i *createdState) status() Status { + return Created } -func (i *initializedState) transition(s containerState) error { +func (i *createdState) transition(s containerState) error { switch s.(type) { case *runningState: i.c.state = s - case *initializedState: + case *createdState: return nil } return newStateTransitionError(i, s) } -func (i *initializedState) destroy() error { +func (i *createdState) destroy() error { return destroy(i.c) } @@ -226,23 +226,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 } From 3fc929f350ca3512a49c66befa922dcff9a8a8f3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 13 May 2016 17:11:22 -0700 Subject: [PATCH 04/11] Only create a buffered channel of one Signed-off-by: Michael Crosby --- libcontainer/factory_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index cff080888..cf779509b 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -221,7 +221,7 @@ func (l *LinuxFactory) Type() string { // 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) + s := make(chan os.Signal, 1) signal.Notify(s, syscall.SIGCONT) fdStr := os.Getenv("_LIBCONTAINER_INITPIPE") pipefd, err := strconv.Atoi(fdStr) From 88dcf1d68637b5641ce7abf800aed67c619e1401 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 16 May 2016 17:04:49 -0700 Subject: [PATCH 05/11] Kill container on delete If the container's state is `created` when runc delete is called make sure that the init is killed before deleting the on system state. Signed-off-by: Michael Crosby --- delete.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/delete.go b/delete.go index 17d5c2fa7..ac9283151 100644 --- a/delete.go +++ b/delete.go @@ -5,6 +5,7 @@ package main import ( "os" "path/filepath" + "syscall" "github.com/codegangsta/cli" "github.com/opencontainers/runc/libcontainer" @@ -36,6 +37,10 @@ status of "ubuntu01" as "destroyed" the following will delete resources held for } return nil } + s, err := container.Status() + if err == nil && s == libcontainer.Created { + container.Signal(syscall.SIGKILL) + } destroy(container) return nil }, From 60f5df6e09796ea7e16f2347454d205a24ed5cd6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 17 May 2016 10:25:28 -0700 Subject: [PATCH 06/11] Check container status for start call Signed-off-by: Michael Crosby --- start.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/start.go b/start.go index eb794371c..1ee571f30 100644 --- a/start.go +++ b/start.go @@ -4,6 +4,7 @@ import ( "syscall" "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" ) var startCommand = cli.Command{ @@ -20,8 +21,14 @@ your host.`, if err != nil { fatal(err) } - if err := container.Signal(syscall.SIGCONT); err != nil { + status, err := container.Status() + if err != nil { fatal(err) } + if status == libcontainer.Created { + if err := container.Signal(syscall.SIGCONT); err != nil { + fatal(err) + } + } }, } From efcd73fb5b6cd96c41c63d4870c33d9381ce7780 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 19 May 2016 17:28:58 -0700 Subject: [PATCH 07/11] Fix signal handling for unit tests Signed-off-by: Michael Crosby --- Makefile | 2 +- libcontainer/container.go | 15 +++++-- libcontainer/container_linux.go | 50 ++++++++++++++++----- libcontainer/factory_linux.go | 2 +- libcontainer/integration/checkpoint_test.go | 2 +- libcontainer/integration/exec_test.go | 46 +++++++++---------- libcontainer/integration/execin_test.go | 32 ++++++------- libcontainer/integration/seccomp_test.go | 6 +-- libcontainer/integration/template_test.go | 14 +++--- libcontainer/integration/utils_test.go | 5 +-- libcontainer/process_linux.go | 1 + libcontainer/state_linux.go | 5 ++- libcontainer/state_linux_test.go | 3 +- run.go | 2 +- start.go | 4 +- tests/integration/delete.bats | 2 +- tests/integration/exec.bats | 8 ++-- tests/integration/helpers.bash | 4 +- tests/integration/kill.bats | 2 +- tests/integration/root.bats | 4 +- tests/integration/state.bats | 2 +- utils_linux.go | 2 +- 22 files changed, 125 insertions(+), 88 deletions(-) diff --git a/Makefile b/Makefile index c1bbafaaa..06b116d96 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,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/libcontainer/container.go b/libcontainer/container.go index 5e081da5c..7e89422a0 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -17,16 +17,12 @@ type Status int const ( // 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 - // Stopped is the status that denotes the container does not have a created or running process. Stopped ) @@ -127,6 +123,17 @@ type BaseContainer interface { // Systemerror - System error. Start(process *Process) (err error) + // StartI 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. + StartI(process *Process) (err error) + // Destroys the container after killing all running processes. // // Any event registrations are removed before the container is destroyed. diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 81139c1ae..a0413069f 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 == Stopped - parent, err := c.newParentProcess(process, doInit) + return c.start(process, status == Stopped) +} + +func (c *linuxContainer) StartI(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") } @@ -198,7 +222,10 @@ func (c *linuxContainer) Start(process *Process) error { c.state = &runningState{ c: c, } - if doInit { + if isInit { + c.state = &createdState{ + c: c, + } if err := c.updateState(parent); err != nil { return err } @@ -370,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 { diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index cf779509b..1ac06fb0d 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -222,7 +222,7 @@ func (l *LinuxFactory) Type() string { func (l *LinuxFactory) StartInitialization() (err error) { // start the signal handler as soon as we can s := make(chan os.Signal, 1) - signal.Notify(s, syscall.SIGCONT) + signal.Notify(s, InitContinueSignal) fdStr := os.Getenv("_LIBCONTAINER_INITPIPE") pipefd, err := strconv.Atoi(fdStr) if err != nil { diff --git a/libcontainer/integration/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go index a71c172a3..66867b0db 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.StartI(&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..4945048de 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.StartI(&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.StartI(&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.StartI(&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.StartI(&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.StartI(&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.StartI(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.StartI(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.StartI(&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.StartI(&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.StartI(&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.StartI(&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.StartI(&pconfig) ok(t, err) // Wait for process @@ -1231,7 +1231,7 @@ func TestRootfsPropagationSlaveMount(t *testing.T) { Stdin: stdinR, } - err = container.Start(pconfig) + err = container.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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 + // StartI 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.StartI(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.StartI(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.StartI(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 + // StartI 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.StartI(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..b3211baa1 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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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..4049033aa 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.StartI(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.StartI(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.StartI(dmesg) if err != nil { t.Fatal(err) } diff --git a/libcontainer/integration/template_test.go b/libcontainer/integration/template_test.go index f54a849ac..2345dd350 100644 --- a/libcontainer/integration/template_test.go +++ b/libcontainer/integration/template_test.go @@ -89,12 +89,14 @@ func newTemplateConfig(rootfs string) *configs.Config { Data: "mode=1777,size=65536k", Flags: defaultMountFlags, }, - { - Source: "mqueue", - Destination: "/dev/mqueue", - Device: "mqueue", - Flags: defaultMountFlags, - }, + /* + { + 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 e77587f6d..b2f884c6d 100644 --- a/libcontainer/integration/utils_test.go +++ b/libcontainer/integration/utils_test.go @@ -123,13 +123,10 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe Stderr: buffers.Stderr, } - err = container.Start(process) + err = container.StartI(process) 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/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/state_linux.go b/libcontainer/state_linux.go index 790af0819..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" @@ -149,8 +150,9 @@ func (i *createdState) status() Status { func (i *createdState) transition(s containerState) error { switch s.(type) { - case *runningState: + case *runningState, *pausedState, *stoppedState: i.c.state = s + return nil case *createdState: return nil } @@ -158,6 +160,7 @@ func (i *createdState) transition(s containerState) error { } func (i *createdState) destroy() error { + i.c.initProcess.signal(syscall.SIGKILL) return destroy(i.c) } 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/run.go b/run.go index 81832b2dc..7b8920353 100644 --- a/run.go +++ b/run.go @@ -40,7 +40,7 @@ command(s) that get executed on start, edit the args parameter of the spec. See Usage: "specify the pty slave path for use with the container", }, cli.BoolFlag{ - Name: "detach,d", + Name: "detach, d", Usage: "detach from the container's process", }, cli.StringFlag{ diff --git a/start.go b/start.go index 1ee571f30..c440e2922 100644 --- a/start.go +++ b/start.go @@ -1,8 +1,6 @@ package main import ( - "syscall" - "github.com/codegangsta/cli" "github.com/opencontainers/runc/libcontainer" ) @@ -26,7 +24,7 @@ your host.`, fatal(err) } if status == libcontainer.Created { - if err := container.Signal(syscall.SIGCONT); err != nil { + if err := container.Signal(libcontainer.InitContinueSignal); err != nil { fatal(err) } } diff --git a/tests/integration/delete.bats b/tests/integration/delete.bats index 8e0e29484..89a60ebcb 100644 --- a/tests/integration/delete.bats +++ b/tests/integration/delete.bats @@ -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/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/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 e688db006..e0d89e947 100644 --- a/tests/integration/kill.bats +++ b/tests/integration/kill.bats @@ -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/root.bats b/tests/integration/root.bats index f4a7ff0d2..a2cb377cb 100644 --- a/tests/integration/root.bats +++ b/tests/integration/root.bats @@ -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/state.bats b/tests/integration/state.bats index 582986d3a..0073a2a39 100644 --- a/tests/integration/state.bats +++ b/tests/integration/state.bats @@ -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/utils_linux.go b/utils_linux.go index 810d5d262..093020496 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -247,7 +247,7 @@ func (r *runner) run(config *specs.Process) (int, error) { } } if !r.create { - if err := process.Signal(syscall.SIGCONT); err != nil { + if err := process.Signal(libcontainer.InitContinueSignal); err != nil { r.terminate(process) r.destroy() tty.Close() From b9bc020f0dd115c09519cbdf23e4c0467cbc7615 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 20 May 2016 15:21:16 -0700 Subject: [PATCH 08/11] Update Dockerfile to 1.6.2 With this change we need a fix in go 1.6 to allow us to receive SIGCONT signals. Ref: https://github.com/golang/go/issues/8953 Signed-off-by: Michael Crosby --- script/test_Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6eba9b8ffb2bf5b855510a48f932bf80e2ed2a9f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 25 May 2016 11:24:26 -0700 Subject: [PATCH 09/11] Fix SystemError and env lookup Signed-off-by: Michael Crosby --- create.go | 22 +++-------- delete.go | 11 ++++-- libcontainer/container.go | 18 ++++----- libcontainer/container_linux.go | 6 +-- libcontainer/integration/template_test.go | 13 ++++--- run.go | 46 +---------------------- start.go | 23 ++++++++---- utils.go | 24 ++++++++++++ utils_linux.go | 29 ++++++++++++++ 9 files changed, 99 insertions(+), 93 deletions(-) diff --git a/create.go b/create.go index 1b1c79c83..6b754d8fd 100644 --- a/create.go +++ b/create.go @@ -43,30 +43,18 @@ command(s) that get executed on start, edit the args parameter of the spec. See 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) + Action: func(context *cli.Context) error { + spec, err := setupSpec(context) 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") + return err } status, err := startContainer(context, spec, true) if err != nil { - fatal(err) + 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 ac9283151..7135e35a6 100644 --- a/delete.go +++ b/delete.go @@ -3,9 +3,9 @@ package main import ( + "fmt" "os" "path/filepath" - "syscall" "github.com/codegangsta/cli" "github.com/opencontainers/runc/libcontainer" @@ -20,7 +20,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`, @@ -38,8 +38,11 @@ status of "ubuntu01" as "destroyed" the following will delete resources held for return nil } s, err := container.Status() - if err == nil && s == libcontainer.Created { - container.Signal(syscall.SIGKILL) + if err != nil { + return err + } + if s != libcontainer.Stopped { + return fmt.Errorf("cannot delete container that is not stopped: %s", s) } destroy(container) return nil diff --git a/libcontainer/container.go b/libcontainer/container.go index 7e89422a0..5c5c6c77d 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -76,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. @@ -92,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. @@ -102,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 @@ -110,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 @@ -120,7 +120,7 @@ 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) // StartI immediatly starts the process inside the conatiner. Returns error if process @@ -131,7 +131,7 @@ type BaseContainer interface { // ContainerDestroyed - Container no longer exists, // ConfigInvalid - config is invalid, // ContainerPaused - Container is paused, - // Systemerror - System error. + // SystemError - System error. StartI(process *Process) (err error) // Destroys the container after killing all running processes. @@ -140,12 +140,12 @@ type BaseContainer interface { // 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 a0413069f..dcced5e81 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -1096,10 +1096,8 @@ func (c *linuxContainer) runType() (Status, error) { 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 Created, nil - } + if bytes.Contains(environ, check) { + return Created, nil } return Running, nil } diff --git a/libcontainer/integration/template_test.go b/libcontainer/integration/template_test.go index 2345dd350..961656840 100644 --- a/libcontainer/integration/template_test.go +++ b/libcontainer/integration/template_test.go @@ -90,12 +90,13 @@ func newTemplateConfig(rootfs string) *configs.Config { 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", diff --git a/run.go b/run.go index 7b8920353..b69fcfa74 100644 --- a/run.go +++ b/run.go @@ -3,12 +3,9 @@ package main import ( - "fmt" "os" "github.com/codegangsta/cli" - "github.com/coreos/go-systemd/activation" - "github.com/opencontainers/runtime-spec/specs-go" ) // default action is to start a container @@ -58,23 +55,10 @@ command(s) that get executed on start, edit the args parameter of the spec. See }, }, 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) + spec, err := setupSpec(context) 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, false) if err == nil { // exit with the container's exit status so any external supervisor is @@ -84,31 +68,3 @@ command(s) that get executed on start, edit the args parameter of the spec. See return err }, } - -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) -} diff --git a/start.go b/start.go index c440e2922..45b39147a 100644 --- a/start.go +++ b/start.go @@ -1,32 +1,39 @@ package main import ( + "fmt" + "github.com/codegangsta/cli" "github.com/opencontainers/runc/libcontainer" ) var startCommand = cli.Command{ Name: "start", - Usage: "start signals a created container to execute the users defined process", + 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 signals the container to start the user's defined process.`, - Action: func(context *cli.Context) { + Action: func(context *cli.Context) error { container, err := getContainer(context) if err != nil { - fatal(err) + return err } status, err := container.Status() if err != nil { - fatal(err) + return err } - if status == libcontainer.Created { - if err := container.Signal(libcontainer.InitContinueSignal); err != nil { - fatal(err) - } + 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) } }, } 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 093020496..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" @@ -290,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) +} From 06fab0f8608832d0eee05344d0b8e632e1db984e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 26 May 2016 14:44:59 -0700 Subject: [PATCH 10/11] Add integration tests for create/start Signed-off-by: Michael Crosby --- tests/integration/create.bats | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/integration/create.bats diff --git a/tests/integration/create.bats b/tests/integration/create.bats new file mode 100644 index 000000000..b6810d65e --- /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" { + run "$RUNC" create --console /dev/pts/ptmx test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox created + + # start the command + run "$RUNC" start test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox running +} + +@test "runc create exec" { + run "$RUNC" create --console /dev/pts/ptmx test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox created + + run "$RUNC" exec test_busybox true + [ "$status" -eq 0 ] + + # start the command + run "$RUNC" start test_busybox + [ "$status" -eq 0 ] + + testcontainer test_busybox running +} From 1d61abea46acf375008c983df29b1dcf03d9d127 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 27 May 2016 13:13:11 -0700 Subject: [PATCH 11/11] Allow delete of created container Signed-off-by: Michael Crosby --- delete.go | 18 +++++++- libcontainer/container.go | 4 +- libcontainer/container_linux.go | 2 +- libcontainer/integration/checkpoint_test.go | 2 +- libcontainer/integration/exec_test.go | 46 ++++++++++----------- libcontainer/integration/execin_test.go | 32 +++++++------- libcontainer/integration/seccomp_test.go | 6 +-- libcontainer/integration/utils_test.go | 2 +- tests/integration/create.bats | 10 ++--- 9 files changed, 68 insertions(+), 54 deletions(-) diff --git a/delete.go b/delete.go index 7135e35a6..81cb4cda6 100644 --- a/delete.go +++ b/delete.go @@ -6,6 +6,8 @@ import ( "fmt" "os" "path/filepath" + "syscall" + "time" "github.com/codegangsta/cli" "github.com/opencontainers/runc/libcontainer" @@ -41,10 +43,22 @@ status of "ubuntu01" as "stopped" the following will delete resources held for if err != nil { return err } - if s != libcontainer.Stopped { + 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) } - destroy(container) return nil }, } diff --git a/libcontainer/container.go b/libcontainer/container.go index 5c5c6c77d..1dc283d8b 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -123,7 +123,7 @@ type BaseContainer interface { // SystemError - System error. Start(process *Process) (err error) - // StartI immediatly starts the process inside the conatiner. Returns error if process + // 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. // @@ -132,7 +132,7 @@ type BaseContainer interface { // ConfigInvalid - config is invalid, // ContainerPaused - Container is paused, // SystemError - System error. - StartI(process *Process) (err error) + Run(process *Process) (err error) // Destroys the container after killing all running processes. // diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index dcced5e81..31a51c4f4 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -188,7 +188,7 @@ func (c *linuxContainer) Start(process *Process) error { return c.start(process, status == Stopped) } -func (c *linuxContainer) StartI(process *Process) error { +func (c *linuxContainer) Run(process *Process) error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus() diff --git a/libcontainer/integration/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go index 66867b0db..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.StartI(&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 4945048de..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.StartI(&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.StartI(&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.StartI(&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.StartI(&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.StartI(&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.StartI(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.StartI(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.StartI(&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.StartI(&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.StartI(&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.StartI(&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.StartI(&pconfig) + err = container.Run(&pconfig) ok(t, err) // Wait for process @@ -1231,7 +1231,7 @@ func TestRootfsPropagationSlaveMount(t *testing.T) { Stdin: stdinR, } - err = container.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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] - // StartI 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.StartI(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.StartI(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.StartI(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] - // StartI 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.StartI(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 b3211baa1..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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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.StartI(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 4049033aa..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.StartI(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.StartI(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.StartI(dmesg) + err = container.Run(dmesg) if err != nil { t.Fatal(err) } diff --git a/libcontainer/integration/utils_test.go b/libcontainer/integration/utils_test.go index b2f884c6d..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.StartI(process) + err = container.Run(process) if err != nil { return buffers, -1, err } diff --git a/tests/integration/create.bats b/tests/integration/create.bats index b6810d65e..0b6d91b7e 100644 --- a/tests/integration/create.bats +++ b/tests/integration/create.bats @@ -12,29 +12,29 @@ function teardown() { } @test "runc create" { - run "$RUNC" create --console /dev/pts/ptmx test_busybox + runc create --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created # start the command - run "$RUNC" start test_busybox + runc start test_busybox [ "$status" -eq 0 ] testcontainer test_busybox running } @test "runc create exec" { - run "$RUNC" create --console /dev/pts/ptmx test_busybox + runc create --console /dev/pts/ptmx test_busybox [ "$status" -eq 0 ] testcontainer test_busybox created - run "$RUNC" exec test_busybox true + runc exec test_busybox true [ "$status" -eq 0 ] # start the command - run "$RUNC" start test_busybox + runc start test_busybox [ "$status" -eq 0 ] testcontainer test_busybox running