Merge pull request #827 from crosbymichael/create-start

Implement create and start
This commit is contained in:
Michael Crosby
2016-06-03 10:38:03 -07:00
44 changed files with 591 additions and 302 deletions
+1 -1
View File
@@ -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
+60
View File
@@ -0,0 +1,60 @@
package main
import (
"os"
"github.com/codegangsta/cli"
)
var createCommand = cli.Command{
Name: "create",
Usage: "create a container",
ArgsUsage: `<container-id>
Where "<container-id>" 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
},
}
+24 -2
View File
@@ -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 "<container-id>" 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
},
}
+2 -1
View File
@@ -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 (
+24 -17
View File
@@ -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
}
+61 -20
View File
@@ -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) {
+6 -2
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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 {
+23 -23
View File
@@ -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)
+16 -16
View File
@@ -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()
+3 -3
View File
@@ -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)
}
+9 -6
View File
@@ -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",
+1 -1
View File
@@ -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
}
+1
View File
@@ -221,6 +221,7 @@ func (p *initProcess) execSetns() error {
return err
}
p.cmd.Process = process
p.process.ops = p
return nil
}
+4 -1
View File
@@ -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())
}
+18 -4
View File
@@ -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())
}
+37 -12
View File
@@ -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
}
+2 -1
View File
@@ -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 {
+2
View File
@@ -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,
+29 -1
View File
@@ -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")
},
}
+70
View File
@@ -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: `<container-id>
Where "<container-id>" 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
},
}
+1 -1
View File
@@ -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
+15 -115
View File
@@ -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: `<container-id>
Where "<container-id>" 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)
}
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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 ]
) &
+41
View File
@@ -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
}
+8 -8
View File
@@ -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
+3 -3
View File
@@ -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
+8 -8
View File
@@ -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
+4 -4
View File
@@ -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
+5 -1
View File
@@ -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+ ]]
+2 -2
View File
@@ -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
}
+3 -3
View File
@@ -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 ]
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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
+6 -6
View File
@@ -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 ]
}
+4 -4
View File
@@ -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 ]
}
+6 -6
View File
@@ -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
+9 -9
View File
@@ -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"* ]]
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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
+24
View File
@@ -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
}
+40 -2
View File
@@ -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)
}