summaryrefslogtreecommitdiff
path: root/libpod/runtime_volume_linux.go
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@pm.me>2020-12-04 16:24:56 -0500
committerMatthew Heon <mheon@redhat.com>2021-01-14 15:35:33 -0500
commitb53cb57680a6fd7b383636ac2d6cd71003532915 (patch)
tree3979d1f9763326cd4db3a80742cec3a031ca99b2 /libpod/runtime_volume_linux.go
parent2b7793b6121d336a285fb7b9a7612c221cbf63d2 (diff)
downloadpodman-b53cb57680a6fd7b383636ac2d6cd71003532915.tar.gz
podman-b53cb57680a6fd7b383636ac2d6cd71003532915.tar.bz2
podman-b53cb57680a6fd7b383636ac2d6cd71003532915.zip
Initial implementation of volume plugins
This implements support for mounting and unmounting volumes backed by volume plugins. Support for actually retrieving plugins requires a pull request to land in containers.conf and then that to be vendored, and as such is not yet ready. Given this, this code is only compile tested. However, the code for everything past retrieving the plugin has been written - there is support for creating, removing, mounting, and unmounting volumes, which should allow full functionality once the c/common PR is merged. A major change is the signature of the MountPoint function for volumes, which now, by necessity, returns an error. Named volumes managed by a plugin do not have a mountpoint we control; instead, it is managed entirely by the plugin. As such, we need to cache the path in the DB, and calls to retrieve it now need to access the DB (and may fail as such). Notably absent is support for SELinux relabelling and chowning these volumes. Given that we don't manage the mountpoint for these volumes, I am extremely reluctant to try and modify it - we could easily break the plugin trying to chown or relabel it. Also, we had no less than *5* separate implementations of inspecting a volume floating around in pkg/infra/abi and pkg/api/handlers/libpod. And none of them used volume.Inspect(), the only correct way of inspecting volumes. Remove them all and consolidate to using the correct way. Compat API is likely still doing things the wrong way, but that is an issue for another day. Fixes #4304 Signed-off-by: Matthew Heon <matthew.heon@pm.me>
Diffstat (limited to 'libpod/runtime_volume_linux.go')
-rw-r--r--libpod/runtime_volume_linux.go130
1 files changed, 110 insertions, 20 deletions
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index 9bf0fd108..4a29f01aa 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -11,7 +11,9 @@ import (
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/events"
+ volplugin "github.com/containers/podman/v2/libpod/plugin"
"github.com/containers/storage/pkg/stringid"
+ pluginapi "github.com/docker/go-plugins-helpers/volume"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -53,6 +55,14 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
return nil, errors.Wrapf(define.ErrVolumeExists, "volume with name %s already exists", volume.config.Name)
}
+ // Plugin can be nil if driver is local, but that's OK - superfluous
+ // assignment doesn't hurt much.
+ plugin, err := r.getVolumePlugin(volume.config.Driver)
+ if err != nil {
+ return nil, errors.Wrapf(err, "volume %s uses volume plugin %s but it could not be retrieved", volume.config.Name, volume.config.Driver)
+ }
+ volume.plugin = plugin
+
if volume.config.Driver == define.VolumeDriverLocal {
logrus.Debugf("Validating options for local driver")
// Validate options
@@ -66,25 +76,38 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
}
}
- // Create the mountpoint of this volume
- volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name)
- if err := os.MkdirAll(volPathRoot, 0700); err != nil {
- return nil, errors.Wrapf(err, "error creating volume directory %q", volPathRoot)
- }
- if err := os.Chown(volPathRoot, volume.config.UID, volume.config.GID); err != nil {
- return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID)
- }
- fullVolPath := filepath.Join(volPathRoot, "_data")
- if err := os.MkdirAll(fullVolPath, 0755); err != nil {
- return nil, errors.Wrapf(err, "error creating volume directory %q", fullVolPath)
- }
- if err := os.Chown(fullVolPath, volume.config.UID, volume.config.GID); err != nil {
- return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID)
- }
- if err := LabelVolumePath(fullVolPath); err != nil {
- return nil, err
+ // Now we get conditional: we either need to make the volume in the
+ // volume plugin, or on disk if not using a plugin.
+ if volume.plugin != nil {
+ // We can't chown, or relabel, or similar the path the volume is
+ // using, because it's not managed by us.
+ // TODO: reevaluate this once we actually have volume plugins in
+ // use in production - it may be safe, but I can't tell without
+ // knowing what the actual plugin does...
+ if err := makeVolumeInPluginIfNotExist(volume.config.Name, volume.config.Options, volume.plugin); err != nil {
+ return nil, err
+ }
+ } else {
+ // Create the mountpoint of this volume
+ volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name)
+ if err := os.MkdirAll(volPathRoot, 0700); err != nil {
+ return nil, errors.Wrapf(err, "error creating volume directory %q", volPathRoot)
+ }
+ if err := os.Chown(volPathRoot, volume.config.UID, volume.config.GID); err != nil {
+ return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID)
+ }
+ fullVolPath := filepath.Join(volPathRoot, "_data")
+ if err := os.MkdirAll(fullVolPath, 0755); err != nil {
+ return nil, errors.Wrapf(err, "error creating volume directory %q", fullVolPath)
+ }
+ if err := os.Chown(fullVolPath, volume.config.UID, volume.config.GID); err != nil {
+ return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID)
+ }
+ if err := LabelVolumePath(fullVolPath); err != nil {
+ return nil, err
+ }
+ volume.config.MountPoint = fullVolPath
}
- volume.config.MountPoint = fullVolPath
lock, err := r.lockManager.AllocateLock()
if err != nil {
@@ -111,6 +134,39 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
return volume, nil
}
+// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it
+// does not already exist.
+func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error {
+ // Ping the volume plugin to see if it exists first.
+ // If it does, use the existing volume in the plugin.
+ // Options may not match exactly, but not much we can do about
+ // that. Not complaining avoids a lot of the sync issues we see
+ // with c/storage and libpod DB.
+ needsCreate := true
+ getReq := new(pluginapi.GetRequest)
+ getReq.Name = name
+ if resp, err := plugin.GetVolume(getReq); err == nil {
+ // TODO: What do we do if we get a 200 response, but the
+ // Volume is nil? The docs on the Plugin API are very
+ // nonspecific, so I don't know if this is valid or
+ // not...
+ if resp != nil {
+ needsCreate = false
+ logrus.Infof("Volume %q already exists in plugin %q, using existing volume", name, plugin.Name)
+ }
+ }
+ if needsCreate {
+ createReq := new(pluginapi.CreateRequest)
+ createReq.Name = name
+ createReq.Options = options
+ if err := plugin.CreateVolume(createReq); err != nil {
+ return errors.Wrapf(err, "error creating volume %q in plugin %s", name, plugin.Name)
+ }
+ }
+
+ return nil
+}
+
// removeVolume removes the specified volume from state as well tears down its mountpoint and storage
func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error {
if !v.valid {
@@ -185,9 +241,43 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error
var removalErr error
+ // If we use a volume plugin, we need to remove from the plugin.
+ if v.UsesVolumeDriver() {
+ canRemove := true
+
+ // Do we have a volume driver?
+ if v.plugin == nil {
+ canRemove = false
+ removalErr = errors.Wrapf(define.ErrMissingPlugin, "cannot remove volume %s from plugin %s, but it has been removed from Podman", v.Name(), v.Driver())
+ } else {
+ // Ping the plugin first to verify the volume still
+ // exists.
+ // We're trying to be very tolerant of missing volumes
+ // in the backend, to avoid the problems we see with
+ // sync between c/storage and the Libpod DB.
+ getReq := new(pluginapi.GetRequest)
+ getReq.Name = v.Name()
+ if _, err := v.plugin.GetVolume(getReq); err != nil {
+ canRemove = false
+ removalErr = errors.Wrapf(err, "volume %s could not be retrieved from plugin %s, but it has been removed from Podman", v.Name(), v.Driver())
+ }
+ }
+ if canRemove {
+ req := new(pluginapi.RemoveRequest)
+ req.Name = v.Name()
+ if err := v.plugin.RemoveVolume(req); err != nil {
+ removalErr = errors.Wrapf(err, "volume %s could not be removed from plugin %s, but it has been removed from Podman", v.Name(), v.Driver())
+ }
+ }
+ }
+
// Free the volume's lock
if err := v.lock.Free(); err != nil {
- removalErr = errors.Wrapf(err, "error freeing lock for volume %s", v.Name())
+ if removalErr == nil {
+ removalErr = errors.Wrapf(err, "error freeing lock for volume %s", v.Name())
+ } else {
+ logrus.Errorf("Error freeing lock for volume %q: %v", v.Name(), err)
+ }
}
// Delete the mountpoint path of the volume, that is delete the volume
@@ -196,7 +286,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error
if removalErr == nil {
removalErr = errors.Wrapf(err, "error cleaning up volume storage for %q", v.Name())
} else {
- logrus.Errorf("error cleaning up volume storage for volume %q: %v", v.Name(), err)
+ logrus.Errorf("Error cleaning up volume storage for volume %q: %v", v.Name(), err)
}
}