mirror of
https://github.com/containers/podman
synced 2024-10-20 17:23:30 +00:00
92bae8d308
Allow Podman containers to request to use a specific OCI runtime if multiple runtimes are configured. This is the first step to properly supporting containers in a multi-runtime environment. The biggest changes are that all OCI runtimes are now initialized when Podman creates its runtime, and containers now use the runtime requested in their configuration (instead of always the default runtime). Signed-off-by: Matthew Heon <matthew.heon@pm.me>
771 lines
23 KiB
Go
771 lines
23 KiB
Go
package libpod
|
|
|
|
import (
|
|
"bytes"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/containers/libpod/pkg/rootless"
|
|
"github.com/containers/storage"
|
|
bolt "github.com/etcd-io/bbolt"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
idRegistryName = "id-registry"
|
|
nameRegistryName = "name-registry"
|
|
nsRegistryName = "ns-registry"
|
|
ctrName = "ctr"
|
|
allCtrsName = "all-ctrs"
|
|
podName = "pod"
|
|
allPodsName = "allPods"
|
|
volName = "vol"
|
|
allVolsName = "allVolumes"
|
|
runtimeConfigName = "runtime-config"
|
|
|
|
configName = "config"
|
|
stateName = "state"
|
|
dependenciesName = "dependencies"
|
|
volCtrDependencies = "vol-dependencies"
|
|
netNSName = "netns"
|
|
containersName = "containers"
|
|
podIDName = "pod-id"
|
|
namespaceName = "namespace"
|
|
|
|
staticDirName = "static-dir"
|
|
tmpDirName = "tmp-dir"
|
|
runRootName = "run-root"
|
|
graphRootName = "graph-root"
|
|
graphDriverName = "graph-driver-name"
|
|
osName = "os"
|
|
volPathName = "volume-path"
|
|
)
|
|
|
|
var (
|
|
idRegistryBkt = []byte(idRegistryName)
|
|
nameRegistryBkt = []byte(nameRegistryName)
|
|
nsRegistryBkt = []byte(nsRegistryName)
|
|
ctrBkt = []byte(ctrName)
|
|
allCtrsBkt = []byte(allCtrsName)
|
|
podBkt = []byte(podName)
|
|
allPodsBkt = []byte(allPodsName)
|
|
volBkt = []byte(volName)
|
|
allVolsBkt = []byte(allVolsName)
|
|
runtimeConfigBkt = []byte(runtimeConfigName)
|
|
|
|
configKey = []byte(configName)
|
|
stateKey = []byte(stateName)
|
|
dependenciesBkt = []byte(dependenciesName)
|
|
volDependenciesBkt = []byte(volCtrDependencies)
|
|
netNSKey = []byte(netNSName)
|
|
containersBkt = []byte(containersName)
|
|
podIDKey = []byte(podIDName)
|
|
namespaceKey = []byte(namespaceName)
|
|
|
|
staticDirKey = []byte(staticDirName)
|
|
tmpDirKey = []byte(tmpDirName)
|
|
runRootKey = []byte(runRootName)
|
|
graphRootKey = []byte(graphRootName)
|
|
graphDriverKey = []byte(graphDriverName)
|
|
osKey = []byte(osName)
|
|
volPathKey = []byte(volPathName)
|
|
)
|
|
|
|
// Check if the configuration of the database is compatible with the
|
|
// configuration of the runtime opening it
|
|
// If there is no runtime configuration loaded, load our own
|
|
func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error {
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
configBkt, err := getRuntimeConfigBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateDBAgainstConfig(configBkt, "OS", runtime.GOOS, osKey, runtime.GOOS); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateDBAgainstConfig(configBkt, "libpod root directory (staticdir)",
|
|
rt.config.StaticDir, staticDirKey, ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateDBAgainstConfig(configBkt, "libpod temporary files directory (tmpdir)",
|
|
rt.config.TmpDir, tmpDirKey, ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := validateDBAgainstConfig(configBkt, "storage temporary directory (runroot)",
|
|
rt.config.StorageConfig.RunRoot, runRootKey,
|
|
storeOpts.RunRoot); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateDBAgainstConfig(configBkt, "storage graph root directory (graphroot)",
|
|
rt.config.StorageConfig.GraphRoot, graphRootKey,
|
|
storeOpts.GraphRoot); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateDBAgainstConfig(configBkt, "storage graph driver",
|
|
rt.config.StorageConfig.GraphDriverName,
|
|
graphDriverKey,
|
|
storeOpts.GraphDriverName); err != nil {
|
|
return err
|
|
}
|
|
|
|
return validateDBAgainstConfig(configBkt, "volume path",
|
|
rt.config.VolumePath, volPathKey, "")
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// Validate a configuration entry in the DB against current runtime config
|
|
// If the given configuration key does not exist it will be created
|
|
// If the given runtimeValue or value retrieved from the database are the empty
|
|
// string and defaultValue is not, defaultValue will be checked instead. This
|
|
// ensures that we will not fail on configuration changes in configured c/storage.
|
|
func validateDBAgainstConfig(bucket *bolt.Bucket, fieldName, runtimeValue string, keyName []byte, defaultValue string) error {
|
|
keyBytes := bucket.Get(keyName)
|
|
if keyBytes == nil {
|
|
dbValue := []byte(runtimeValue)
|
|
if runtimeValue == "" && defaultValue != "" {
|
|
dbValue = []byte(defaultValue)
|
|
}
|
|
|
|
if err := bucket.Put(keyName, dbValue); err != nil {
|
|
return errors.Wrapf(err, "error updating %s in DB runtime config", fieldName)
|
|
}
|
|
} else {
|
|
if runtimeValue != string(keyBytes) {
|
|
// If runtimeValue is the empty string, check against
|
|
// the default
|
|
if runtimeValue == "" && defaultValue != "" &&
|
|
string(keyBytes) == defaultValue {
|
|
return nil
|
|
}
|
|
|
|
// If DB value is the empty string, check that the
|
|
// runtime value is the default
|
|
if string(keyBytes) == "" && defaultValue != "" &&
|
|
runtimeValue == defaultValue {
|
|
return nil
|
|
}
|
|
|
|
return errors.Wrapf(ErrDBBadConfig, "database %s %s does not match our %s %s",
|
|
fieldName, string(keyBytes), fieldName, runtimeValue)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Open a connection to the database.
|
|
// Must be paired with a `defer closeDBCon()` on the returned database, to
|
|
// ensure the state is properly unlocked
|
|
func (s *BoltState) getDBCon() (*bolt.DB, error) {
|
|
// We need an in-memory lock to avoid issues around POSIX file advisory
|
|
// locks as described in the link below:
|
|
// https://www.sqlite.org/src/artifact/c230a7a24?ln=994-1081
|
|
s.dbLock.Lock()
|
|
|
|
db, err := bolt.Open(s.dbPath, 0600, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error opening database %s", s.dbPath)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
// Close a connection to the database.
|
|
// MUST be used in place of `db.Close()` to ensure proper unlocking of the
|
|
// state.
|
|
func (s *BoltState) closeDBCon(db *bolt.DB) error {
|
|
err := db.Close()
|
|
|
|
s.dbLock.Unlock()
|
|
|
|
return err
|
|
}
|
|
|
|
func getIDBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(idRegistryBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "id registry bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(nameRegistryBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "name registry bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getNSBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(nsRegistryBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "namespace registry bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(ctrBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "containers bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getAllCtrsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(allCtrsBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "all containers bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getPodBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(podBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "pods bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getAllPodsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(allPodsBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "all pods bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getVolBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(volBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "volumes bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getAllVolsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(allVolsBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "all volumes bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|
bkt := tx.Bucket(runtimeConfigBkt)
|
|
if bkt == nil {
|
|
return nil, errors.Wrapf(ErrDBBadConfig, "runtime configuration bucket not found in DB")
|
|
}
|
|
return bkt, nil
|
|
}
|
|
|
|
func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error {
|
|
valid := true
|
|
ctrBkt := ctrsBkt.Bucket(id)
|
|
if ctrBkt == nil {
|
|
return errors.Wrapf(ErrNoSuchCtr, "container %s not found in DB", string(id))
|
|
}
|
|
|
|
if s.namespaceBytes != nil {
|
|
ctrNamespaceBytes := ctrBkt.Get(namespaceKey)
|
|
if !bytes.Equal(s.namespaceBytes, ctrNamespaceBytes) {
|
|
return errors.Wrapf(ErrNSMismatch, "cannot retrieve container %s as it is part of namespace %q and we are in namespace %q", string(id), string(ctrNamespaceBytes), s.namespace)
|
|
}
|
|
}
|
|
|
|
configBytes := ctrBkt.Get(configKey)
|
|
if configBytes == nil {
|
|
return errors.Wrapf(ErrInternal, "container %s missing config key in DB", string(id))
|
|
}
|
|
|
|
if err := json.Unmarshal(configBytes, ctr.config); err != nil {
|
|
return errors.Wrapf(err, "error unmarshalling container %s config", string(id))
|
|
}
|
|
|
|
// Get the lock
|
|
lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error retrieving lock for container %s", string(id))
|
|
}
|
|
ctr.lock = lock
|
|
|
|
if ctr.config.OCIRuntime == "" {
|
|
ctr.ociRuntime = s.runtime.defaultOCIRuntime
|
|
} else {
|
|
ociRuntime, ok := s.runtime.ociRuntimes[ctr.config.OCIRuntime]
|
|
if !ok {
|
|
return errors.Wrapf(ErrInternal, "container %s was created with OCI runtime %s, but that runtime is not available in the current configuration", ctr.ID(), ctr.config.OCIRuntime)
|
|
}
|
|
ctr.ociRuntime = ociRuntime
|
|
}
|
|
|
|
ctr.runtime = s.runtime
|
|
ctr.valid = valid
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error {
|
|
podDB := podBkt.Bucket(id)
|
|
if podDB == nil {
|
|
return errors.Wrapf(ErrNoSuchPod, "pod with ID %s not found", string(id))
|
|
}
|
|
|
|
if s.namespaceBytes != nil {
|
|
podNamespaceBytes := podDB.Get(namespaceKey)
|
|
if !bytes.Equal(s.namespaceBytes, podNamespaceBytes) {
|
|
return errors.Wrapf(ErrNSMismatch, "cannot retrieve pod %s as it is part of namespace %q and we are in namespace %q", string(id), string(podNamespaceBytes), s.namespace)
|
|
}
|
|
}
|
|
|
|
podConfigBytes := podDB.Get(configKey)
|
|
if podConfigBytes == nil {
|
|
return errors.Wrapf(ErrInternal, "pod %s is missing configuration key in DB", string(id))
|
|
}
|
|
|
|
if err := json.Unmarshal(podConfigBytes, pod.config); err != nil {
|
|
return errors.Wrapf(err, "error unmarshalling pod %s config from DB", string(id))
|
|
}
|
|
|
|
// Get the lock
|
|
lock, err := s.runtime.lockManager.RetrieveLock(pod.config.LockID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error retrieving lock for pod %s", string(id))
|
|
}
|
|
pod.lock = lock
|
|
|
|
pod.runtime = s.runtime
|
|
pod.valid = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bucket) error {
|
|
volDB := volBkt.Bucket(name)
|
|
if volDB == nil {
|
|
return errors.Wrapf(ErrNoSuchVolume, "volume with name %s not found", string(name))
|
|
}
|
|
|
|
volConfigBytes := volDB.Get(configKey)
|
|
if volConfigBytes == nil {
|
|
return errors.Wrapf(ErrInternal, "volume %s is missing configuration key in DB", string(name))
|
|
}
|
|
|
|
if err := json.Unmarshal(volConfigBytes, volume.config); err != nil {
|
|
return errors.Wrapf(err, "error unmarshalling volume %s config from DB", string(name))
|
|
}
|
|
|
|
volume.runtime = s.runtime
|
|
volume.valid = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// Add a container to the DB
|
|
// If pod is not nil, the container is added to the pod as well
|
|
func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
|
|
if s.namespace != "" && s.namespace != ctr.config.Namespace {
|
|
return errors.Wrapf(ErrNSMismatch, "cannot add container %s as it is in namespace %q and we are in namespace %q",
|
|
ctr.ID(), s.namespace, ctr.config.Namespace)
|
|
}
|
|
|
|
// 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 := getNetNSPath(ctr)
|
|
dependsCtrs := ctr.Dependencies()
|
|
|
|
ctrID := []byte(ctr.ID())
|
|
ctrName := []byte(ctr.Name())
|
|
|
|
var ctrNamespace []byte
|
|
if ctr.config.Namespace != "" {
|
|
ctrNamespace = []byte(ctr.config.Namespace)
|
|
}
|
|
|
|
db, err := s.getDBCon()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.closeDBCon(db)
|
|
|
|
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
|
|
}
|
|
|
|
nsBucket, err := getNSBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctrBucket, err := getCtrBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allCtrsBucket, err := getAllCtrsBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
volBkt, err := getVolBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If a pod was given, check if it exists
|
|
var podDB *bolt.Bucket
|
|
var podCtrs *bolt.Bucket
|
|
if pod != nil {
|
|
podBucket, err := getPodBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
podID := []byte(pod.ID())
|
|
|
|
podDB = podBucket.Bucket(podID)
|
|
if podDB == nil {
|
|
pod.valid = false
|
|
return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist in database", pod.ID())
|
|
}
|
|
podCtrs = podDB.Bucket(containersBkt)
|
|
if podCtrs == nil {
|
|
return errors.Wrapf(ErrInternal, "pod %s does not have a containers bucket", pod.ID())
|
|
}
|
|
|
|
podNS := podDB.Get(namespaceKey)
|
|
if !bytes.Equal(podNS, ctrNamespace) {
|
|
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and pod %s is in namespace %s",
|
|
ctr.ID(), ctr.config.Namespace, pod.ID(), pod.config.Namespace)
|
|
}
|
|
}
|
|
|
|
// Check if we already have a container with the given ID and name
|
|
idExist := idsBucket.Get(ctrID)
|
|
if idExist != nil {
|
|
return errors.Wrapf(ErrCtrExists, "ID %s is in use", ctr.ID())
|
|
}
|
|
nameExist := namesBucket.Get(ctrName)
|
|
if nameExist != nil {
|
|
return errors.Wrapf(ErrCtrExists, "name %s is in use", 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 ctrNamespace != nil {
|
|
if err := nsBucket.Put(ctrID, ctrNamespace); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s namespace (%q) to DB", ctr.ID(), ctr.Namespace())
|
|
}
|
|
}
|
|
if err := allCtrsBucket.Put(ctrID, ctrName); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s to all containers bucket in DB", ctr.ID())
|
|
}
|
|
|
|
newCtrBkt, err := ctrBucket.CreateBucket(ctrID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error adding container %s bucket to DB", ctr.ID())
|
|
}
|
|
|
|
if err := newCtrBkt.Put(configKey, configJSON); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s config to DB", ctr.ID())
|
|
}
|
|
if err := newCtrBkt.Put(stateKey, stateJSON); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s state to DB", ctr.ID())
|
|
}
|
|
if ctrNamespace != nil {
|
|
if err := newCtrBkt.Put(namespaceKey, ctrNamespace); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s namespace to DB", ctr.ID())
|
|
}
|
|
}
|
|
if pod != nil {
|
|
if err := newCtrBkt.Put(podIDKey, []byte(pod.ID())); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s pod to DB", ctr.ID())
|
|
}
|
|
}
|
|
if netNSPath != "" {
|
|
if err := newCtrBkt.Put(netNSKey, []byte(netNSPath)); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s netns path to DB", ctr.ID())
|
|
}
|
|
}
|
|
if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil {
|
|
return errors.Wrapf(err, "error creating dependencies bucket for container %s", ctr.ID())
|
|
}
|
|
|
|
// Add dependencies for the container
|
|
for _, dependsCtr := range dependsCtrs {
|
|
depCtrID := []byte(dependsCtr)
|
|
|
|
depCtrBkt := ctrBucket.Bucket(depCtrID)
|
|
if depCtrBkt == nil {
|
|
return errors.Wrapf(ErrNoSuchCtr, "container %s depends on container %s, but it does not exist in the DB", ctr.ID(), dependsCtr)
|
|
}
|
|
|
|
depCtrPod := depCtrBkt.Get(podIDKey)
|
|
if pod != nil {
|
|
// If we're part of a pod, make sure the dependency is part of the same pod
|
|
if depCtrPod == nil {
|
|
return errors.Wrapf(ErrInvalidArg, "container %s depends on container %s which is not in pod %s", ctr.ID(), dependsCtr, pod.ID())
|
|
}
|
|
|
|
if string(depCtrPod) != pod.ID() {
|
|
return errors.Wrapf(ErrInvalidArg, "container %s depends on container %s which is in a different pod (%s)", ctr.ID(), dependsCtr, string(depCtrPod))
|
|
}
|
|
} else {
|
|
// If we're not part of a pod, we cannot depend on containers in a pod
|
|
if depCtrPod != nil {
|
|
return errors.Wrapf(ErrInvalidArg, "container %s depends on container %s which is in a pod - containers not in pods cannot depend on containers in pods", ctr.ID(), dependsCtr)
|
|
}
|
|
}
|
|
|
|
depNamespace := depCtrBkt.Get(namespaceKey)
|
|
if !bytes.Equal(ctrNamespace, depNamespace) {
|
|
return errors.Wrapf(ErrNSMismatch, "container %s in namespace %q depends on container %s in namespace %q - namespaces must match", ctr.ID(), ctr.config.Namespace, dependsCtr, string(depNamespace))
|
|
}
|
|
|
|
depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt)
|
|
if depCtrDependsBkt == nil {
|
|
return errors.Wrapf(ErrInternal, "container %s does not have a dependencies bucket", dependsCtr)
|
|
}
|
|
if err := depCtrDependsBkt.Put(ctrID, ctrName); err != nil {
|
|
return errors.Wrapf(err, "error adding ctr %s as dependency of container %s", ctr.ID(), dependsCtr)
|
|
}
|
|
}
|
|
|
|
// Add ctr to pod
|
|
if pod != nil {
|
|
if err := podCtrs.Put(ctrID, ctrName); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s to pod %s", ctr.ID(), pod.ID())
|
|
}
|
|
}
|
|
|
|
// Add container to named volume dependencies buckets
|
|
for _, vol := range ctr.config.NamedVolumes {
|
|
volDB := volBkt.Bucket([]byte(vol.Name))
|
|
if volDB == nil {
|
|
return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database when adding container %s", vol.Name, ctr.ID())
|
|
}
|
|
|
|
ctrDepsBkt := volDB.Bucket(volDependenciesBkt)
|
|
if depExists := ctrDepsBkt.Get(ctrID); depExists == nil {
|
|
if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil {
|
|
return errors.Wrapf(err, "error adding container %s to volume %s dependencies", ctr.ID(), vol.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
// Remove a container from the DB
|
|
// If pod is not nil, the container is treated as belonging to a pod, and
|
|
// will be removed from the pod as well
|
|
func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
|
|
ctrID := []byte(ctr.ID())
|
|
ctrName := []byte(ctr.Name())
|
|
|
|
idsBucket, err := getIDBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
namesBucket, err := getNamesBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctrBucket, err := getCtrBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nsBucket, err := getNSBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allCtrsBucket, err := getAllCtrsBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
volBkt, err := getVolBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Does the pod exist?
|
|
var podDB *bolt.Bucket
|
|
if pod != nil {
|
|
podBucket, err := getPodBucket(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
podID := []byte(pod.ID())
|
|
|
|
podDB = podBucket.Bucket(podID)
|
|
if podDB == nil {
|
|
pod.valid = false
|
|
return errors.Wrapf(ErrNoSuchPod, "no pod with ID %s found in DB", pod.ID())
|
|
}
|
|
}
|
|
|
|
// Does the container exist?
|
|
ctrExists := ctrBucket.Bucket(ctrID)
|
|
if ctrExists == nil {
|
|
ctr.valid = false
|
|
return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID())
|
|
}
|
|
|
|
// Compare namespace
|
|
// We can't remove containers not in our namespace
|
|
if s.namespace != "" {
|
|
if s.namespace != ctr.config.Namespace {
|
|
return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace)
|
|
}
|
|
if pod != nil && s.namespace != pod.config.Namespace {
|
|
return errors.Wrapf(ErrNSMismatch, "pod %s is in namespace %q, does not match out namespace %q", pod.ID(), pod.config.Namespace, s.namespace)
|
|
}
|
|
}
|
|
|
|
if podDB != nil {
|
|
// Check if the container is in the pod, remove it if it is
|
|
podCtrs := podDB.Bucket(containersBkt)
|
|
if podCtrs == nil {
|
|
// Malformed pod
|
|
logrus.Errorf("pod %s malformed in database, missing containers bucket!", pod.ID())
|
|
} else {
|
|
ctrInPod := podCtrs.Get(ctrID)
|
|
if ctrInPod == nil {
|
|
return errors.Wrapf(ErrNoSuchCtr, "container %s is not in pod %s", ctr.ID(), pod.ID())
|
|
}
|
|
if err := podCtrs.Delete(ctrID); err != nil {
|
|
return errors.Wrapf(err, "error removing container %s from pod %s", ctr.ID(), pod.ID())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Does the container have dependencies?
|
|
ctrDepsBkt := ctrExists.Bucket(dependenciesBkt)
|
|
if ctrDepsBkt == nil {
|
|
return errors.Wrapf(ErrInternal, "container %s does not have a dependencies bucket", ctr.ID())
|
|
}
|
|
deps := []string{}
|
|
err = ctrDepsBkt.ForEach(func(id, value []byte) error {
|
|
deps = append(deps, string(id))
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(deps) != 0 {
|
|
return errors.Wrapf(ErrCtrExists, "container %s is a dependency of the following containers: %s", ctr.ID(), strings.Join(deps, ", "))
|
|
}
|
|
|
|
if err := ctrBucket.DeleteBucket(ctrID); err != nil {
|
|
return errors.Wrapf(ErrInternal, "error deleting container %s from DB", ctr.ID())
|
|
}
|
|
|
|
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 := nsBucket.Delete(ctrID); err != nil {
|
|
return errors.Wrapf(err, "error deleting container %s namespace in DB", ctr.ID())
|
|
}
|
|
if err := allCtrsBucket.Delete(ctrID); err != nil {
|
|
return errors.Wrapf(err, "error deleting container %s from all containers bucket in DB", ctr.ID())
|
|
}
|
|
|
|
depCtrs := ctr.Dependencies()
|
|
|
|
// Remove us from other container's dependencies
|
|
for _, depCtr := range depCtrs {
|
|
depCtrID := []byte(depCtr)
|
|
|
|
depCtrBkt := ctrBucket.Bucket(depCtrID)
|
|
if depCtrBkt == nil {
|
|
// The dependent container has been removed
|
|
// This should not be possible, and means the
|
|
// state is inconsistent, but don't error
|
|
// The container with inconsistent state is the
|
|
// one being removed
|
|
continue
|
|
}
|
|
|
|
depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt)
|
|
if depCtrDependsBkt == nil {
|
|
// This is more serious - another container in
|
|
// the state is inconsistent
|
|
// Log it, continue removing
|
|
logrus.Errorf("Container %s is missing dependencies bucket in DB", ctr.ID())
|
|
continue
|
|
}
|
|
|
|
if err := depCtrDependsBkt.Delete(ctrID); err != nil {
|
|
return errors.Wrapf(err, "error removing container %s as a dependency of container %s", ctr.ID(), depCtr)
|
|
}
|
|
}
|
|
|
|
// Remove container from named volume dependencies buckets
|
|
for _, vol := range ctr.config.NamedVolumes {
|
|
volDB := volBkt.Bucket([]byte(vol.Name))
|
|
if volDB == nil {
|
|
// Let's assume the volume was already deleted and
|
|
// continue to remove the container
|
|
continue
|
|
}
|
|
|
|
ctrDepsBkt := volDB.Bucket(volDependenciesBkt)
|
|
if depExists := ctrDepsBkt.Get(ctrID); depExists == nil {
|
|
if err := ctrDepsBkt.Delete(ctrID); err != nil {
|
|
return errors.Wrapf(err, "error deleting container %s dependency on volume %s", ctr.ID(), vol.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|