Wire SQL backed state into rest of libpod
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
This commit is contained in:
parent
cb56716fc4
commit
763e372649
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue