Wire SQL backed state into rest of libpod

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
This commit is contained in:
Matthew Heon 2017-11-09 13:51:20 -05:00
parent cb56716fc4
commit 763e372649
7 changed files with 257 additions and 44 deletions

View File

@ -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)
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
}

View File

@ -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))

View File

@ -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 {

View File

@ -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()
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)