Implement container restarting

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>

Closes: #482
Approved by: baude
This commit is contained in:
Matthew Heon
2018-03-13 11:49:24 -04:00
committed by Atomic Bot
parent ff091cf731
commit 02a26c2934
5 changed files with 101 additions and 28 deletions

View File

@ -104,8 +104,18 @@ func startCmd(c *cli.Context) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to parse annotations in %s", ctr.ID()) return errors.Wrapf(err, "unable to parse annotations in %s", ctr.ID())
} }
err = ctr.Start()
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "unable to start %s", container)
continue
}
// We only get a terminal session if both a tty was specified in the spec and // We only get a terminal session if both a tty was specified in the spec and
// -a on the command-line was given. // -a on the command-line was given.
// Must be done after Start() because we might be restarting
// If so, the attach socket might be removed & recreated
if attach && tty { if attach && tty {
// We increment the wg counter because we need to do the attach // We increment the wg counter because we need to do the attach
wg.Add(1) wg.Add(1)
@ -121,14 +131,6 @@ func startCmd(c *cli.Context) error {
return errors.Errorf("unable to attach to container %s", ctr.ID()) return errors.Errorf("unable to attach to container %s", ctr.ID())
} }
} }
err = ctr.Start()
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "unable to start %s", container)
continue
}
if !attach { if !attach {
fmt.Println(ctr.ID()) fmt.Println(ctr.ID())
} }
@ -141,7 +143,11 @@ func startCmd(c *cli.Context) error {
if lastError != nil { if lastError != nil {
fmt.Fprintln(os.Stderr, lastError) fmt.Fprintln(os.Stderr, lastError)
} }
lastError = ctr.Cleanup() // We can only do this if we attached
// Otherwise the container is probably still running
if attach && tty {
lastError = ctr.Cleanup()
}
} }
return lastError return lastError
} }

View File

@ -106,8 +106,6 @@ type Container struct {
runtime *Runtime runtime *Runtime
} }
// TODO fetch IP and Subnet Mask from networks once we have updated OCICNI
// containerState contains the current state of the container // containerState contains the current state of the container
// It is stored on disk in a tmpfs and recreated on reboot // It is stored on disk in a tmpfs and recreated on reboot
type containerState struct { type containerState struct {

View File

@ -63,6 +63,9 @@ func (c *Container) Init() (err error) {
} }
// Start starts a container // Start starts a container
// Start can start created or stopped containers
// Stopped containers will be deleted and re-created in runc, undergoing a fresh
// Init()
func (c *Container) Start() (err error) { func (c *Container) Start() (err error) {
if !c.locked { if !c.locked {
c.lock.Lock() c.lock.Lock()
@ -78,11 +81,6 @@ func (c *Container) Start() (err error) {
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID()) return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID())
} }
// TODO remove this when we patch conmon to support restarting containers
if c.state.State == ContainerStateStopped {
return errors.Wrapf(ErrNotImplemented, "restarting a stopped container is not yet supported")
}
// Mount storage for the container if necessary // Mount storage for the container if necessary
if err := c.mountStorage(); err != nil { if err := c.mountStorage(); err != nil {
return err return err
@ -109,6 +107,34 @@ func (c *Container) Start() (err error) {
} }
}() }()
// Reinitialize the container if we need to
if c.state.State == ContainerStateStopped {
// If necessary, delete attach and ctl files
if err := c.removeConmonFiles(); err != nil {
return err
}
// Delete the container in the runtime
if err := c.runtime.ociRuntime.deleteContainer(c); err != nil {
return errors.Wrapf(err, "error removing container %s from runtime", c.ID())
}
// Our state is now Configured, as we've removed ourself from
// the runtime
// Set and save now to make sure that, if the init() below fails
// we still have a valid state
c.state.State = ContainerStateConfigured
if err := c.save(); err != nil {
return err
}
// Reinitialize the container
if err := c.init(); err != nil {
return err
}
}
// Start the container
return c.start() return c.start()
} }

View File

@ -255,6 +255,38 @@ func (c *Container) refresh() error {
return errors.Wrapf(err, "error refreshing state for container %s", c.ID()) return errors.Wrapf(err, "error refreshing state for container %s", c.ID())
} }
// Remove ctl and attach files, which may persist across reboot
if err := c.removeConmonFiles(); err != nil {
return err
}
return nil
}
// Remove conmon attach socket and terminal resize FIFO
// This is necessary for restarting containers
func (c *Container) removeConmonFiles() error {
// Files are allowed to not exist, so ignore ENOENT
attachFile := filepath.Join(c.bundlePath(), "attach")
if err := os.Remove(attachFile); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "error removing container %s attach file", c.ID())
}
ctlFile := filepath.Join(c.bundlePath(), "ctl")
if err := os.Remove(ctlFile); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "error removing container %s ctl file", c.ID())
}
oomFile := filepath.Join(c.bundlePath(), "oom")
if err := os.Remove(oomFile); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "error removing container %s OOM file", c.ID())
}
exitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, c.ID())
if err := os.Remove(exitFile); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "error removing container %s exit file", c.ID())
}
return nil return nil
} }
@ -360,10 +392,6 @@ func (c *Container) initAndStart() (err error) {
if c.state.State == ContainerStatePaused { if c.state.State == ContainerStatePaused {
return errors.Wrapf(ErrCtrStateInvalid, "cannot start paused container %s", c.ID()) return errors.Wrapf(ErrCtrStateInvalid, "cannot start paused container %s", c.ID())
} }
// TODO remove this once we can restart containers
if c.state.State == ContainerStateStopped {
return errors.Wrapf(ErrNotImplemented, "restarting containers is not yet implemented")
}
// Mount if necessary // Mount if necessary
if err := c.mountStorage(); err != nil { if err := c.mountStorage(); err != nil {
@ -391,6 +419,29 @@ func (c *Container) initAndStart() (err error) {
} }
}() }()
// If we are ContainerStateStopped we need to remove from runtime
// And reset to ContainerStateConfigured
if c.state.State == ContainerStateStopped {
// If necessary, delete attach and ctl files
if err := c.removeConmonFiles(); err != nil {
return err
}
// Delete the container in the runtime
if err := c.runtime.ociRuntime.deleteContainer(c); err != nil {
return errors.Wrapf(err, "error removing container %s from runtime", c.ID())
}
// Our state is now Configured, as we've removed ourself from
// the runtime
// Set and save now to make sure that, if the init() below fails
// we still have a valid state
c.state.State = ContainerStateConfigured
if err := c.save(); err != nil {
return err
}
}
// If we are ContainerStateConfigured we need to init() // If we are ContainerStateConfigured we need to init()
if c.state.State == ContainerStateConfigured { if c.state.State == ContainerStateConfigured {
if err := c.init(); err != nil { if err := c.init(); err != nil {

View File

@ -130,14 +130,6 @@ func (p *Pod) Start() (map[string]error, error) {
for len(ctrsToStart) > 0 { for len(ctrsToStart) > 0 {
// Loop through all containers, attempting to start them // Loop through all containers, attempting to start them
for id, ctr := range ctrsToStart { for id, ctr := range ctrsToStart {
// TODO remove this when we support restarting containers
if ctr.state.State == ContainerStateStopped {
ctrErrors[id] = errors.Wrapf(ErrNotImplemented, "starting stopped containers is not yet supported")
delete(ctrsToStart, id)
continue
}
// TODO should we only do a dependencies check if we are not ContainerStateCreated? // TODO should we only do a dependencies check if we are not ContainerStateCreated?
depsOK := true depsOK := true
var depErr error var depErr error