diff --git a/execdriver/driver.go b/execdriver/driver.go index 225f23cf2..d5b207366 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -1,10 +1,26 @@ package execdriver import ( + "errors" "io" "net" + "os/exec" + "sync" + "syscall" + "time" ) +var ( + ErrCommandIsNil = errors.New("Process's cmd is nil") +) + +type Driver interface { + Start(c *Process) error + Stop(c *Process) error + Kill(c *Process, sig int) error + Wait(c *Process, duration time.Duration) error +} + // Network settings of the container type Network struct { Gateway string @@ -13,12 +29,44 @@ type Network struct { Mtu int } +type State struct { + sync.RWMutex + running bool + pid int + exitCode int + startedAt time.Time + finishedAt time.Time +} + +func (s *State) IsRunning() bool { + s.RLock() + defer s.RUnlock() + return s.running +} + +func (s *State) SetRunning() error { + s.Lock() + defer s.Unlock() + s.running = true + return nil +} + +func (s *State) SetStopped(exitCode int) error { + s.Lock() + defer s.Unlock() + s.exitCode = exitCode + s.running = false + return nil +} + // Container / Process / Whatever, we can redefine the conatiner here // to be what it should be and not have to carry the baggage of the // container type in the core with backward compat. This is what a // driver needs to execute a process inside of a conatiner. This is what // a container is at it's core. -type Container struct { +type Process struct { + State State + Name string // unique name for the conatienr Privileged bool User string @@ -28,19 +76,45 @@ type Container struct { Args []string Environment map[string]string WorkingDir string + ConfigPath string Network *Network // if network is nil then networking is disabled Stdin io.Reader Stdout io.Writer Stderr io.Writer - Context interface{} + cmd *exec.Cmd } -// State can be handled internally in the drivers -type Driver interface { - Start(c *Container) error - Stop(c *Container) error - Kill(c *Container, sig int) error - Running(c *Container) (bool, error) - Wait(c *Container, seconds int) error +func (c *Process) SetCmd(cmd *exec.Cmd) error { + c.cmd = cmd + cmd.Stdout = c.Stdout + cmd.Stderr = c.Stderr + cmd.Stdin = c.Stdin + return nil +} + +func (c *Process) StdinPipe() (io.WriteCloser, error) { + return c.cmd.StdinPipe() +} + +func (c *Process) StderrPipe() (io.ReadCloser, error) { + return c.cmd.StderrPipe() +} + +func (c *Process) StdoutPipe() (io.ReadCloser, error) { + return c.cmd.StdoutPipe() +} + +func (c *Process) GetExitCode() int { + if c.cmd != nil { + return c.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + } + return -1 +} + +func (c *Process) Wait() error { + if c.cmd != nil { + return c.cmd.Wait() + } + return ErrCommandIsNil } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index c14657197..61223ce8b 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -1,47 +1,58 @@ package lxc import ( + "errors" + "fmt" "github.com/dotcloud/docker/execdriver" "os/exec" "strconv" - "sync" + "strings" "syscall" + "time" ) const ( startPath = "lxc-start" ) +var ( + ErrNotRunning = errors.New("Process could not be started") + ErrWaitTimeoutReached = errors.New("Wait timeout reached") +) + +func init() { + // Register driver +} + type driver struct { - containerLock map[string]*sync.Mutex + root string // root path for the driver to use + containers map[string]*execdriver.Process } -func NewDriver() (execdriver.Driver, error) { +func NewDriver(root string) (execdriver.Driver, error) { // setup unconfined symlink + return &driver{ + root: root, + containers: make(map[string]*execdriver.Process), + }, nil } -func (d *driver) Start(c *execdriver.Container) error { - l := d.getLock(c) - l.Lock() - defer l.Unlock() - - running, err := d.running(c) - if err != nil { - return err - } - if running { +func (d *driver) Start(c *execdriver.Process) error { + if c.State.IsRunning() { return nil } - configPath, err := d.generateConfig(c) - if err != nil { - return err - } + /* + configPath, err := d.generateConfig(c) + if err != nil { + return err + } + */ params := []string{ startPath, "-n", c.Name, - "-f", configPath, + "-f", c.ConfigPath, "--", c.InitPath, } @@ -49,8 +60,8 @@ func (d *driver) Start(c *execdriver.Container) error { if c.Network != nil { params = append(params, "-g", c.Network.Gateway, - "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network, IPPrefixLen), - "-mtu", c.Network.Mtu, + "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), + "-mtu", strconv.Itoa(c.Network.Mtu), ) } @@ -72,9 +83,9 @@ func (d *driver) Start(c *execdriver.Container) error { cmd := exec.Command(params[0], params[1:]...) cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - cmd.Stdout = c.Stdout - cmd.Stderr = c.Stderr - cmd.Stdin = c.Stdin + if err := c.SetCmd(cmd); err != nil { + return err + } if err := cmd.Start(); err != nil { return err @@ -87,58 +98,135 @@ func (d *driver) Start(c *execdriver.Container) error { return nil } -func (d *driver) Stop(c *execdriver.Container) error { - l := d.getLock(c) - l.Lock() - defer l.Unlock() - +func (d *driver) Stop(c *execdriver.Process) error { if err := d.kill(c, 15); err != nil { if err := d.kill(c, 9); err != nil { return err } } - if err := d.wait(c, 10); err != nil { - return d.kill(c, 9) + if err := d.wait(c, 10*time.Second); err != nil { + if err := d.kill(c, 9); err != nil { + return err + } + } + exitCode := c.GetExitCode() + if err := c.State.SetStopped(exitCode); err != nil { + return err } return nil } -func (d *driver) Wait(c *execdriver.Container, seconds int) error { - l := d.getLock(c) - l.Lock() - defer l.Unlock() +func (d *driver) Kill(c *execdriver.Process, sig int) error { + c.State.Lock() + defer c.State.Unlock() + return d.kill(c, sig) +} - return d.wait(c, seconds) +func (d *driver) Wait(c *execdriver.Process, duration time.Duration) error { + return d.wait(c, duration) } // If seconds < 0 then wait forever -func (d *driver) wait(c *execdriver.Container, seconds int) error { - +func (d *driver) wait(c *execdriver.Process, duration time.Duration) error { +begin: + var ( + killer bool + done = d.waitCmd(c) + ) + if duration > 0 { + select { + case err := <-done: + if err != nil && err == execdriver.ErrCommandIsNil { + done = d.waitLxc(c, &killer) + goto begin + } + return err + case <-time.After(duration): + killer = true + return ErrWaitTimeoutReached + } + } else { + if err := <-done; err != nil { + if err == execdriver.ErrCommandIsNil { + done = d.waitLxc(c, &killer) + goto begin + } + return err + } + } + return nil } -func (d *driver) kill(c *execdriver.Container, sig int) error { +func (d *driver) kill(c *execdriver.Process, sig int) error { return exec.Command("lxc-kill", "-n", c.Name, strconv.Itoa(sig)).Run() } -func (d *driver) running(c *execdriver.Container) (bool, error) { - -} - -// Generate the lxc configuration and return the path to the file -func (d *driver) generateConfig(c *execdriver.Container) (string, error) { - -} - -func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Container) error { - -} - -func (d *driver) getLock(c *execdriver.Container) *sync.Mutex { - l, ok := d.containerLock[c.Name] - if !ok { - l = &sync.Mutex{} - d.containerLock[c.Name] = l +/* Generate the lxc configuration and return the path to the file +func (d *driver) generateConfig(c *execdriver.Process) (string, error) { + p := path.Join(d.root, c.Name) + f, err := os.Create(p) + if err != nil { + return "", nil } - return l + defer f.Close() + + if err := LxcTemplateCompiled.Execute(f, c.Context); err != nil { + return "", err + } + return p, nil +} +*/ + +func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Process) error { + // We wait for the container to be fully running. + // Timeout after 5 seconds. In case of broken pipe, just retry. + // Note: The container can run and finish correctly before + // the end of this loop + for now := time.Now(); time.Since(now) < 5*time.Second; { + // If the container dies while waiting for it, just return + if !c.State.IsRunning() { + return nil + } + output, err := exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() + if err != nil { + output, err = exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() + if err != nil { + return err + } + + } + if strings.Contains(string(output), "RUNNING") { + return nil + } + time.Sleep(50 * time.Millisecond) + } + return ErrNotRunning +} + +func (d *driver) waitCmd(c *execdriver.Process) <-chan error { + done := make(chan error) + go func() { + done <- c.Wait() + }() + return done +} + +func (d *driver) waitLxc(c *execdriver.Process, kill *bool) <-chan error { + done := make(chan error) + go func() { + for *kill { + output, err := exec.Command("lxc-info", "-n", c.Name).CombinedOutput() + if err != nil { + done <- err + return + } + if !strings.Contains(string(output), "RUNNING") { + done <- err + return + } + time.Sleep(500 * time.Millisecond) + } + }() + return done }