From 5a8a71ed817a4fa50fd9444846a50b76f25228d1 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 30 Aug 2019 16:09:17 -0400 Subject: 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 --- libpod/boltdb_state.go | 114 ++++++++++++++++++++++++++++++++++++++++ libpod/boltdb_state_internal.go | 8 +++ libpod/in_memory_state.go | 30 +++++++++++ libpod/state.go | 4 ++ libpod/volume.go | 31 +++++++++-- 5 files changed, 183 insertions(+), 4 deletions(-) (limited to 'libpod') 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 -- cgit v1.2.3-54-g00ecf