mirror of
https://github.com/containers/podman.git
synced 2025-10-19 20:23:08 +08:00
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>
This commit is contained in:
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user