Skip to content

Commit

Permalink
Merge pull request #8604 from mheon/volume_plugin_impl
Browse files Browse the repository at this point in the history
Initial implementation of volume plugins
  • Loading branch information
openshift-merge-robot authored Jan 15, 2021
2 parents 2b7793b + f781efd commit 8ce9995
Show file tree
Hide file tree
Showing 34 changed files with 1,029 additions and 264 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ release.txt
/test/checkseccomp/checkseccomp
/test/copyimg/copyimg
/test/goecho/goecho
/test/testvol/testvol
.vscode*
result
# Necessary to prevent hack/tree-status.sh false-positive
Expand Down
10 changes: 10 additions & 0 deletions Containerfile-testvol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM golang:1.15-alpine AS build-img
COPY ./test/testvol/ /go/src/github.com/containers/podman/cmd/testvol/
COPY ./vendor /go/src/github.com/containers/podman/vendor/
WORKDIR /go/src/github.com/containers/podman
RUN go build -o /testvol ./cmd/testvol

FROM alpine
COPY --from=build-img /testvol /usr/local/bin
WORKDIR /
ENTRYPOINT ["/usr/local/bin/testvol"]
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ gofmt: ## Verify the source code gofmt
test/checkseccomp/checkseccomp: .gopathok $(wildcard test/checkseccomp/*.go)
$(GO) build $(BUILDFLAGS) -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS)" -o $@ ./test/checkseccomp

.PHONY: test/testvol/testvol
test/testvol/testvol: .gopathok $(wildcard test/testvol/*.go)
$(GO) build $(BUILDFLAGS) -ldflags '$(LDFLAGS_PODMAN)' -o $@ ./test/testvol

.PHONY: volume-plugin-test-image
volume-plugin-test-img:
podman build -t quay.io/libpod/volume-plugin-test-img -f Containerfile-testvol .

.PHONY: test/goecho/goecho
test/goecho/goecho: .gopathok $(wildcard test/goecho/*.go)
$(GO) build $(BUILDFLAGS) -ldflags '$(LDFLAGS_PODMAN)' -o $@ ./test/goecho
Expand Down
10 changes: 6 additions & 4 deletions docs/source/markdown/podman-volume-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ driver options can be set using the **--opt** flag.

#### **--driver**=*driver*

Specify the volume driver name (default local).
Specify the volume driver name (default **local**). Setting this to a value other than **local** Podman will attempt to create the volume using a volume plugin with the given name. Such plugins must be defined in the **volume_plugins** section of the **containers.conf**(5) configuration file.

#### **--help**

Expand All @@ -30,13 +30,14 @@ Set metadata for a volume (e.g., --label mykey=value).
#### **--opt**=*option*, **-o**

Set driver specific options.
For the default driver, `local`, this allows a volume to be configured to mount a filesystem on the host.
For the default driver, **local**, this allows a volume to be configured to mount a filesystem on the host.
For the `local` driver the following options are supported: `type`, `device`, and `o`.
The `type` option sets the type of the filesystem to be mounted, and is equivalent to the `-t` flag to **mount(8)**.
The `device` option sets the device to be mounted, and is equivalent to the `device` argument to **mount(8)**.
The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with two exceptions.
The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**.
Using volume options with the `local` driver requires root privileges.
Using volume options with the **local** driver requires root privileges.
When not using the **local** driver, the given options will be passed directly to the volume plugin. In this case, supported options will be dictated by the plugin in question, not Podman.

## EXAMPLES

Expand All @@ -53,7 +54,8 @@ $ podman volume create --label foo=bar myvol
```

## SEE ALSO
podman-volume(1), mount(8)
**podman-volume**(1), **mount**(8), **containers.conf**(5)

## HISTORY
January 2020, updated with information on volume plugins by Matthew Heon <[email protected]>
November 2018, Originally compiled by Urvashi Mohnani <[email protected]>
1 change: 1 addition & 0 deletions libpod/boltdb_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ func (s *BoltState) Refresh() error {

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

newState, err := json.Marshal(oldState)
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions libpod/boltdb_state_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,21 @@ func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bu
}
}

// Retrieve volume driver
if volume.UsesVolumeDriver() {
plugin, err := s.runtime.getVolumePlugin(volume.config.Driver)
if err != nil {
// We want to fail gracefully here, to ensure that we
// can still remove volumes even if their plugin is
// missing. Otherwise, we end up with volumes that
// cannot even be retrieved from the database and will
// cause things like `volume ls` to fail.
logrus.Errorf("Volume %s uses volume plugin %s, but it cannot be accessed - some functionality may not be available: %v", volume.Name(), volume.config.Driver, err)
} else {
volume.plugin = plugin
}
}

// Get the lock
lock, err := s.runtime.lockManager.RetrieveLock(volume.config.LockID)
if err != nil {
Expand Down
7 changes: 6 additions & 1 deletion libpod/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,12 @@ func (c *Container) getInspectMounts(namedVolumes []*ContainerNamedVolume, image
return nil, errors.Wrapf(err, "error looking up volume %s in container %s config", volume.Name, c.ID())
}
mountStruct.Driver = volFromDB.Driver()
mountStruct.Source = volFromDB.MountPoint()

mountPoint, err := volFromDB.MountPoint()
if err != nil {
return nil, err
}
mountStruct.Source = mountPoint

parseMountOptionsForInspect(volume.Options, &mountStruct)

Expand Down
23 changes: 20 additions & 3 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1580,8 +1580,18 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string)
return nil, err
}

// HACK HACK HACK - copy up into a volume driver is 100% broken
// right now.
if vol.UsesVolumeDriver() {
logrus.Infof("Not copying up into volume %s as it uses a volume driver", vol.Name())
return vol, nil
}

// If the volume is not empty, we should not copy up.
volMount := vol.MountPoint()
volMount, err := vol.MountPoint()
if err != nil {
return nil, err
}
contents, err := ioutil.ReadDir(volMount)
if err != nil {
return nil, errors.Wrapf(err, "error listing contents of volume %s mountpoint when copying up from container %s", vol.Name(), c.ID())
Expand Down Expand Up @@ -1619,7 +1629,11 @@ func (c *Container) chownVolume(volumeName string) error {
return err
}

if vol.state.NeedsChown {
// TODO: For now, I've disabled chowning volumes owned by non-Podman
// drivers. This may be safe, but it's really going to be a case-by-case
// thing, I think - safest to leave disabled now and reenable later if
// there is a demand.
if vol.state.NeedsChown && !vol.UsesVolumeDriver() {
vol.state.NeedsChown = false

uid := int(c.config.Spec.Process.User.UID)
Expand All @@ -1646,7 +1660,10 @@ func (c *Container) chownVolume(volumeName string) error {
return err
}

mountPoint := vol.MountPoint()
mountPoint, err := vol.MountPoint()
if err != nil {
return err
}

if err := os.Lchown(mountPoint, uid, gid); err != nil {
return err
Expand Down
39 changes: 28 additions & 11 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
if err != nil {
return nil, errors.Wrapf(err, "error retrieving volume %s to add to container %s", namedVol.Name, c.ID())
}
mountPoint := volume.MountPoint()
mountPoint, err := volume.MountPoint()
if err != nil {
return nil, err
}
volMount := spec.Mount{
Type: "bind",
Source: mountPoint,
Expand Down Expand Up @@ -903,7 +906,15 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
return err
}

input, err := archive.TarWithOptions(volume.MountPoint(), &archive.TarOptions{
mp, err := volume.MountPoint()
if err != nil {
return err
}
if mp == "" {
return errors.Wrapf(define.ErrInternal, "volume %s is not mounted, cannot export", volume.Name())
}

input, err := archive.TarWithOptions(mp, &archive.TarOptions{
Compression: archive.Uncompressed,
IncludeSourceDir: true,
})
Expand Down Expand Up @@ -958,10 +969,10 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {

func (c *Container) checkpointRestoreSupported() error {
if !criu.CheckForCriu() {
return errors.Errorf("Checkpoint/Restore requires at least CRIU %d", criu.MinCriuVersion)
return errors.Errorf("checkpoint/restore requires at least CRIU %d", criu.MinCriuVersion)
}
if !c.ociRuntime.SupportsCheckpoint() {
return errors.Errorf("Configured runtime does not support checkpoint/restore")
return errors.Errorf("configured runtime does not support checkpoint/restore")
}
return nil
}
Expand Down Expand Up @@ -993,7 +1004,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
}

if c.AutoRemove() && options.TargetFile == "" {
return errors.Errorf("Cannot checkpoint containers that have been started with '--rm' unless '--export' is used")
return errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used")
}

if err := c.checkpointRestoreLabelLog("dump.log"); err != nil {
Expand Down Expand Up @@ -1079,13 +1090,13 @@ func (c *Container) importCheckpoint(input string) error {
}
err = archive.Untar(archiveFile, c.bundlePath(), options)
if err != nil {
return errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input)
return errors.Wrapf(err, "unpacking of checkpoint archive %s failed", input)
}

// Make sure the newly created config.json exists on disk
g := generate.Generator{Config: c.config.Spec}
if err = c.saveSpec(g.Config); err != nil {
return errors.Wrap(err, "Saving imported container specification for restore failed")
return errors.Wrap(err, "saving imported container specification for restore failed")
}

return nil
Expand Down Expand Up @@ -1130,7 +1141,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// Let's try to stat() CRIU's inventory file. If it does not exist, it makes
// no sense to try a restore. This is a minimal check if a checkpoint exist.
if _, err := os.Stat(filepath.Join(c.CheckpointPath(), "inventory.img")); os.IsNotExist(err) {
return errors.Wrapf(err, "A complete checkpoint for this container cannot be found, cannot restore")
return errors.Wrapf(err, "a complete checkpoint for this container cannot be found, cannot restore")
}

if err := c.checkpointRestoreLabelLog("restore.log"); err != nil {
Expand Down Expand Up @@ -1286,16 +1297,22 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti

volumeFile, err := os.Open(volumeFilePath)
if err != nil {
return errors.Wrapf(err, "Failed to open volume file %s", volumeFilePath)
return errors.Wrapf(err, "failed to open volume file %s", volumeFilePath)
}
defer volumeFile.Close()

volume, err := c.runtime.GetVolume(v.Name)
if err != nil {
return errors.Wrapf(err, "Failed to retrieve volume %s", v.Name)
return errors.Wrapf(err, "failed to retrieve volume %s", v.Name)
}

mountPoint := volume.MountPoint()
mountPoint, err := volume.MountPoint()
if err != nil {
return err
}
if mountPoint == "" {
return errors.Wrapf(err, "unable to import volume %s as it is not mounted", volume.Name())
}
if err := archive.UntarUncompressed(volumeFile, mountPoint, nil); err != nil {
return errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint)
}
Expand Down
4 changes: 4 additions & 0 deletions libpod/define/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ var (
// aliases.
ErrNoAliases = errors.New("no aliases for container")

// ErrMissingPlugin indicates that the requested operation requires a
// plugin that is not present on the system or in the configuration.
ErrMissingPlugin = errors.New("required plugin missing")

// ErrCtrExists indicates a container with the same name or ID already
// exists
ErrCtrExists = errors.New("container already exists")
Expand Down
51 changes: 51 additions & 0 deletions libpod/define/volume_inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package define

import (
"time"
)

// InspectVolumeData is the output of Inspect() on a volume. It is matched to
// the format of 'docker volume inspect'.
type InspectVolumeData struct {
// Name is the name of the volume.
Name string `json:"Name"`
// Driver is the driver used to create the volume.
// If set to "local" or "", the Local driver (Podman built-in code) is
// used to service the volume; otherwise, a volume plugin with the given
// name is used to mount and manage the volume.
Driver string `json:"Driver"`
// Mountpoint is the path on the host where the volume is mounted.
Mountpoint string `json:"Mountpoint"`
// CreatedAt is the date and time the volume was created at. This is not
// stored for older Libpod volumes; if so, it will be omitted.
CreatedAt time.Time `json:"CreatedAt,omitempty"`
// Status is used to return information on the volume's current state,
// if the volume was created using a volume plugin (uses a Driver that
// is not the local driver).
// Status is provided to us by an external program, so no guarantees are
// made about its format or contents. Further, it is an optional field,
// so it may not be set even in cases where a volume plugin is in use.
Status map[string]interface{} `json:"Status,omitempty"`
// Labels includes the volume's configured labels, key:value pairs that
// can be passed during volume creation to provide information for third
// party tools.
Labels map[string]string `json:"Labels"`
// Scope is unused and provided solely for Docker compatibility. It is
// unconditionally set to "local".
Scope string `json:"Scope"`
// Options is a set of options that were used when creating the volume.
// For the Local driver, these are mount options that will be used to
// determine how a local filesystem is mounted; they are handled as
// parameters to Mount in a manner described in the volume create
// manpage.
// For non-local drivers, these are passed as-is to the volume plugin.
Options map[string]string `json:"Options"`
// UID is the UID that the volume was created with.
UID int `json:"UID,omitempty"`
// GID is the GID that the volume was created with.
GID int `json:"GID,omitempty"`
// Anonymous indicates that the volume was created as an anonymous
// volume for a specific container, and will be be removed when any
// container using it is removed.
Anonymous bool `json:"Anonymous,omitempty"`
}
11 changes: 0 additions & 11 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -1549,17 +1549,6 @@ func WithVolumeDriver(driver string) VolumeCreateOption {
return define.ErrVolumeFinalized
}

// Uncomment when volume plugins are ready for use.
// if driver != define.VolumeDriverLocal {
// if _, err := plugin.GetVolumePlugin(driver); err != nil {
// return err
// }
// }

if driver != define.VolumeDriverLocal {
return define.ErrNotImplemented
}

volume.config.Driver = driver
return nil
}
Expand Down
Loading

0 comments on commit 8ce9995

Please sign in to comment.