summaryrefslogtreecommitdiff
path: root/libpod/boltdb_state.go
diff options
context:
space:
mode:
Diffstat (limited to 'libpod/boltdb_state.go')
-rw-r--r--libpod/boltdb_state.go463
1 files changed, 445 insertions, 18 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index 42f029379..b154d8bda 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -3,7 +3,6 @@ package libpod
import (
"bytes"
"encoding/json"
- "os"
"strings"
"sync"
@@ -19,7 +18,6 @@ type BoltState struct {
dbLock sync.Mutex
namespace string
namespaceBytes []byte
- lockDir string
runtime *Runtime
}
@@ -52,25 +50,15 @@ type BoltState struct {
// containers/storage do not occur.
// NewBoltState creates a new bolt-backed state database
-func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) {
+func NewBoltState(path string, runtime *Runtime) (State, error) {
state := new(BoltState)
state.dbPath = path
- state.lockDir = lockDir
state.runtime = runtime
state.namespace = ""
state.namespaceBytes = nil
logrus.Debugf("Initializing boltdb state at %s", path)
- // 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)
@@ -106,6 +94,12 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) {
if _, err := tx.CreateBucketIfNotExists(allPodsBkt); err != nil {
return errors.Wrapf(err, "error creating all pods bucket")
}
+ if _, err := tx.CreateBucketIfNotExists(volBkt); err != nil {
+ return errors.Wrapf(err, "error creating volume bucket")
+ }
+ if _, err := tx.CreateBucketIfNotExists(allVolsBkt); err != nil {
+ return errors.Wrapf(err, "error creating all volumes bucket")
+ }
if _, err := tx.CreateBucketIfNotExists(runtimeConfigBkt); err != nil {
return errors.Wrapf(err, "error creating runtime-config bucket")
}
@@ -115,11 +109,6 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) {
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
@@ -240,6 +229,72 @@ func (s *BoltState) Refresh() error {
return err
}
+// GetDBConfig retrieves runtime configuration fields that were created when
+// the database was first initialized
+func (s *BoltState) GetDBConfig() (*DBConfig, error) {
+ if !s.valid {
+ return nil, ErrDBClosed
+ }
+
+ cfg := new(DBConfig)
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return nil, err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ configBucket, err := getRuntimeConfigBucket(tx)
+ if err != nil {
+ return nil
+ }
+
+ // Some of these may be nil
+ // When we convert to string, Go will coerce them to ""
+ // That's probably fine - we could raise an error if the key is
+ // missing, but just not including it is also OK.
+ libpodRoot := configBucket.Get(staticDirKey)
+ libpodTmp := configBucket.Get(tmpDirKey)
+ storageRoot := configBucket.Get(graphRootKey)
+ storageTmp := configBucket.Get(runRootKey)
+ graphDriver := configBucket.Get(graphDriverKey)
+
+ cfg.LibpodRoot = string(libpodRoot)
+ cfg.LibpodTmp = string(libpodTmp)
+ cfg.StorageRoot = string(storageRoot)
+ cfg.StorageTmp = string(storageTmp)
+ cfg.GraphDriver = string(graphDriver)
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return cfg, nil
+}
+
+// ValidateDBConfig validates paths in the given runtime against the database
+func (s *BoltState) ValidateDBConfig(runtime *Runtime) error {
+ if !s.valid {
+ return ErrDBClosed
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.closeDBCon(db)
+
+ // Check runtime configuration
+ if err := checkRuntimeConfig(db, runtime); err != nil {
+ return err
+ }
+
+ return nil
+}
+
// SetNamespace sets the namespace that will be used for container and pod
// retrieval
func (s *BoltState) SetNamespace(ns string) error {
@@ -1101,6 +1156,378 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) {
return ctrs, nil
}
+// AddVolume adds the given volume to the state. It also adds ctrDepID to
+// the sub bucket holding the container dependencies that this volume has
+func (s *BoltState) AddVolume(volume *Volume) error {
+ if !s.valid {
+ return ErrDBClosed
+ }
+
+ if !volume.valid {
+ return ErrVolumeRemoved
+ }
+
+ volName := []byte(volume.Name())
+
+ volConfigJSON, err := json.Marshal(volume.config)
+ if err != nil {
+ return errors.Wrapf(err, "error marshalling volume %s config to JSON", volume.Name())
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.Update(func(tx *bolt.Tx) error {
+ volBkt, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ allVolsBkt, err := getAllVolsBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ // Check if we already have a volume with the given name
+ volExists := allVolsBkt.Get(volName)
+ if volExists != nil {
+ return errors.Wrapf(ErrVolumeExists, "name %s is in use", volume.Name())
+ }
+
+ // We are good to add the volume
+ // Make a bucket for it
+ newVol, err := volBkt.CreateBucket(volName)
+ if err != nil {
+ return errors.Wrapf(err, "error creating bucket for volume %s", volume.Name())
+ }
+
+ // Make a subbucket for the containers using the volume. Dependent container IDs will be addedremoved to
+ // this bucket in addcontainer/removeContainer
+ if _, err := newVol.CreateBucket(volDependenciesBkt); err != nil {
+ return errors.Wrapf(err, "error creating bucket for containers using volume %s", volume.Name())
+ }
+
+ if err := newVol.Put(configKey, volConfigJSON); err != nil {
+ return errors.Wrapf(err, "error storing volume %s configuration in DB", volume.Name())
+ }
+
+ if err := allVolsBkt.Put(volName, volName); err != nil {
+ return errors.Wrapf(err, "error storing volume %s in all volumes bucket in DB", volume.Name())
+ }
+
+ return nil
+ })
+ return err
+}
+
+// RemoveVolCtrDep updates the container dependencies sub bucket of the given volume.
+// It deletes it from the bucket when found.
+// This is important when force removing a volume and we want to get rid of the dependencies.
+func (s *BoltState) RemoveVolCtrDep(volume *Volume, ctrID string) error {
+ if ctrID == "" {
+ return nil
+ }
+
+ if !s.valid {
+ return ErrDBBadConfig
+ }
+
+ if !volume.valid {
+ return ErrVolumeRemoved
+ }
+
+ volName := []byte(volume.Name())
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.Update(func(tx *bolt.Tx) error {
+ volBkt, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volDB := volBkt.Bucket(volName)
+ if volDB == nil {
+ volume.valid = false
+ return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in database", volume.Name())
+ }
+
+ // Make a subbucket for the containers using the volume
+ ctrDepsBkt := volDB.Bucket(volDependenciesBkt)
+ depCtrID := []byte(ctrID)
+ if depExists := ctrDepsBkt.Get(depCtrID); depExists != nil {
+ if err := ctrDepsBkt.Delete(depCtrID); err != nil {
+ return errors.Wrapf(err, "error deleting container dependencies %q for volume %s in ctrDependencies bucket in DB", ctrID, volume.Name())
+ }
+ }
+
+ return nil
+ })
+ return err
+}
+
+// RemoveVolume removes the given volume from the state
+func (s *BoltState) RemoveVolume(volume *Volume) error {
+ if !s.valid {
+ return ErrDBClosed
+ }
+
+ if !volume.valid {
+ return ErrVolumeRemoved
+ }
+
+ volName := []byte(volume.Name())
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.Update(func(tx *bolt.Tx) error {
+ volBkt, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ allVolsBkt, err := getAllVolsBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ // Check if the volume exists
+ volDB := volBkt.Bucket(volName)
+ if volDB == nil {
+ volume.valid = false
+ return errors.Wrapf(ErrNoSuchVolume, "volume %s does not exist in DB", volume.Name())
+ }
+
+ // Check if volume is not being used by any container
+ // This should never be nil
+ // But if it is, we can assume that no containers are using
+ // the volume.
+ volCtrsBkt := volDB.Bucket(volDependenciesBkt)
+ if volCtrsBkt != nil {
+ var deps []string
+ err = volCtrsBkt.ForEach(func(id, value []byte) error {
+ deps = append(deps, string(id))
+ return nil
+ })
+ if err != nil {
+ return errors.Wrapf(err, "error getting list of dependencies from dependencies bucket for volumes %q", volume.Name())
+ }
+ if len(deps) > 0 {
+ return errors.Wrapf(ErrVolumeBeingUsed, "volume %s is being used by container(s) %s", volume.Name(), strings.Join(deps, ","))
+ }
+ }
+
+ // volume is ready for removal
+ // Let's kick it out
+ if err := allVolsBkt.Delete(volName); err != nil {
+ return errors.Wrapf(err, "error removing volume %s from all volumes bucket in DB", volume.Name())
+ }
+ if err := volBkt.DeleteBucket(volName); err != nil {
+ return errors.Wrapf(err, "error removing volume %s from DB", volume.Name())
+ }
+
+ return nil
+ })
+ return err
+}
+
+// AllVolumes returns all volumes present in the state
+func (s *BoltState) AllVolumes() ([]*Volume, error) {
+ if !s.valid {
+ return nil, ErrDBClosed
+ }
+
+ volumes := []*Volume{}
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return nil, err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ allVolsBucket, err := getAllVolsBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volBucket, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+ err = allVolsBucket.ForEach(func(id, name []byte) error {
+ volExists := volBucket.Bucket(id)
+ // This check can be removed if performance becomes an
+ // issue, but much less helpful errors will be produced
+ if volExists == nil {
+ return errors.Wrapf(ErrInternal, "inconsistency in state - volume %s is in all volumes bucket but volume not found", string(id))
+ }
+
+ volume := new(Volume)
+ volume.config = new(VolumeConfig)
+
+ if err := s.getVolumeFromDB(id, volume, volBucket); err != nil {
+ if errors.Cause(err) != ErrNSMismatch {
+ logrus.Errorf("Error retrieving volume %s from the database: %v", string(id), err)
+ }
+ } else {
+ volumes = append(volumes, volume)
+ }
+
+ return nil
+ })
+ return err
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return volumes, nil
+}
+
+// Volume retrieves a volume from full name
+func (s *BoltState) Volume(name string) (*Volume, error) {
+ if name == "" {
+ return nil, ErrEmptyID
+ }
+
+ if !s.valid {
+ return nil, ErrDBClosed
+ }
+
+ volName := []byte(name)
+
+ volume := new(Volume)
+ volume.config = new(VolumeConfig)
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return nil, err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ volBkt, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ return s.getVolumeFromDB(volName, volume, volBkt)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return volume, nil
+}
+
+// HasVolume returns true if the given volume exists in the state, otherwise it returns false
+func (s *BoltState) HasVolume(name string) (bool, error) {
+ if name == "" {
+ return false, ErrEmptyID
+ }
+
+ if !s.valid {
+ return false, ErrDBClosed
+ }
+
+ volName := []byte(name)
+
+ exists := false
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return false, err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ volBkt, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volDB := volBkt.Bucket(volName)
+ if volDB != nil {
+ exists = true
+ }
+
+ return nil
+ })
+ if err != nil {
+ return false, err
+ }
+
+ return exists, nil
+}
+
+// VolumeInUse checks if any container is using the volume
+// It returns a slice of the IDs of the containers using the given
+// volume. If the slice is empty, no containers use the given volume
+func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) {
+ if !s.valid {
+ return nil, ErrDBClosed
+ }
+
+ if !volume.valid {
+ return nil, ErrVolumeRemoved
+ }
+
+ depCtrs := []string{}
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return nil, err
+ }
+ defer s.closeDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ volBucket, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volDB := volBucket.Bucket([]byte(volume.Name()))
+ if volDB == nil {
+ volume.valid = false
+ return errors.Wrapf(ErrNoSuchVolume, "no volume with name %s found in DB", volume.Name())
+ }
+
+ dependsBkt := volDB.Bucket(volDependenciesBkt)
+ if dependsBkt == nil {
+ return errors.Wrapf(ErrInternal, "volume %s has no dependencies bucket", volume.Name())
+ }
+
+ // Iterate through and add dependencies
+ err = dependsBkt.ForEach(func(id, value []byte) error {
+ depCtrs = append(depCtrs, string(id))
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return depCtrs, nil
+}
+
// AddPod adds the given pod to the state.
func (s *BoltState) AddPod(pod *Pod) error {
if !s.valid {