Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mounting volumes with local driver and options #3931

Merged
merged 4 commits into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/podman-volume-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ $ podman volume create myvol
$ podman volume create

$ podman volume create --label foo=bar myvol

$ podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=nodev,noexec myvol
```

## SEE ALSO
Expand Down
162 changes: 162 additions & 0 deletions libpod/boltdb_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ func (s *BoltState) Refresh() error {
return err
}

allVolsBucket, err := getAllVolsBucket(tx)
if err != nil {
return err
}

volBucket, err := getVolBucket(tx)
if err != nil {
return err
}

// Iterate through all IDs. Check if they are containers.
// If they are, unmarshal their state, and then clear
// PID, mountpoint, and state for all of them
Expand Down Expand Up @@ -235,6 +245,44 @@ func (s *BoltState) Refresh() error {

return nil
})
if err != nil {
return err
}

// Now refresh volumes
err = allVolsBucket.ForEach(func(id, name []byte) error {
dbVol := volBucket.Bucket(id)
if dbVol == nil {
return errors.Wrapf(define.ErrInternal, "inconsistency in state - volume %s is in all volumes bucket but volume not found", string(id))
}

// Get the state
volStateBytes := dbVol.Get(stateKey)
if volStateBytes == nil {
// If the volume doesn't have a state, nothing to do
return nil
}

oldState := new(VolumeState)

if err := json.Unmarshal(volStateBytes, oldState); err != nil {
return errors.Wrapf(err, "error unmarshalling state for volume %s", string(id))
}

// Reset mount count to 0
oldState.MountCount = 0

newState, err := json.Marshal(oldState)
if err != nil {
return errors.Wrapf(err, "error marshalling state for volume %s", string(id))
}

if err := dbVol.Put(stateKey, newState); err != nil {
return errors.Wrapf(err, "error storing new state for volume %s", string(id))
}

return nil
})
return err
})
return err
Expand Down Expand Up @@ -1352,6 +1400,15 @@ 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 {
volStateJSON, err = json.Marshal(volume.state)
if err != nil {
return errors.Wrapf(err, "error marshalling volume %s state to JSON", volume.Name())
}
}

db, err := s.getDBCon()
if err != nil {
return err
Expand Down Expand Up @@ -1392,6 +1449,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())
}
Expand Down Expand Up @@ -1483,6 +1546,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 {
Expand Down Expand Up @@ -1517,6 +1677,7 @@ func (s *BoltState) AllVolumes() ([]*Volume, error) {

volume := new(Volume)
volume.config = new(VolumeConfig)
volume.state = new(VolumeState)

if err := s.getVolumeFromDB(id, volume, volBucket); err != nil {
if errors.Cause(err) != define.ErrNSMismatch {
Expand Down Expand Up @@ -1551,6 +1712,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 {
Expand Down
8 changes: 8 additions & 0 deletions libpod/boltdb_state_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 compatibility
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 {
Expand Down
86 changes: 79 additions & 7 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e
// TODO: Add ability to override mount label so we can use this for Mount() too
// TODO: Can we use this for export? Copying SHM into the export might not be
// good
func (c *Container) mountStorage() (string, error) {
func (c *Container) mountStorage() (_ string, Err error) {
var err error
// Container already mounted, nothing to do
if c.state.Mounted {
Expand All @@ -1225,6 +1225,40 @@ func (c *Container) mountStorage() (string, error) {
if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil {
return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir)
}
defer func() {
if Err != nil {
if err := c.unmountSHM(c.config.ShmDir); err != nil {
logrus.Errorf("Error unmounting SHM for container %s after mount error: %v", c.ID(), err)
}
}
}()
}

// Request a mount of all named volumes
for _, v := range c.config.NamedVolumes {
vol, err := c.runtime.state.Volume(v.Name)
if err != nil {
return "", errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID())
}

if vol.needsMount() {
vol.lock.Lock()
if err := vol.mount(); err != nil {
vol.lock.Unlock()
return "", errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID())
}
vol.lock.Unlock()
defer func() {
if Err == nil {
return
}
vol.lock.Lock()
if err := vol.unmount(false); err != nil {
logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err)
}
vol.lock.Unlock()
}()
}
}

// TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts
Expand All @@ -1247,14 +1281,19 @@ func (c *Container) cleanupStorage() error {
return nil
}

var cleanupErr error

for _, containerMount := range c.config.Mounts {
if err := c.unmountSHM(containerMount); err != nil {
return err
if cleanupErr != nil {
logrus.Errorf("Error unmounting container %s: %v", c.ID(), cleanupErr)
}
cleanupErr = err
}
}

if c.config.Rootfs != "" {
return nil
return cleanupErr
}

if err := c.unmount(false); err != nil {
Expand All @@ -1264,19 +1303,52 @@ func (c *Container) cleanupStorage() error {
// state
if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown {
logrus.Errorf("Storage for container %s has been removed", c.ID())
return nil
} else {
if cleanupErr != nil {
logrus.Errorf("Error cleaning up container %s storage: %v", c.ID(), cleanupErr)
}
cleanupErr = err
}
}

return err
// Request an unmount of all named volumes
for _, v := range c.config.NamedVolumes {
vol, err := c.runtime.state.Volume(v.Name)
if err != nil {
if cleanupErr != nil {
logrus.Errorf("Error unmounting container %s: %v", c.ID(), cleanupErr)
}
cleanupErr = errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID())

// We need to try and unmount every volume, so continue
// if they fail.
continue
}

if vol.needsMount() {
vol.lock.Lock()
if err := vol.unmount(false); err != nil {
if cleanupErr != nil {
logrus.Errorf("Error unmounting container %s: %v", c.ID(), cleanupErr)
}
cleanupErr = errors.Wrapf(err, "error unmounting volume %s for container %s", vol.Name(), c.ID())
}
vol.lock.Unlock()
}
}

c.state.Mountpoint = ""
c.state.Mounted = false

if c.valid {
return c.save()
if err := c.save(); err != nil {
if cleanupErr != nil {
logrus.Errorf("Error unmounting container %s: %v", c.ID(), cleanupErr)
}
cleanupErr = err
}
}
return nil
return cleanupErr
}

// Unmount the a container and free its resources
Expand Down
Loading