Skip to content

Commit

Permalink
Add create and start command for container lifecycle
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Crosby <[email protected]>
  • Loading branch information
crosbymichael committed May 31, 2016
1 parent 75fb70b commit 3fe7d7f
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 29 deletions.
72 changes: 72 additions & 0 deletions create.go
Original file line number Diff line number Diff line change
@@ -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)
},
}
11 changes: 11 additions & 0 deletions libcontainer/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"
}
Expand Down
33 changes: 24 additions & 9 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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 Stopped, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", pid)
}
// 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 false, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", c.initProcess.pid())
}
return true, nil
return Running, nil
}

func (c *linuxContainer) isPaused() (bool, error) {
Expand Down
6 changes: 5 additions & 1 deletion libcontainer/factory_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime/debug"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion libcontainer/init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions libcontainer/integration/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion libcontainer/setns_init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package libcontainer
import (
"fmt"
"os"
"os/signal"

"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/keys"
Expand All @@ -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
Expand All @@ -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())
}
22 changes: 18 additions & 4 deletions libcontainer/standard_init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"

"github.com/opencontainers/runc/libcontainer/apparmor"
Expand All @@ -17,7 +19,7 @@ import (
)

type linuxStandardInit struct {
pipe io.ReadWriter
pipe io.ReadWriteCloser
parentPid int
config *initConfig
}
Expand All @@ -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
Expand Down Expand Up @@ -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())
}
34 changes: 28 additions & 6 deletions libcontainer/state_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func main() {
}
app.Commands = []cli.Command{
checkpointCommand,
createCommand,
deleteCommand,
eventsCommand,
execCommand,
Expand All @@ -98,6 +99,7 @@ func main() {
resumeCommand,
runCommand,
specCommand,
startCommand,
stateCommand,
updateCommand,
}
Expand Down
Loading

0 comments on commit 3fe7d7f

Please sign in to comment.