diff --git a/Makefile b/Makefile index 0c4dda7c9..bc7f17851 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ sh: GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u) direct-test: + go get github.com/golang/glog && \ go test $(TEST_TAGS) -cover -v $(GO_PACKAGES) direct-test-short: diff --git a/error.go b/error.go index e86267009..062943a11 100644 --- a/error.go +++ b/error.go @@ -14,6 +14,7 @@ const ( // Container errors ContainerNotExists ContainerPaused + ContainerNotStopped // Common errors ConfigInvalid @@ -34,6 +35,8 @@ func (c ErrorCode) String() string { return "System error" case ContainerNotExists: return "Container does not exist" + case ContainerNotStopped: + return "Container isn't stopped" default: return "Unknown error" } diff --git a/factory.go b/factory.go index 389c138a6..4959ff1e1 100644 --- a/factory.go +++ b/factory.go @@ -27,4 +27,14 @@ type Factory interface { // Container is stopped // System error Load(id string) (Container, error) + + // StartInitialization is an internal API to libcontainer used during the rexec of the + // container. pipefd is the fd to the child end of the pipe used to syncronize the + // parent and child process providing state and configuration to the child process and + // returning any errors during the init of the container + // + // Errors: + // pipe connection error + // system error + StartInitialization(pipefd uintptr) error } diff --git a/linux_container.go b/linux_container.go index 0ba92a601..10d66bfe1 100644 --- a/linux_container.go +++ b/linux_container.go @@ -3,6 +3,13 @@ package libcontainer import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "syscall" + "github.com/docker/libcontainer/network" "github.com/golang/glog" ) @@ -13,6 +20,7 @@ type linuxContainer struct { config *Config state *State cgroupManager CgroupManager + initArgs []string } func (c *linuxContainer) ID() string { @@ -24,7 +32,7 @@ func (c *linuxContainer) Config() *Config { } func (c *linuxContainer) RunState() (RunState, error) { - panic("not implemented") + return Destroyed, nil // FIXME return a real state } func (c *linuxContainer) Processes() ([]int, error) { @@ -53,13 +61,91 @@ func (c *linuxContainer) Stats() (*ContainerStats, error) { } func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) { - glog.Info("start new container process") - panic("not implemented") + state, err := c.RunState() + if err != nil { + return -1, err + } + + if state != Destroyed { + glog.Info("start new container process") + panic("not implemented") + } + + if err := c.startInitProcess(config); err != nil { + return -1, err + } + + return c.state.InitPid, nil +} + +func (c *linuxContainer) updateStateFile() error { + data, err := json.MarshalIndent(c.state, "", "\t") + if err != nil { + return newGenericError(err, SystemError) + } + + fnew := filepath.Join(c.root, fmt.Sprintf("%s.new", stateFilename)) + f, err := os.Create(fnew) + if err != nil { + return newGenericError(err, SystemError) + } + + _, err = f.Write(data) + if err != nil { + f.Close() + return newGenericError(err, SystemError) + } + f.Close() + + fname := filepath.Join(c.root, stateFilename) + if err := os.Rename(fnew, fname); err != nil { + return newGenericError(err, SystemError) + } + + return nil +} + +func (c *linuxContainer) startInitProcess(config *ProcessConfig) error { + cmd := exec.Command(c.initArgs[0], append(c.initArgs[1:], config.Args...)...) + cmd.Stdin = config.Stdin + cmd.Stdout = config.Stdout + cmd.Stderr = config.Stderr + + cmd.Env = config.Env + cmd.Dir = c.config.RootFs + + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + + cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL + + //FIXME call namespaces.Exec() + if err := cmd.Start(); err != nil { + return err + } + + c.state.InitPid = cmd.Process.Pid + err := c.updateStateFile() + if err != nil { + return err + } + + return nil } func (c *linuxContainer) Destroy() error { - glog.Info("destroy container") - panic("not implemented") + state, err := c.RunState() + if err != nil { + return err + } + + if state != Destroyed { + return newGenericError(nil, ContainerNotStopped) + } + + os.RemoveAll(c.root) + return nil } func (c *linuxContainer) Pause() error { diff --git a/linux_factory.go b/linux_factory.go index 772c89ef5..30c63566a 100644 --- a/linux_factory.go +++ b/linux_factory.go @@ -23,23 +23,30 @@ var ( ) // New returns a linux based container factory based in the root directory. -func New(root string) (Factory, error) { - if err := os.MkdirAll(root, 0700); err != nil { - return nil, newGenericError(err, SystemError) +func New(root string, initArgs []string) (Factory, error) { + if root != "" { + if err := os.MkdirAll(root, 0700); err != nil { + return nil, newGenericError(err, SystemError) + } } return &linuxFactory{ - root: root, + root: root, + initArgs: initArgs, }, nil } // linuxFactory implements the default factory interface for linux based systems. type linuxFactory struct { // root is the root directory - root string + root string + initArgs []string } func (l *linuxFactory) Create(id string, config *Config) (Container, error) { + if l.root == "" { + return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) + } if !idRegex.MatchString(id) { return nil, newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat) } @@ -56,10 +63,43 @@ func (l *linuxFactory) Create(id string, config *Config) (Container, error) { return nil, newGenericError(err, SystemError) } - panic("not implemented") + data, err := json.MarshalIndent(config, "", "\t") + if err != nil { + return nil, newGenericError(err, SystemError) + } + + if err := os.MkdirAll(containerRoot, 0700); err != nil { + return nil, newGenericError(err, SystemError) + } + + f, err := os.Create(filepath.Join(containerRoot, configFilename)) + if err != nil { + os.RemoveAll(containerRoot) + return nil, newGenericError(err, SystemError) + } + defer f.Close() + + _, err = f.Write(data) + if err != nil { + os.RemoveAll(containerRoot) + return nil, newGenericError(err, SystemError) + } + + cgroupManager := NewCgroupManager() + return &linuxContainer{ + id: id, + root: containerRoot, + config: config, + initArgs: l.initArgs, + state: &State{}, + cgroupManager: cgroupManager, + }, nil } func (l *linuxFactory) Load(id string) (Container, error) { + if l.root == "" { + return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) + } containerRoot := filepath.Join(l.root, id) glog.Infof("loading container config from %s", containerRoot) config, err := l.loadContainerConfig(containerRoot) @@ -81,6 +121,7 @@ func (l *linuxFactory) Load(id string) (Container, error) { config: config, state: state, cgroupManager: cgroupManager, + initArgs: l.initArgs, }, nil } @@ -117,3 +158,11 @@ func (l *linuxFactory) loadContainerState(root string) (*State, error) { } return state, nil } + +// 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 (f *linuxFactory) StartInitialization(pipefd uintptr) (err error) { + + /* FIXME call namespaces.Init() */ + return nil +} diff --git a/linux_factory_test.go b/linux_factory_test.go index aa9a88482..168be1be7 100644 --- a/linux_factory_test.go +++ b/linux_factory_test.go @@ -28,7 +28,7 @@ func TestFactoryNew(t *testing.T) { } defer os.RemoveAll(root) - factory, err := New(root) + factory, err := New(root, nil) if err != nil { t.Fatal(err) } @@ -54,7 +54,7 @@ func TestFactoryLoadNotExists(t *testing.T) { } defer os.RemoveAll(root) - factory, err := New(root) + factory, err := New(root, nil) if err != nil { t.Fatal(err) } @@ -101,7 +101,7 @@ func TestFactoryLoadContainer(t *testing.T) { t.Fatal(err) } - factory, err := New(root) + factory, err := New(root, nil) if err != nil { t.Fatal(err) } diff --git a/nsinit/Makefile b/nsinit/Makefile new file mode 100644 index 000000000..57adf154d --- /dev/null +++ b/nsinit/Makefile @@ -0,0 +1,2 @@ +all: + go build -o nsinit . diff --git a/nsinit/exec.go b/nsinit/exec.go index 6fc553b8f..4c7d6316c 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -1,6 +1,7 @@ package main import ( + "crypto/md5" "fmt" "io" "log" @@ -17,6 +18,12 @@ import ( "github.com/docker/libcontainer/namespaces" ) +var ( + dataPath = os.Getenv("data_path") + console = os.Getenv("console") + rawPipeFd = os.Getenv("pipe") +) + var execCommand = cli.Command{ Name: "exec", Usage: "execute a new command inside a container", @@ -43,26 +50,59 @@ func execAction(context *cli.Context) { var exitCode int - container, err := loadConfig() + process := &libcontainer.ProcessConfig{ + Args: context.Args(), + Env: context.StringSlice("env"), + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + } + + factory, err := libcontainer.New(context.GlobalString("root"), []string{os.Args[0], "init", "--fd", "3", "--"}) if err != nil { log.Fatal(err) } - state, err := libcontainer.GetState(dataPath) + id := fmt.Sprintf("%x", md5.Sum([]byte(dataPath))) + container, err := factory.Load(id) if err != nil && !os.IsNotExist(err) { - log.Fatalf("unable to read state.json: %s", err) - } + var config *libcontainer.Config - if state != nil { - exitCode, err = startInExistingContainer(container, state, context.String("func"), context) - } else { - exitCode, err = startContainer(container, dataPath, []string(context.Args())) + config, err = loadConfig() + if err != nil { + log.Fatal(err) + } + container, err = factory.Create(id, config) + } + if err != nil { + log.Fatal(err) } + pid, err := container.StartProcess(process) if err != nil { log.Fatalf("failed to exec: %s", err) } + p, err := os.FindProcess(pid) + if err != nil { + log.Fatalf("Unable to find the %d process: %s", pid, err) + } + + ps, err := p.Wait() + if err != nil { + log.Fatalf("Unable to wait the %d process: %s", pid, err) + } + container.Destroy() + + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + log.Fatalf("Unexpected status") + } + os.Exit(exitCode) } diff --git a/nsinit/init.go b/nsinit/init.go index 6df9b1d89..853c9e8e9 100644 --- a/nsinit/init.go +++ b/nsinit/init.go @@ -1,47 +1,44 @@ package main import ( + "github.com/docker/libcontainer/system" "log" "os" - "runtime" - "strconv" "github.com/codegangsta/cli" - "github.com/docker/libcontainer/namespaces" + "github.com/docker/libcontainer" ) var ( - dataPath = os.Getenv("data_path") - console = os.Getenv("console") - rawPipeFd = os.Getenv("pipe") - initCommand = cli.Command{ Name: "init", Usage: "runs the init process inside the namespace", Action: initAction, + Flags: []cli.Flag{ + cli.IntFlag{"fd", 0, "internal pipe fd"}, + }, } ) func initAction(context *cli.Context) { - runtime.LockOSThread() - - container, err := loadConfig() + factory, err := libcontainer.New("", []string{}) if err != nil { log.Fatal(err) } - rootfs, err := os.Getwd() - if err != nil { - log.Fatal(err) + if context.Int("fd") == 0 { + log.Fatal("--fd must be specified for init process") } - pipeFd, err := strconv.Atoi(rawPipeFd) - if err != nil { + fd := uintptr(context.Int("fd")) + + if err := factory.StartInitialization(fd); err != nil { log.Fatal(err) } - pipe := os.NewFile(uintptr(pipeFd), "pipe") - if err := namespaces.Init(container, rootfs, console, pipe, []string(context.Args())); err != nil { - log.Fatalf("unable to initialize for container: %s", err) + args := []string(context.Args()) + + if err := system.Execv(args[0], args[0:], os.Environ()); err != nil { + log.Fatal(err) } } diff --git a/nsinit/stats.go b/nsinit/stats.go index 62fc1d4b2..6d8f75855 100644 --- a/nsinit/stats.go +++ b/nsinit/stats.go @@ -16,7 +16,7 @@ var statsCommand = cli.Command{ } func statsAction(context *cli.Context) { - factory, err := libcontainer.New(context.GlobalString("root")) + factory, err := libcontainer.New(context.GlobalString("root"), nil) if err != nil { log.Fatal(err) }