From 334345671759d521ee6fbe7f6e1e7392ee312ab4 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 21 Jun 2018 09:45:03 -0400 Subject: Add Refresh() to ctrs to refresh state after db change The Refresh() function is used to reset a container's state after a database format change to state is made that requires migration Signed-off-by: Matthew Heon Closes: #981 Approved by: baude --- libpod/boltdb_state.go | 12 ++--- libpod/container_api.go | 121 +++++++++++++++++++++++++++++++++++++------ libpod/container_internal.go | 43 +++++++++++++++ 3 files changed, 151 insertions(+), 25 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 7880265b6..3ee24122a 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -174,15 +174,9 @@ func (s *BoltState) Refresh() error { return errors.Wrapf(err, "error unmarshalling state for container %s", string(id)) } - state.PID = 0 - state.Mountpoint = "" - state.Mounted = false - state.State = ContainerStateConfigured - state.ExecSessions = make(map[string]*ExecSession) - state.IPs = nil - state.Interfaces = nil - state.Routes = nil - state.BindMounts = make(map[string]string) + if err := resetState(state); err != nil { + return errors.Wrapf(err, "error resetting state for container %s", string(id)) + } newStateBytes, err := json.Marshal(state) if err != nil { diff --git a/libpod/container_api.go b/libpod/container_api.go index 7613c8ea5..d181af2a8 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -477,15 +477,8 @@ func (c *Container) Pause() error { if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, can't pause", c.state.State) } - if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { - return err - } - - logrus.Debugf("Paused container %s", c.ID()) - c.state.State = ContainerStatePaused - - return c.save() + return c.pause() } // Unpause unpauses a container @@ -502,15 +495,8 @@ func (c *Container) Unpause() error { if c.state.State != ContainerStatePaused { return errors.Wrapf(ErrCtrStateInvalid, "%q is not paused, can't unpause", c.ID()) } - if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { - return err - } - - logrus.Debugf("Unpaused container %s", c.ID()) - - c.state.State = ContainerStateRunning - return c.save() + return c.unpause() } // Export exports a container's root filesystem as a tar archive @@ -753,3 +739,106 @@ func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err e return c.start() } + +// Refresh refreshes a container's state in the database, restarting the +// container if it is running +func (c *Container) Refresh(ctx context.Context) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + wasCreated := false + if c.state.State == ContainerStateCreated { + wasCreated = true + } + wasRunning := false + if c.state.State == ContainerStateRunning { + wasRunning = true + } + wasPaused := false + if c.state.State == ContainerStatePaused { + wasPaused = true + } + + // First, unpause the container if it's paused + if c.state.State == ContainerStatePaused { + if err := c.unpause(); err != nil { + return err + } + } + + // Next, if the container is running, stop it + if c.state.State == ContainerStateRunning { + if err := c.stop(c.config.StopTimeout); err != nil { + return err + } + } + + // If there are active exec sessions, we need to kill them + if len(c.state.ExecSessions) > 0 { + logrus.Infof("Killing %d exec sessions in container %s. They will not be restored after refresh.", + len(c.state.ExecSessions), c.ID()) + if err := c.runtime.ociRuntime.execStopContainer(c, c.config.StopTimeout); err != nil { + return err + } + } + + // If the container is in ContainerStateStopped, we need to delete it + // from the runtime and clear conmon state + if c.state.State == ContainerStateStopped { + if err := c.delete(ctx); err != nil { + return err + } + if err := c.removeConmonFiles(); err != nil { + return err + } + } + + // Fire cleanup code one more time unconditionally to ensure we are good + // to refresh + if err := c.cleanup(); err != nil { + return err + } + + // We've finished unwinding the container back to its initial state + // Now safe to refresh container state + if err := resetState(c.state); err != nil { + return errors.Wrapf(err, "error resetting state of container %s", c.ID()) + } + if err := c.refresh(); err != nil { + return err + } + + logrus.Debugf("Successfully refresh container %s state") + + // Initialize the container if it was created in runc + if wasCreated || wasRunning || wasPaused { + if err := c.prepare(); err != nil { + return err + } + if err := c.init(ctx); err != nil { + return err + } + } + + // If the container was running before, start it + if wasRunning || wasPaused { + if err := c.start(); err != nil { + return err + } + } + + // If the container was paused before, re-pause it + if wasPaused { + if err := c.pause(); err != nil { + return err + } + } + + return nil +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index f3be6f73b..fee13953c 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -316,6 +316,23 @@ func (c *Container) teardownStorage() error { return nil } +// Reset resets state fields to default values +// It is performed before a refresh and clears the state after a reboot +// It does not save the results - assumes the database will do that for us +func resetState(state *containerState) error { + state.PID = 0 + state.Mountpoint = "" + state.Mounted = false + state.State = ContainerStateConfigured + state.ExecSessions = make(map[string]*ExecSession) + state.IPs = nil + state.Interfaces = nil + state.Routes = nil + state.BindMounts = make(map[string]string) + + return nil +} + // Refresh refreshes the container's state after a restart func (c *Container) refresh() error { c.lock.Lock() @@ -681,6 +698,32 @@ func (c *Container) stop(timeout uint) error { return c.cleanup() } +// Internal, non-locking function to pause a container +func (c *Container) pause() error { + if err := c.runtime.ociRuntime.pauseContainer(c); err != nil { + return err + } + + logrus.Debugf("Paused container %s", c.ID()) + + c.state.State = ContainerStatePaused + + return c.save() +} + +// Internal, non-locking function to unpause a container +func (c *Container) unpause() error { + if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil { + return err + } + + logrus.Debugf("Unpaused container %s", c.ID()) + + c.state.State = ContainerStateRunning + + return c.save() +} + // mountStorage sets up the container's root filesystem // It mounts the image and any other requested mounts // TODO: Add ability to override mount label so we can use this for Mount() too -- cgit v1.2.3-54-g00ecf