2017-12-14 03:02:15 +00:00
|
|
|
package libpod
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/boltdb/bolt"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// BoltState is a state implementation backed by a Bolt DB
|
|
|
|
type BoltState struct {
|
|
|
|
valid bool
|
|
|
|
dbPath string
|
|
|
|
lockDir string
|
|
|
|
runtime *Runtime
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewBoltState creates a new bolt-backed state database
|
|
|
|
func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) {
|
|
|
|
state := new(BoltState)
|
|
|
|
state.dbPath = path
|
|
|
|
state.lockDir = lockDir
|
|
|
|
state.runtime = runtime
|
|
|
|
|
|
|
|
// Make the directory that will hold container lockfiles
|
|
|
|
if err := os.MkdirAll(lockDir, 0750); err != nil {
|
|
|
|
// The directory is allowed to exist
|
|
|
|
if !os.IsExist(err) {
|
|
|
|
return nil, errors.Wrapf(err, "error creating lockfiles dir %s", lockDir)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.lockDir = lockDir
|
|
|
|
|
|
|
|
db, err := bolt.Open(path, 0600, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error opening database %s", path)
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
// Perform initial database setup
|
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
|
|
_, err = tx.CreateBucketIfNotExists(idRegistryBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating id-registry bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(nameRegistryBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating name-registry bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(ctrConfigBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating container-config bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(ctrStateBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating container-state bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(netNSBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating net-ns bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(runtimeConfigBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating runtime-config bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(ctrDependsBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating container-depends bucket")
|
|
|
|
}
|
2018-02-08 18:55:02 +00:00
|
|
|
_, err = tx.CreateBucketIfNotExists(podBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating pod bucket")
|
|
|
|
}
|
|
|
|
_, err = tx.CreateBucketIfNotExists(podContainersBkt)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error creating pod-containers bucket")
|
|
|
|
}
|
2017-12-14 03:02:15 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error creating initial database layout")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check runtime configuration
|
|
|
|
if err := checkRuntimeConfig(db, runtime); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
state.valid = true
|
|
|
|
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the state and prevents further use
|
|
|
|
func (s *BoltState) Close() error {
|
|
|
|
s.valid = false
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh clears container and pod states after a reboot
|
|
|
|
func (s *BoltState) Refresh() error {
|
|
|
|
if !s.valid {
|
|
|
|
return ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
|
|
idBucket, err := getIDBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through all IDs. Check if they are containers.
|
|
|
|
// If they are, unmarshal their state, and then clear
|
|
|
|
// PID, mountpoint, and state for all of them
|
|
|
|
// Then save the modified state
|
|
|
|
// Also clear all network namespaces
|
|
|
|
err = idBucket.ForEach(func(id, name []byte) error {
|
|
|
|
if err := netNSBucket.Delete(id); err != nil {
|
|
|
|
return errors.Wrapf(err, "error removing network namespace ID %s", string(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
stateBytes := ctrStateBucket.Get(id)
|
|
|
|
if stateBytes == nil {
|
|
|
|
// This is a pod, not a container
|
|
|
|
// Nothing to do
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
state := new(containerState)
|
|
|
|
|
|
|
|
if err := json.Unmarshal(stateBytes, state); err != nil {
|
|
|
|
return errors.Wrapf(err, "error unmarshalling state for container %s", string(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
state.PID = 0
|
|
|
|
state.Mountpoint = ""
|
|
|
|
state.Mounted = false
|
|
|
|
state.State = ContainerStateConfigured
|
|
|
|
|
|
|
|
newStateBytes, err := json.Marshal(state)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error marshalling modified state for container %s", string(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ctrStateBucket.Put(id, newStateBytes); err != nil {
|
|
|
|
return errors.Wrapf(err, "error updating state for container %s in DB", string(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Container retrieves a single container from the state by its full ID
|
|
|
|
func (s *BoltState) Container(id string) (*Container, error) {
|
|
|
|
if id == "" {
|
|
|
|
return nil, ErrEmptyID
|
|
|
|
}
|
|
|
|
|
|
|
|
if !s.valid {
|
|
|
|
return nil, ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrID := []byte(id)
|
|
|
|
|
|
|
|
ctr := new(Container)
|
|
|
|
ctr.config = new(ContainerConfig)
|
|
|
|
ctr.state = new(containerState)
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
ctrConfigBucket, err := getCtrConfigBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = s.getContainerFromDB(ctrID, ctr, ctrConfigBucket,
|
|
|
|
ctrStateBucket, netNSBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupContainer retrieves a container from the state by full or unique
|
|
|
|
// partial ID or name
|
|
|
|
func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
|
|
|
|
if idOrName == "" {
|
|
|
|
return nil, ErrEmptyID
|
|
|
|
}
|
|
|
|
|
|
|
|
if !s.valid {
|
|
|
|
return nil, ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
ctr := new(Container)
|
|
|
|
ctr.config = new(ContainerConfig)
|
|
|
|
ctr.state = new(containerState)
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
idBucket, err := getIDBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrConfigBucket, err := getCtrConfigBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// First, check if the ID given was the actual container ID
|
|
|
|
// Query against state because it's a lot smaller than config
|
|
|
|
var id []byte
|
|
|
|
stateExists := ctrStateBucket.Get([]byte(idOrName))
|
|
|
|
if stateExists != nil {
|
|
|
|
// A full container ID was given
|
|
|
|
id = []byte(idOrName)
|
|
|
|
} else {
|
|
|
|
// They did not give us a full container ID.
|
|
|
|
// Search for partial ID or full name matches
|
|
|
|
// Use else-if in case the name is set to a partial ID
|
|
|
|
exists := false
|
|
|
|
err = idBucket.ForEach(func(checkID, checkName []byte) error {
|
|
|
|
if string(checkName) == idOrName {
|
|
|
|
if exists {
|
|
|
|
return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
|
|
|
|
}
|
|
|
|
id = checkID
|
|
|
|
exists = true
|
|
|
|
} else if strings.HasPrefix(string(checkID), idOrName) {
|
|
|
|
if exists {
|
|
|
|
return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
|
|
|
|
}
|
|
|
|
id = checkID
|
|
|
|
exists = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if !exists {
|
|
|
|
return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = s.getContainerFromDB(id, ctr, ctrConfigBucket,
|
|
|
|
ctrStateBucket, netNSBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasContainer checks if a container is present in the state
|
|
|
|
func (s *BoltState) HasContainer(id string) (bool, error) {
|
|
|
|
if id == "" {
|
|
|
|
return false, ErrEmptyID
|
|
|
|
}
|
|
|
|
|
|
|
|
if !s.valid {
|
|
|
|
return false, ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrID := []byte(id)
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
exists := false
|
|
|
|
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
idsBucket, err := getIDBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrExists := idsBucket.Get(ctrID)
|
|
|
|
if ctrExists != nil {
|
|
|
|
exists = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return exists, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddContainer adds a container to the state
|
|
|
|
// The container being added cannot belong to a pod
|
|
|
|
func (s *BoltState) AddContainer(ctr *Container) error {
|
|
|
|
if !s.valid {
|
|
|
|
return ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ctr.valid {
|
|
|
|
return ErrCtrRemoved
|
|
|
|
}
|
|
|
|
|
|
|
|
if ctr.config.Pod != "" {
|
|
|
|
return errors.Wrapf(ErrInvalidArg, "cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod")
|
|
|
|
}
|
|
|
|
|
|
|
|
// JSON container structs to insert into DB
|
|
|
|
// TODO use a higher-performance struct encoding than JSON
|
|
|
|
configJSON, err := json.Marshal(ctr.config)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error marshalling container %s config to JSON", ctr.ID())
|
|
|
|
}
|
|
|
|
stateJSON, err := json.Marshal(ctr.state)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID())
|
|
|
|
}
|
|
|
|
netNSPath := ""
|
|
|
|
if ctr.state.NetNS != nil {
|
|
|
|
netNSPath = ctr.state.NetNS.Path()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collect dependencies for the container. Use a map to ensure no dupes.
|
|
|
|
dependsCtrs := ctr.Dependencies()
|
|
|
|
|
|
|
|
ctrID := []byte(ctr.ID())
|
|
|
|
ctrName := []byte(ctr.Name())
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
|
|
idsBucket, err := getIDBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
namesBucket, err := getNamesBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrConfigBucket, err := getCtrConfigBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrDependsBucket, err := getCtrDependsBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we already have a container with the given ID and name
|
|
|
|
idExist := idsBucket.Get(ctrID)
|
|
|
|
if idExist != nil {
|
|
|
|
return errors.Wrapf(ErrCtrExists, "container with ID %s already exists", ctr.ID())
|
|
|
|
}
|
|
|
|
nameExist := namesBucket.Get(ctrName)
|
|
|
|
if nameExist != nil {
|
|
|
|
return errors.Wrapf(ErrCtrExists, "container with name %s already exists", ctr.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
// No overlapping containers
|
|
|
|
// Add the new container to the DB
|
|
|
|
if err := idsBucket.Put(ctrID, ctrName); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s ID to DB", ctr.ID())
|
|
|
|
}
|
|
|
|
if err := namesBucket.Put(ctrName, ctrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s name (%s) to DB", ctr.ID(), ctr.Name())
|
|
|
|
}
|
|
|
|
if err := ctrConfigBucket.Put(ctrID, configJSON); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s config to DB", ctr.ID())
|
|
|
|
}
|
|
|
|
if err := ctrStateBucket.Put(ctrID, stateJSON); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s state to DB", ctr.ID())
|
|
|
|
}
|
|
|
|
if netNSPath != "" {
|
|
|
|
if err := netNSBucket.Put(ctrID, []byte(netNSPath)); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s netns path to DB", ctr.ID())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add dependencies for the container
|
|
|
|
for _, dependsCtr := range dependsCtrs {
|
|
|
|
depCtrID := []byte(dependsCtr)
|
|
|
|
deps := ctrDependsBucket.Get(depCtrID)
|
|
|
|
depsArray := []string{}
|
|
|
|
if deps != nil {
|
|
|
|
if err := json.Unmarshal(deps, &depsArray); err != nil {
|
|
|
|
return errors.Wrapf(err, "error unmarshalling container %s deps JSON", dependsCtr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
depsArray = append(depsArray, ctr.ID())
|
|
|
|
depsJSON, err := json.Marshal(&depsArray)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error marshalling container %s deps JSON", dependsCtr)
|
|
|
|
}
|
|
|
|
if err := ctrDependsBucket.Put(depCtrID, depsJSON); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s dependencies JSON to DB", dependsCtr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveContainer removes a container from the state
|
|
|
|
// The container will only be removed from the state and not any pods it belongs
|
|
|
|
// to
|
|
|
|
func (s *BoltState) RemoveContainer(ctr *Container) error {
|
|
|
|
if !s.valid {
|
|
|
|
return ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrID := []byte(ctr.ID())
|
|
|
|
ctrName := []byte(ctr.Name())
|
|
|
|
|
|
|
|
depCtrs := ctr.Dependencies()
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
|
|
idsBucket, err := getIDBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
namesBucket, err := getNamesBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrConfigBucket, err := getCtrConfigBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrDependsBucket, err := getCtrDependsBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does the container exist?
|
|
|
|
ctrExists := idsBucket.Get(ctrID)
|
|
|
|
if ctrExists == nil {
|
|
|
|
return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does the container have dependencies?
|
|
|
|
ctrDeps := ctrDependsBucket.Get(ctrID)
|
|
|
|
if ctrDeps != nil {
|
|
|
|
dependsCtrs := []string{}
|
|
|
|
if err := json.Unmarshal(ctrDeps, &dependsCtrs); err != nil {
|
|
|
|
return errors.Wrapf(err, "cannot unmarshal container %s dependencies JSON", ctr.ID())
|
|
|
|
}
|
|
|
|
if len(dependsCtrs) > 0 {
|
|
|
|
depsStr := strings.Join(dependsCtrs, ", ")
|
|
|
|
|
|
|
|
return errors.Wrapf(ErrCtrExists, "container %s is a dependency on the following containers: %s", ctr.ID(), depsStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := idsBucket.Delete(ctrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s ID in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := namesBucket.Delete(ctrName); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s name in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ctrConfigBucket.Delete(ctrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s config in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ctrStateBucket.Delete(ctrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s state in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can safely delete netNS even if it doesn't exist
|
|
|
|
// Delete on a non-existent key doesn't error
|
|
|
|
if err := netNSBucket.Delete(ctrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s network ns in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// As above, can safely delete even if it doesn't exist
|
|
|
|
if err := ctrDependsBucket.Delete(ctrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s dependencies in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove us from other container's dependencies
|
|
|
|
for _, depCtr := range depCtrs {
|
|
|
|
depCtrID := []byte(depCtr)
|
|
|
|
dep := ctrDependsBucket.Get(depCtrID)
|
|
|
|
if dep == nil {
|
|
|
|
// Inconsistent state, but the dependency we were trying to remove doesn't exist...
|
|
|
|
// Just continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
depStr := []string{}
|
|
|
|
if err := json.Unmarshal(dep, &depStr); err != nil {
|
|
|
|
return errors.Wrapf(err, "error unmarshaling ctr %s dependencies", ctr.ID(), depCtr)
|
|
|
|
}
|
|
|
|
newDeps := make([]string, 0, len(depStr))
|
|
|
|
for _, checkID := range depStr {
|
|
|
|
if checkID != ctr.ID() {
|
|
|
|
newDeps = append(newDeps, checkID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(newDeps) == 0 {
|
|
|
|
// Just delete the container's deps
|
|
|
|
if err := ctrDependsBucket.Delete(depCtrID); err != nil {
|
|
|
|
return errors.Wrapf(err, "error deleting container %s dependencies in DB", depCtr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Store the new deps
|
|
|
|
depsJSON, err := json.Marshal(&newDeps)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error marshalling container %s dependencies", depCtr)
|
|
|
|
}
|
|
|
|
if err := ctrDependsBucket.Put(depCtrID, depsJSON); err != nil {
|
|
|
|
return errors.Wrapf(err, "error adding container %s dependencies to DB", depCtr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateContainer updates a container's state from the database
|
|
|
|
func (s *BoltState) UpdateContainer(ctr *Container) error {
|
|
|
|
if !s.valid {
|
|
|
|
return ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ctr.valid {
|
|
|
|
return ErrCtrRemoved
|
|
|
|
}
|
|
|
|
|
|
|
|
newState := new(containerState)
|
|
|
|
netNSPath := ""
|
|
|
|
|
|
|
|
ctrID := []byte(ctr.ID())
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
newStateBytes := ctrStateBucket.Get(ctrID)
|
|
|
|
if newStateBytes == nil {
|
|
|
|
ctr.valid = false
|
|
|
|
return errors.Wrapf(ErrCtrRemoved, "container %s removed from database", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(newStateBytes, newState); err != nil {
|
|
|
|
return errors.Wrapf(err, "error unmarshalling container %s state", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBytes := netNSBucket.Get(ctrID)
|
|
|
|
if netNSBytes != nil {
|
|
|
|
netNSPath = string(netNSBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do we need to replace the container's netns?
|
|
|
|
if netNSPath != "" {
|
|
|
|
// Check if the container's old state has a good netns
|
|
|
|
if ctr.state.NetNS != nil && netNSPath == ctr.state.NetNS.Path() {
|
|
|
|
newState.NetNS = ctr.state.NetNS
|
|
|
|
} else {
|
|
|
|
// Tear down the existing namespace
|
|
|
|
if err := s.runtime.teardownNetNS(ctr); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the new network namespace
|
|
|
|
ns, err := joinNetNS(netNSPath)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error joining network namespace for container %s", ctr.ID())
|
|
|
|
}
|
|
|
|
newState.NetNS = ns
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The container no longer has a network namespace
|
|
|
|
// Tear down the old one
|
|
|
|
if err := s.runtime.teardownNetNS(ctr); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// New state compiled successfully, swap it into the current state
|
|
|
|
ctr.state = newState
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveContainer saves a container's current state in the database
|
|
|
|
func (s *BoltState) SaveContainer(ctr *Container) error {
|
|
|
|
if !s.valid {
|
|
|
|
return ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ctr.valid {
|
|
|
|
return ErrCtrRemoved
|
|
|
|
}
|
|
|
|
|
|
|
|
stateJSON, err := json.Marshal(ctr.state)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID())
|
|
|
|
}
|
|
|
|
netNSPath := ""
|
|
|
|
if ctr.state.NetNS != nil {
|
|
|
|
netNSPath = ctr.state.NetNS.Path()
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrID := []byte(ctr.ID())
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
oldJSON := ctrStateBucket.Get(ctrID)
|
|
|
|
if oldJSON == nil {
|
|
|
|
ctr.valid = false
|
|
|
|
return errors.Wrapf(ErrCtrRemoved, "container %s no longer present in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the state
|
|
|
|
if err := ctrStateBucket.Put(ctrID, stateJSON); err != nil {
|
|
|
|
return errors.Wrapf(err, "error updating container %s state in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
if netNSPath != "" {
|
|
|
|
if err := netNSBucket.Put(ctrID, []byte(netNSPath)); err != nil {
|
|
|
|
return errors.Wrapf(err, "error updating network namespace path for container %s in DB", ctr.ID())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContainerInUse checks if other containers depend on the given container
|
|
|
|
// It returns a slice of the IDs of the containers depending on the given
|
|
|
|
// container. If the slice is empty, no containers depend on the given container
|
|
|
|
func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) {
|
|
|
|
if !s.valid {
|
|
|
|
return nil, ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ctr.valid {
|
|
|
|
return nil, ErrCtrRemoved
|
|
|
|
}
|
|
|
|
|
|
|
|
depCtrs := []string{}
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
ctrDependsBucket, err := getCtrDependsBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
depsJSON := ctrDependsBucket.Get([]byte(ctr.ID()))
|
|
|
|
if depsJSON == nil {
|
|
|
|
// No deps, just return
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have deps, un-JSON them
|
|
|
|
if err := json.Unmarshal(depsJSON, &depCtrs); err != nil {
|
|
|
|
return errors.Wrapf(err, "error unmarshalling container %s dependencies", ctr.ID())
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return depCtrs, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllContainers retrieves all the containers in the database
|
|
|
|
func (s *BoltState) AllContainers() ([]*Container, error) {
|
|
|
|
if !s.valid {
|
|
|
|
return nil, ErrDBClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrs := []*Container{}
|
|
|
|
|
|
|
|
db, err := s.getDBCon()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
err = db.View(func(tx *bolt.Tx) error {
|
|
|
|
ctrConfigBucket, err := getCtrConfigBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrStateBucket, err := getCtrStateBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
netNSBucket, err := getNetNSBucket(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through all containers we know of in the state
|
|
|
|
// Build a full container struct for all of them and append
|
|
|
|
// it into the containers listing
|
|
|
|
err = ctrStateBucket.ForEach(func(id, data []byte) error {
|
|
|
|
ctr := new(Container)
|
|
|
|
ctr.config = new(ContainerConfig)
|
|
|
|
ctr.state = new(containerState)
|
|
|
|
|
|
|
|
err = s.getContainerFromDB(id, ctr, ctrConfigBucket,
|
|
|
|
ctrStateBucket, netNSBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrs = append(ctrs, ctr)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctrs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pod retrieves a pod given its full ID
|
|
|
|
func (s *BoltState) Pod(id string) (*Pod, error) {
|
|
|
|
return nil, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupPod retrieves a pod from full or unique partial ID or name
|
|
|
|
func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
|
|
|
|
return nil, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasPod checks if a pod with the given ID exists in the state
|
|
|
|
func (s *BoltState) HasPod(id string) (bool, error) {
|
|
|
|
return false, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// PodHasContainer checks if the given pod has a container with the given ID
|
|
|
|
func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) {
|
|
|
|
return false, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// PodContainersByID returns the IDs of all containers present in the given pod
|
|
|
|
func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) {
|
|
|
|
return nil, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// PodContainers returns all the containers present in the given pod
|
|
|
|
func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) {
|
|
|
|
return nil, ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddPod adds the given pod to the state. Only empty pods can be added.
|
|
|
|
func (s *BoltState) AddPod(pod *Pod) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemovePod removes the given pod from the state
|
|
|
|
// Only empty pods can be removed
|
|
|
|
func (s *BoltState) RemovePod(pod *Pod) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemovePodContainers removes all containers in a pod
|
|
|
|
func (s *BoltState) RemovePodContainers(pod *Pod) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddContainerToPod adds the given container to an existing pod
|
|
|
|
// The container will be added to the state and the pod
|
|
|
|
func (s *BoltState) AddContainerToPod(pod *Pod, ctr *Container) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveContainerFromPod removes a container from an existing pod
|
|
|
|
// The container will also be removed from the state
|
|
|
|
func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllPods returns all pods present in the state
|
|
|
|
func (s *BoltState) AllPods() ([]*Pod, error) {
|
|
|
|
return nil, ErrNotImplemented
|
|
|
|
}
|