Add create and start command for container lifecycle

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2016-05-13 16:54:16 -07:00
parent 75fb70be01
commit 3fe7d7f31e
13 changed files with 209 additions and 29 deletions

72
create.go Normal file
View File

@ -0,0 +1,72 @@
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) {
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)
},
}

View File

@ -29,6 +29,13 @@ const (
// Destroyed is the status that denotes the container does not exist. // Destroyed is the status that denotes the container does not exist.
Destroyed 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 { func (s Status) String() string {
@ -43,6 +50,10 @@ func (s Status) String() string {
return "paused" return "paused"
case Destroyed: case Destroyed:
return "destroyed" return "destroyed"
case Stopped:
return "stopped"
case Initialized:
return "initialized"
default: default:
return "unknown" return "unknown"
} }

View File

@ -195,7 +195,6 @@ func (c *linuxContainer) Start(process *Process) error {
} }
// generate a timestamp indicating when the container was started // generate a timestamp indicating when the container was started
c.created = time.Now().UTC() c.created = time.Now().UTC()
c.state = &runningState{ c.state = &runningState{
c: c, c: c,
} }
@ -1034,31 +1033,47 @@ func (c *linuxContainer) refreshState() error {
if paused { if paused {
return c.state.transition(&pausedState{c: c}) return c.state.transition(&pausedState{c: c})
} }
running, err := c.isRunning() t, err := c.runType()
if err != nil { if err != nil {
return err 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(&runningState{c: c})
} }
return c.state.transition(&stoppedState{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 { if c.initProcess == nil {
return false, nil return Stopped, nil
} }
pid := c.initProcess.pid()
// return Running if the init process is alive // 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 { if err == syscall.ESRCH {
// It means the process does not exist anymore, could happen when the // It means the process does not exist anymore, could happen when the
// process exited just when we call the function, we should not return // process exited just when we call the function, we should not return
// error in this case. // 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) { func (c *linuxContainer) isPaused() (bool, error) {

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime/debug" "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 // 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 // This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error) { 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") fdStr := os.Getenv("_LIBCONTAINER_INITPIPE")
pipefd, err := strconv.Atoi(fdStr) pipefd, err := strconv.Atoi(fdStr)
if err != nil { if err != nil {
@ -260,7 +264,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
if err != nil { if err != nil {
return err return err
} }
return i.Init() return i.Init(s)
} }
func (l *LinuxFactory) loadState(root string) (*State, error) { func (l *LinuxFactory) loadState(root string) (*State, error) {

View File

@ -61,7 +61,7 @@ type initConfig struct {
} }
type initer interface { type initer interface {
Init() error Init(s chan os.Signal) error
} }
func newContainerInit(t initType, pipe *os.File) (initer, error) { func newContainerInit(t initType, pipe *os.File) (initer, error) {

View File

@ -127,6 +127,9 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe
if err != nil { if err != nil {
return buffers, -1, err return buffers, -1, err
} }
if err := container.Signal(syscall.SIGCONT); err != nil {
return buffers, -1, err
}
ps, err := process.Wait() ps, err := process.Wait()
if err != nil { if err != nil {
return buffers, -1, err return buffers, -1, err

View File

@ -5,6 +5,7 @@ package libcontainer
import ( import (
"fmt" "fmt"
"os" "os"
"os/signal"
"github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/keys" "github.com/opencontainers/runc/libcontainer/keys"
@ -23,7 +24,7 @@ func (l *linuxSetnsInit) getSessionRingName() string {
return fmt.Sprintf("_ses.%s", l.config.ContainerId) 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 // do not inherit the parent's session keyring
if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil { if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil {
return err return err
@ -49,5 +50,7 @@ func (l *linuxSetnsInit) Init() error {
return err return err
} }
} }
signal.Stop(s)
close(s)
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
} }

View File

@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"os/signal"
"syscall" "syscall"
"github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/apparmor"
@ -17,7 +19,7 @@ import (
) )
type linuxStandardInit struct { type linuxStandardInit struct {
pipe io.ReadWriter pipe io.ReadWriteCloser
parentPid int parentPid int
config *initConfig config *initConfig
} }
@ -42,7 +44,7 @@ func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
// the kernel // the kernel
const PR_SET_NO_NEW_PRIVS = 0x26 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() ringname, keepperms, newperms := l.getSessionRingParams()
// do not inherit the parent's session keyring // do not inherit the parent's session keyring
@ -150,6 +152,18 @@ func (l *linuxStandardInit) Init() error {
return err return err
} }
} }
// check for the arg before waiting to make sure it exists and it is returned
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) // 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())
} }

View File

@ -110,11 +110,11 @@ func (r *runningState) status() Status {
func (r *runningState) transition(s containerState) error { func (r *runningState) transition(s containerState) error {
switch s.(type) { switch s.(type) {
case *stoppedState: case *stoppedState:
running, err := r.c.isRunning() t, err := r.c.runType()
if err != nil { if err != nil {
return err return err
} }
if running { if t == Running {
return newGenericError(fmt.Errorf("container still running"), ContainerNotStopped) return newGenericError(fmt.Errorf("container still running"), ContainerNotStopped)
} }
r.c.state = s r.c.state = s
@ -129,16 +129,38 @@ func (r *runningState) transition(s containerState) error {
} }
func (r *runningState) destroy() error { func (r *runningState) destroy() error {
running, err := r.c.isRunning() t, err := r.c.runType()
if err != nil { if err != nil {
return err return err
} }
if running { if t == Running {
return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped) return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped)
} }
return destroy(r.c) 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 // pausedState represents a container that is currently pause. It cannot be destroyed in a
// paused state and must transition back to running first. // paused state and must transition back to running first.
type pausedState struct { type pausedState struct {
@ -161,11 +183,11 @@ func (p *pausedState) transition(s containerState) error {
} }
func (p *pausedState) destroy() error { func (p *pausedState) destroy() error {
isRunning, err := p.c.isRunning() t, err := p.c.runType()
if err != nil { if err != nil {
return err return err
} }
if !isRunning { if t != Running && t != Created {
if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil { if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil {
return err return err
} }

View File

@ -86,6 +86,7 @@ func main() {
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
checkpointCommand, checkpointCommand,
createCommand,
deleteCommand, deleteCommand,
eventsCommand, eventsCommand,
execCommand, execCommand,
@ -98,6 +99,7 @@ func main() {
resumeCommand, resumeCommand,
runCommand, runCommand,
specCommand, specCommand,
startCommand,
stateCommand, stateCommand,
updateCommand, updateCommand,
} }

8
run.go
View File

@ -68,17 +68,14 @@ command(s) that get executed on start, edit the args parameter of the spec. See
if err != nil { if err != nil {
return err return err
} }
notifySocket := os.Getenv("NOTIFY_SOCKET") notifySocket := os.Getenv("NOTIFY_SOCKET")
if notifySocket != "" { if notifySocket != "" {
setupSdNotify(spec, notifySocket) setupSdNotify(spec, notifySocket)
} }
if os.Geteuid() != 0 { if os.Geteuid() != 0 {
return fmt.Errorf("runc should be run as root") return fmt.Errorf("runc should be run as root")
} }
status, err := startContainer(context, spec, false)
status, err := startContainer(context, spec)
if err == nil { if err == nil {
// exit with the container's exit status so any external supervisor is // exit with the container's exit status so any external supervisor is
// notified of the exit with the correct exit status. // 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() id := context.Args().First()
if id == "" { if id == "" {
return -1, errEmptyID return -1, errEmptyID
@ -111,6 +108,7 @@ func startContainer(context *cli.Context, spec *specs.Spec) (int, error) {
console: context.String("console"), console: context.String("console"),
detach: detach, detach: detach,
pidFile: context.String("pid-file"), pidFile: context.String("pid-file"),
create: create,
} }
return r.run(&spec.Process) return r.run(&spec.Process)
} }

27
start.go Normal file
View File

@ -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: `<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 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)
}
},
}

View File

@ -198,6 +198,7 @@ type runner struct {
pidFile string pidFile string
console string console string
container libcontainer.Container container libcontainer.Container
create bool
} }
func (r *runner) run(config *specs.Process) (int, error) { func (r *runner) run(config *specs.Process) (int, error) {
@ -220,7 +221,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
r.destroy() r.destroy()
return -1, err 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 { if err != nil {
r.destroy() r.destroy()
return -1, err return -1, err
@ -245,7 +246,15 @@ func (r *runner) run(config *specs.Process) (int, error) {
return -1, err 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() tty.Close()
return 0, nil return 0, nil
} }