summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@pm.me>2019-08-30 16:09:17 -0400
committerMatthew Heon <matthew.heon@pm.me>2019-09-05 12:29:36 -0400
commit5a8a71ed817a4fa50fd9444846a50b76f25228d1 (patch)
treedee3a3bc42d410ff184acfa38d49ac1e5ad5bbed
parentc8193633cd82228b01e789becead410c2a940227 (diff)
downloadpodman-5a8a71ed817a4fa50fd9444846a50b76f25228d1.tar.gz
podman-5a8a71ed817a4fa50fd9444846a50b76f25228d1.tar.bz2
podman-5a8a71ed817a4fa50fd9444846a50b76f25228d1.zip
Add volume state
We need to be able to track the number of times a volume has been mounted for tmpfs/nfs/etc volumes. As such, we need a mutable state for volumes. Add one, with the expected update/save methods in both states. There is backwards compat here, in that older volumes without a state will still be accepted. Signed-off-by: Matthew Heon <matthew.heon@pm.me>
-rw-r--r--libpod/boltdb_state.go114
-rw-r--r--libpod/boltdb_state_internal.go8
-rw-r--r--libpod/in_memory_state.go30
-rw-r--r--libpod/state.go4
-rw-r--r--libpod/volume.go31
5 files changed, 183 insertions, 4 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index 1de8d80c9..ff5e62ce2 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -1352,6 +1352,16 @@ func (s *BoltState) AddVolume(volume *Volume) error {
return errors.Wrapf(err, "error marshalling volume %s config to JSON", volume.Name())
}
+ // Volume state is allowed to not exist
+ var volStateJSON []byte
+ if volume.state != nil {
+ stateJSON, err := json.Marshal(volume.state)
+ if err != nil {
+ return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name())
+ }
+ volStateJSON = stateJSON
+ }
+
db, err := s.getDBCon()
if err != nil {
return err
@@ -1392,6 +1402,12 @@ func (s *BoltState) AddVolume(volume *Volume) error {
return errors.Wrapf(err, "error storing volume %s configuration in DB", volume.Name())
}
+ if volStateJSON != nil {
+ if err := newVol.Put(stateKey, volStateJSON); err != nil {
+ return errors.Wrapf(err, "error storing volume %s state 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())
}
@@ -1483,6 +1499,103 @@ func (s *BoltState) RemoveVolume(volume *Volume) error {
return err
}
+// UpdateVolume updates the volume's state from the database.
+func (s *BoltState) UpdateVolume(volume *Volume) error {
+ if !s.valid {
+ return define.ErrDBClosed
+ }
+
+ if !volume.valid {
+ return define.ErrVolumeRemoved
+ }
+
+ newState := new(VolumeState)
+ volumeName := []byte(volume.Name())
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ volBucket, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volToUpdate := volBucket.Bucket(volumeName)
+ if volToUpdate == nil {
+ volume.valid = false
+ return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %s found in database", volume.Name())
+ }
+
+ stateBytes := volToUpdate.Get(stateKey)
+ if stateBytes == nil {
+ // Having no state is valid.
+ // Return nil, use the empty state.
+ return nil
+ }
+
+ if err := json.Unmarshal(stateBytes, newState); err != nil {
+ return errors.Wrapf(err, "error unmarshalling volume %s state", volume.Name())
+ }
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ volume.state = newState
+
+ return nil
+}
+
+// SaveVolume saves the volume's state to the database.
+func (s *BoltState) SaveVolume(volume *Volume) error {
+ if !s.valid {
+ return define.ErrDBClosed
+ }
+
+ if !volume.valid {
+ return define.ErrVolumeRemoved
+ }
+
+ volumeName := []byte(volume.Name())
+
+ var newStateJSON []byte
+ if volume.state != nil {
+ stateJSON, err := json.Marshal(volume.state)
+ if err != nil {
+ return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name())
+ }
+ newStateJSON = stateJSON
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ err = db.Update(func(tx *bolt.Tx) error {
+ volBucket, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ volToUpdate := volBucket.Bucket(volumeName)
+ if volToUpdate == nil {
+ volume.valid = false
+ return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %s found in database", volume.Name())
+ }
+
+ return volToUpdate.Put(stateKey, newStateJSON)
+ })
+ return err
+}
+
// AllVolumes returns all volumes present in the state
func (s *BoltState) AllVolumes() ([]*Volume, error) {
if !s.valid {
@@ -1551,6 +1664,7 @@ func (s *BoltState) Volume(name string) (*Volume, error) {
volume := new(Volume)
volume.config = new(VolumeConfig)
+ volume.state = new(VolumeState)
db, err := s.getDBCon()
if err != nil {
diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go
index 6e4179835..8dc3d1309 100644
--- a/libpod/boltdb_state_internal.go
+++ b/libpod/boltdb_state_internal.go
@@ -449,6 +449,14 @@ func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bu
return errors.Wrapf(err, "error unmarshalling volume %s config from DB", string(name))
}
+ // Volume state is allowed to be nil for legacy compatability
+ volStateBytes := volDB.Get(stateKey)
+ if volStateBytes != nil {
+ if err := json.Unmarshal(volStateBytes, volume.state); err != nil {
+ return errors.Wrapf(err, "error unmarshalling volume %s state from DB", string(name))
+ }
+ }
+
// Get the lock
lock, err := s.runtime.lockManager.RetrieveLock(volume.config.LockID)
if err != nil {
diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go
index a9b735327..280ae5f5c 100644
--- a/libpod/in_memory_state.go
+++ b/libpod/in_memory_state.go
@@ -507,6 +507,36 @@ func (s *InMemoryState) RemoveVolume(volume *Volume) error {
return nil
}
+// UpdateVolume updates a volume from the database.
+// For the in-memory state, this is a no-op.
+func (s *InMemoryState) UpdateVolume(volume *Volume) error {
+ if !volume.valid {
+ return define.ErrVolumeRemoved
+ }
+
+ if _, ok := s.volumes[volume.Name()]; !ok {
+ volume.valid = false
+ return errors.Wrapf(define.ErrNoSuchVolume, "volume with name %q not found in state", volume.Name())
+ }
+
+ return nil
+}
+
+// SaveVolume saves a volume's state to the database.
+// For the in-memory state, this is a no-op.
+func (s *InMemoryState) SaveVolume(volume *Volume) error {
+ if !volume.valid {
+ return define.ErrVolumeRemoved
+ }
+
+ if _, ok := s.volumes[volume.Name()]; !ok {
+ volume.valid = false
+ return errors.Wrapf(define.ErrNoSuchVolume, "volume with name %q not found in state", volume.Name())
+ }
+
+ return nil
+}
+
// VolumeInUse checks if the given volume is being used by at least one container
func (s *InMemoryState) VolumeInUse(volume *Volume) ([]string, error) {
if !volume.valid {
diff --git a/libpod/state.go b/libpod/state.go
index 5d704e69a..db4667ad6 100644
--- a/libpod/state.go
+++ b/libpod/state.go
@@ -203,6 +203,10 @@ type State interface {
// RemoveVolume removes the specified volume.
// Only volumes that have no container dependencies can be removed
RemoveVolume(volume *Volume) error
+ // UpdateVolume updates the volume's state from the database.
+ UpdateVolume(volume *Volume) error
+ // SaveVolume saves a volume's state to the database.
+ SaveVolume(volume *Volume) error
// AllVolumes returns all the volumes available in the state
AllVolumes() ([]*Volume, error)
}
diff --git a/libpod/volume.go b/libpod/volume.go
index e6e92c3ac..5970867c3 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -6,17 +6,20 @@ import (
"github.com/containers/libpod/libpod/lock"
)
-// Volume is the type used to create named volumes
-// TODO: all volumes should be created using this and the Volume API
+// Volume is a libpod named volume.
+// Named volumes may be shared by multiple containers, and may be created using
+// more complex options than normal bind mounts. They may be backed by a mounted
+// filesystem on the host.
type Volume struct {
config *VolumeConfig
+ state *VolumeState
valid bool
runtime *Runtime
lock lock.Locker
}
-// VolumeConfig holds the volume's config information
+// VolumeConfig holds the volume's immutable configuration.
type VolumeConfig struct {
// Name of the volume.
Name string `json:"name"`
@@ -34,7 +37,15 @@ type VolumeConfig struct {
// Options to pass to the volume driver. For the local driver, this is
// a list of mount options. For other drivers, they are passed to the
// volume driver handling the volume.
- Options map[string]string `json:"volumeOptions"`
+ Options map[string]string `json:"volumeOptions,omitempty"`
+ // Type is the type of the volume. This is only used with the local
+ // driver. It the the filesystem that we will attempt to mount - nfs,
+ // tmpfs, etc.
+ Type string `json:"type,omitempty"`
+ // Device is the device of the volume. This is only used with the local
+ // driver, and only with some filesystem types (e.g., not required by
+ // tmpfs). It is the device to mount.
+ Device string `json:"device,omitempty"`
// Whether this volume was created for a specific container and will be
// removed with it.
IsCtrSpecific bool `json:"ctrSpecific"`
@@ -44,6 +55,18 @@ type VolumeConfig struct {
GID int `json:"gid"`
}
+// VolumeState holds the volume's mutable state.
+// Volumes are not guaranteed to have a state. Only volumes using the Local
+// driver that have mount options set will create a state.
+type VolumeState struct {
+ // MountCount is the number of times this volume has been requested to
+ // be mounted.
+ // It is incremented on mount() and decremented on unmount().
+ // On incrementing from 0, the volume will be mounted on the host.
+ // On decrementing to 0, the volume will be unmounted on the host.
+ MountCount uint `json:"mountCount"`
+}
+
// Name retrieves the volume's name
func (v *Volume) Name() string {
return v.config.Name