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
// 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"
}

View File

@ -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) {

View File

@ -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) {

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) {

View File

@ -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

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
@ -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())
}

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())
}

View File

@ -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
}

View File

@ -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,
}

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 {
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)
}

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
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
}