summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@gmail.com>2017-11-09 13:51:20 -0500
committerMatthew Heon <matthew.heon@gmail.com>2017-11-18 12:58:48 -0500
commit763e3726493c122d9722e0e294634ae8d78c426b (patch)
treea2b34450ab0dfe2131786315f393a1f7ca05ffdd
parentcb56716fc4bb632db84fab372fff8f5a9007ed3f (diff)
downloadpodman-763e3726493c122d9722e0e294634ae8d78c426b.tar.gz
podman-763e3726493c122d9722e0e294634ae8d78c426b.tar.bz2
podman-763e3726493c122d9722e0e294634ae8d78c426b.zip
Wire SQL backed state into rest of libpod
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
-rw-r--r--libpod/container.go63
-rw-r--r--libpod/in_memory_state.go15
-rw-r--r--libpod/options.go14
-rw-r--r--libpod/runtime.go45
-rw-r--r--libpod/runtime_ctr.go20
-rw-r--r--libpod/sql_state.go140
-rw-r--r--libpod/state.go4
7 files changed, 257 insertions, 44 deletions
diff --git a/libpod/container.go b/libpod/container.go
index 097455112..c8a09fe9e 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -150,10 +150,9 @@ func (c *Container) State() (ContainerState, error) {
c.lock.Lock()
defer c.lock.Unlock()
- // TODO uncomment when working
- // if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil {
- // return ContainerStateUnknown, err
- // }
+ if err := c.runtime.state.UpdateContainer(c); err != nil {
+ return ContainerStateUnknown, errors.Wrapf(err, "error updating container %s state", c.ID())
+ }
return c.state.State, nil
}
@@ -252,6 +251,10 @@ func (c *Container) Create() (err error) {
c.lock.Lock()
defer c.lock.Unlock()
+ if err := c.runtime.state.UpdateContainer(c); err != nil {
+ return errors.Wrapf(err, "error updating container %s state", c.ID())
+ }
+
if !c.valid {
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
}
@@ -308,9 +311,12 @@ func (c *Container) Create() (err error) {
logrus.Debugf("Created container %s in runc", c.ID())
- // TODO should flush this state to disk here
c.state.State = ContainerStateCreated
+ if err := c.runtime.state.SaveContainer(c); err != nil {
+ return errors.Wrapf(err, "error saving container %s state", c.ID())
+ }
+
return nil
}
@@ -319,6 +325,10 @@ func (c *Container) Start() error {
c.lock.Lock()
defer c.lock.Unlock()
+ if err := c.runtime.state.UpdateContainer(c); err != nil {
+ return errors.Wrapf(err, "error updating container %s state", c.ID())
+ }
+
if !c.valid {
return ErrCtrRemoved
}
@@ -334,10 +344,13 @@ func (c *Container) Start() error {
logrus.Debugf("Started container %s", c.ID())
- // TODO should flush state to disk here
c.state.StartedTime = time.Now()
c.state.State = ContainerStateRunning
+ if err := c.runtime.state.SaveContainer(c); err != nil {
+ return errors.Wrapf(err, "error saving container %s state", c.ID())
+ }
+
return nil
}
@@ -360,6 +373,25 @@ func (c *Container) Exec(cmd []string, tty bool, stdin bool) (string, error) {
// Attach attaches to a container
// Returns fully qualified URL of streaming server for the container
func (c *Container) Attach(noStdin bool, keys string, attached chan<- bool) error {
+ if err := c.runtime.state.UpdateContainer(c); err != nil {
+ return errors.Wrapf(err, "error updating container %s state", c.ID())
+ }
+
+ if !c.valid {
+ return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
+ }
+
+ if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused {
+ return errors.Wrapf(ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID())
+ }
+
+ // TODO is it valid to attach to a frozen container?
+ if c.state.State == ContainerStateUnknown ||
+ c.state.State == ContainerStateConfigured ||
+ c.state.State == ContainerStatePaused {
+ return errors.Wrapf(ErrCtrStateInvalid, "can only attach to created, running, or stopped containers")
+ }
+
// Check the validity of the provided keys first
var err error
detachKeys := []byte{}
@@ -369,25 +401,12 @@ func (c *Container) Attach(noStdin bool, keys string, attached chan<- bool) erro
return errors.Wrapf(err, "invalid detach keys")
}
}
- cStatus := c.state.State
- if !(cStatus == ContainerStateRunning || cStatus == ContainerStateCreated) {
- return errors.Errorf("%s is not created or running", c.Name())
- }
resize := make(chan remotecommand.TerminalSize)
defer close(resize)
- err = c.attachContainerSocket(resize, noStdin, detachKeys, attached)
+ err = c.attachContainerSocket(resize, noStdin, detachKeys, attached)
return err
-
- // TODO
- // Re-enable this when mheon is done wth it
- //if err != nil {
- // return err
- //}
- //c.ContainerStateToDisk(c)
-
- //return err
}
// Mount mounts a container's filesystem on the host
@@ -414,10 +433,6 @@ func (c *Container) Export(path string) error {
// Commit commits the changes between a container and its image, creating a new
// image
-// If the container was not created from an image (for example,
-// WithRootFSFromPath will create a container from a directory on the system),
-// a new base image will be created from the contents of the container's
-// filesystem
func (c *Container) Commit() (*storage.Image, error) {
return nil, ErrNotImplemented
}
diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go
index 87caac808..5d03e62e6 100644
--- a/libpod/in_memory_state.go
+++ b/libpod/in_memory_state.go
@@ -153,6 +153,21 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
return nil
}
+// UpdateContainer updates a container's state
+// As all state is in-memory, no update will be required
+// As such this is a no-op
+func (s *InMemoryState) UpdateContainer(ctr *Container) error {
+ return nil
+}
+
+// SaveContainer saves a container's state
+// As all state is in-memory, any changes are always reflected as soon as they
+// are made
+// As such this is a no-op
+func (s *InMemoryState) SaveContainer(ctr *Container) error {
+ return nil
+}
+
// AllContainers retrieves all containers from the state
func (s *InMemoryState) AllContainers() ([]*Container, error) {
ctrs := make([]*Container, 0, len(s.containers))
diff --git a/libpod/options.go b/libpod/options.go
index fb64c0c41..4c21a70c9 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -94,6 +94,20 @@ func WithSignaturePolicy(path string) RuntimeOption {
}
}
+// WithInMemoryState specifies that the runtime will be backed by an in-memory
+// state only, and state will not persist after the runtime is shut down
+func WithInMemoryState() RuntimeOption {
+ return func(rt *Runtime) error {
+ if rt.valid {
+ return ErrRuntimeFinalized
+ }
+
+ rt.config.InMemoryState = true
+
+ return nil
+ }
+}
+
// WithOCIRuntime specifies an OCI runtime to use for running containers
func WithOCIRuntime(runtimePath string) RuntimeOption {
return func(rt *Runtime) error {
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 80202c567..1bfb79947 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -2,6 +2,7 @@ package libpod
import (
"os"
+ "path/filepath"
"sync"
is "github.com/containers/image/storage"
@@ -35,6 +36,7 @@ type RuntimeConfig struct {
InsecureRegistries []string
Registries []string
SignaturePolicyPath string
+ InMemoryState bool
RuntimePath string
ConmonPath string
ConmonEnvVars []string
@@ -52,6 +54,7 @@ var (
// Leave this empty so containers/storage will use its defaults
StorageConfig: storage.StoreOptions{},
ImageDefaultTransport: "docker://",
+ InMemoryState: false,
RuntimePath: "/usr/bin/runc",
ConmonPath: "/usr/local/libexec/crio/conmon",
ConmonEnvVars: []string{
@@ -114,13 +117,6 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
SignaturePolicyPath: runtime.config.SignaturePolicyPath,
}
- // Set up the state
- state, err := NewInMemoryState()
- if err != nil {
- return nil, err
- }
- runtime.state = state
-
// Make an OCI runtime to perform container operations
ociRuntime, err := newOCIRuntime("runc", runtime.config.RuntimePath,
runtime.config.ConmonPath, runtime.config.ConmonEnvVars,
@@ -149,6 +145,34 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
}
}
+ // Set up the state
+ if runtime.config.InMemoryState {
+ state, err := NewInMemoryState()
+ if err != nil {
+ return nil, err
+ }
+ runtime.state = state
+ } else {
+ dbPath := filepath.Join(runtime.config.StaticDir, "state.sql")
+ lockPath := filepath.Join(runtime.config.TmpDir, "state.lck")
+ specsDir := filepath.Join(runtime.config.StaticDir, "ocispec")
+
+ // Make a directory to hold JSON versions of container OCI specs
+ if err := os.MkdirAll(specsDir, 0755); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ return nil, errors.Wrapf(err, "error creating runtime OCI specs directory %s",
+ specsDir)
+ }
+ }
+
+ state, err := NewSQLState(dbPath, lockPath, specsDir, runtime)
+ if err != nil {
+ return nil, err
+ }
+ runtime.state = state
+ }
+
// Mark the runtime as valid - ready to be used, cannot be modified
// further
runtime.valid = true
@@ -188,5 +212,10 @@ func (r *Runtime) Shutdown(force bool) error {
r.valid = false
_, err := r.store.Shutdown(force)
- return err
+ if err != nil {
+ return err
+ }
+
+ // TODO: Should always call this even if store.Shutdown failed
+ return r.state.Close()
}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index b23c65287..a8a5b789d 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -95,17 +95,21 @@ func (r *Runtime) RemoveContainer(c *Container, force bool) error {
return ErrCtrRemoved
}
- // TODO check container status and unmount storage
- // TODO check that no other containers depend on this container's
- // namespaces
- status, err := c.State()
- if err != nil {
+ // Update the container to get current state
+ if err := r.state.UpdateContainer(c); err != nil {
return err
}
- // A container cannot be removed if it is running
- if status == ContainerStateRunning {
- return errors.Wrapf(ErrCtrStateInvalid, "cannot remove container %s as it is running", c.ID())
+ // Check that the container's in a good state to be removed
+ if !(c.state.State == ContainerStateConfigured ||
+ c.state.State == ContainerStateCreated ||
+ c.state.State == ContainerStateStopped) {
+ return errors.Wrapf(ErrCtrStateInvalid, "cannot remove container %s as it is running or paused", c.ID())
+ }
+
+ // Stop the container's storage
+ if err := c.teardownStorage(); err != nil {
+ return err
}
if err := r.state.RemoveContainer(c); err != nil {
diff --git a/libpod/sql_state.go b/libpod/sql_state.go
index 1218bfa79..4754bf71e 100644
--- a/libpod/sql_state.go
+++ b/libpod/sql_state.go
@@ -23,7 +23,7 @@ type SQLState struct {
valid bool
}
-// NewSqlState initializes a SQL-backed state, created the database if necessary
+// NewSQLState initializes a SQL-backed state, created the database if necessary
func NewSQLState(dbPath, lockPath, specsDir string, runtime *Runtime) (State, error) {
state := new(SQLState)
@@ -295,6 +295,141 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
return nil
}
+// UpdateContainer updates a container's state from the database
+func (s *SQLState) UpdateContainer(ctr *Container) error {
+ const query = `SELECT State,
+ ConfigPath,
+ RunDir,
+ Mountpoint,
+ StartedTime,
+ FinishedTime,
+ ExitCode
+ FROM containerState WHERE ID=?;`
+
+ var (
+ state int
+ configPath string
+ runDir string
+ mountpoint string
+ startedTimeString string
+ finishedTimeString string
+ exitCode int32
+ )
+
+ if !s.valid {
+ return ErrDBClosed
+ }
+
+ if !ctr.valid {
+ return ErrCtrRemoved
+ }
+
+ row := s.db.QueryRow(query, ctr.ID())
+ err := row.Scan(
+ &state,
+ &configPath,
+ &runDir,
+ &mountpoint,
+ &startedTimeString,
+ &finishedTimeString,
+ &exitCode)
+ if err != nil {
+ // The container may not exist in the database
+ if err == sql.ErrNoRows {
+ // Assume that the container was removed by another process
+ // As such make it invalid
+ ctr.valid = false
+
+ return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in database", ctr.ID())
+ }
+
+ return errors.Wrapf(err, "error parsing database state for container %s", ctr.ID())
+ }
+
+ newState := new(containerRuntimeInfo)
+ newState.State = ContainerState(state)
+ newState.ConfigPath = configPath
+ newState.RunDir = runDir
+ newState.Mountpoint = mountpoint
+ newState.ExitCode = exitCode
+
+ if newState.Mountpoint != "" {
+ newState.Mounted = true
+ }
+
+ startedTime, err := timeFromSQL(startedTimeString)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing container %s started time", ctr.ID())
+ }
+ newState.StartedTime = startedTime
+
+ finishedTime, err := timeFromSQL(finishedTimeString)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing container %s finished time", ctr.ID())
+ }
+ newState.FinishedTime = finishedTime
+
+ // New state compiled successfully, swap it into the current state
+ ctr.state = newState
+
+ return nil
+}
+
+// SaveContainer updates a container's state in the database
+func (s *SQLState) SaveContainer(ctr *Container) error {
+ const update = `UPDATE containerState SET
+ State=?,
+ ConfigPath=?,
+ RunDir=?,
+ Mountpoint=?,
+ StartedTime=?,
+ FinishedTime=?,
+ ExitCode=?
+ WHERE Id=?;`
+
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ if !s.valid {
+ return ErrDBClosed
+ }
+
+ if !ctr.valid {
+ return ErrCtrRemoved
+ }
+
+ tx, err := s.db.Begin()
+ if err != nil {
+ return errors.Wrapf(err, "error beginning database transaction")
+ }
+ defer func() {
+ if err != nil {
+ if err2 := tx.Rollback(); err2 != nil {
+ logrus.Errorf("Error rolling back transaction to add container %s: %v", ctr.ID(), err2)
+ }
+ }
+ }()
+
+ // Add container state to the database
+ _, err = tx.Exec(update,
+ ctr.state.State,
+ ctr.state.ConfigPath,
+ ctr.state.RunDir,
+ ctr.state.Mountpoint,
+ timeToSQL(ctr.state.StartedTime),
+ timeToSQL(ctr.state.FinishedTime),
+ ctr.state.ExitCode)
+ if err != nil {
+ return errors.Wrapf(err, "error updating container %s state in database", ctr.ID())
+ }
+
+ if err := tx.Commit(); err != nil {
+ return errors.Wrapf(err, "error committing transaction to update container %s", ctr.ID())
+ }
+
+ return nil
+}
+
// RemoveContainer removes the container from the state
func (s *SQLState) RemoveContainer(ctr *Container) error {
const (
@@ -305,9 +440,6 @@ func (s *SQLState) RemoveContainer(ctr *Container) error {
s.lock.Lock()
defer s.lock.Unlock()
- ctr.lock.Lock()
- defer ctr.lock.Unlock()
-
if !s.valid {
return ErrDBClosed
}
diff --git a/libpod/state.go b/libpod/state.go
index 41f44ac8e..4093f14f1 100644
--- a/libpod/state.go
+++ b/libpod/state.go
@@ -21,6 +21,10 @@ type State interface {
// The container will only be removed from the state, not from the pod
// which the container belongs to
RemoveContainer(ctr *Container) error
+ // UpdateContainer updates a container's state from the backing store
+ UpdateContainer(ctr *Container) error
+ // SaveContainer saves a container's current state to the backing store
+ SaveContainer(ctr *Container) error
// Retrieves all containers presently in state
AllContainers() ([]*Container, error)