summaryrefslogtreecommitdiff
path: root/volume
diff options
context:
space:
mode:
authorSebastiaan van Stijn <thaJeztah@users.noreply.github.com>2018-06-05 02:16:20 +0200
committerGitHub <noreply@github.com>2018-06-05 02:16:20 +0200
commit5037c5a8ce762b46638378b7a7d1081572eadba1 (patch)
treeb2182ded0b76dda136f44681c963e908068b9650 /volume
parentd7e94d6ea7c1a76c79b6da722491c79118236999 (diff)
parente4b6adc88e967971de634596654d9bc33e7bd7e0 (diff)
downloaddocker-5037c5a8ce762b46638378b7a7d1081572eadba1.tar.gz
Merge pull request #36688 from cpuguy83/volumes_service
Extract volume interaction to a volumes service
Diffstat (limited to 'volume')
-rw-r--r--volume/drivers/adapter.go4
-rw-r--r--volume/drivers/extpoint.go2
-rw-r--r--volume/service/by.go89
-rw-r--r--volume/service/convert.go132
-rw-r--r--volume/service/db.go (renamed from volume/store/db.go)2
-rw-r--r--volume/service/db_test.go (renamed from volume/store/db_test.go)2
-rw-r--r--volume/service/default_driver.go21
-rw-r--r--volume/service/default_driver_stubs.go10
-rw-r--r--volume/service/errors.go (renamed from volume/store/errors.go)18
-rw-r--r--volume/service/opts/opts.go89
-rw-r--r--volume/service/restore.go (renamed from volume/store/restore.go)9
-rw-r--r--volume/service/restore_test.go (renamed from volume/store/restore_test.go)17
-rw-r--r--volume/service/service.go243
-rw-r--r--volume/service/service_linux_test.go67
-rw-r--r--volume/service/service_test.go253
-rw-r--r--volume/service/store.go (renamed from volume/store/store.go)443
-rw-r--r--volume/service/store_test.go (renamed from volume/store/store_test.go)202
-rw-r--r--volume/service/store_unix.go (renamed from volume/store/store_unix.go)4
-rw-r--r--volume/service/store_windows.go (renamed from volume/store/store_windows.go)2
-rw-r--r--volume/testutils/testutils.go4
20 files changed, 1367 insertions, 246 deletions
diff --git a/volume/drivers/adapter.go b/volume/drivers/adapter.go
index 2d891383cc..f6ee07a006 100644
--- a/volume/drivers/adapter.go
+++ b/volume/drivers/adapter.go
@@ -94,7 +94,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
if err != nil {
// `GetCapabilities` is a not a required endpoint.
// On error assume it's a local-only driver
- logrus.Warnf("Volume driver %s returned an error while trying to query its capabilities, using default capabilities: %v", a.name, err)
+ logrus.WithError(err).WithField("driver", a.name).Debug("Volume driver returned an error while trying to query its capabilities, using default capabilities")
return volume.Capability{Scope: volume.LocalScope}
}
@@ -105,7 +105,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
cap.Scope = strings.ToLower(cap.Scope)
if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope {
- logrus.Warnf("Volume driver %q returned an invalid scope: %q", a.Name(), cap.Scope)
+ logrus.WithField("driver", a.Name()).WithField("scope", a.Scope).Warn("Volume driver returned an invalid scope")
cap.Scope = volume.LocalScope
}
diff --git a/volume/drivers/extpoint.go b/volume/drivers/extpoint.go
index 5a3c4ce95a..b2131c20ef 100644
--- a/volume/drivers/extpoint.go
+++ b/volume/drivers/extpoint.go
@@ -165,10 +165,10 @@ func (s *Store) ReleaseDriver(name string) (volume.Driver, error) {
func (s *Store) GetDriverList() []string {
var driverList []string
s.mu.Lock()
+ defer s.mu.Unlock()
for driverName := range s.extensions {
driverList = append(driverList, driverName)
}
- s.mu.Unlock()
sort.Strings(driverList)
return driverList
}
diff --git a/volume/service/by.go b/volume/service/by.go
new file mode 100644
index 0000000000..c5a4638d2a
--- /dev/null
+++ b/volume/service/by.go
@@ -0,0 +1,89 @@
+package service // import "github.com/docker/docker/volume/service"
+
+import (
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/volume"
+)
+
+// By is an interface which is used to implement filtering on volumes.
+type By interface {
+ isBy()
+}
+
+// ByDriver is `By` that filters based on the driver names that are passed in
+func ByDriver(drivers ...string) By {
+ return byDriver(drivers)
+}
+
+type byDriver []string
+
+func (byDriver) isBy() {}
+
+// ByReferenced is a `By` that filters based on if the volume has references
+type ByReferenced bool
+
+func (ByReferenced) isBy() {}
+
+// And creates a `By` combining all the passed in bys using AND logic.
+func And(bys ...By) By {
+ and := make(andCombinator, 0, len(bys))
+ for _, by := range bys {
+ and = append(and, by)
+ }
+ return and
+}
+
+type andCombinator []By
+
+func (andCombinator) isBy() {}
+
+// Or creates a `By` combining all the passed in bys using OR logic.
+func Or(bys ...By) By {
+ or := make(orCombinator, 0, len(bys))
+ for _, by := range bys {
+ or = append(or, by)
+ }
+ return or
+}
+
+type orCombinator []By
+
+func (orCombinator) isBy() {}
+
+// CustomFilter is a `By` that is used by callers to provide custom filtering
+// logic.
+type CustomFilter filterFunc
+
+func (CustomFilter) isBy() {}
+
+// FromList returns a By which sets the initial list of volumes to use
+func FromList(ls *[]volume.Volume, by By) By {
+ return &fromList{by: by, ls: ls}
+}
+
+type fromList struct {
+ by By
+ ls *[]volume.Volume
+}
+
+func (fromList) isBy() {}
+
+func byLabelFilter(filter filters.Args) By {
+ return CustomFilter(func(v volume.Volume) bool {
+ dv, ok := v.(volume.DetailedVolume)
+ if !ok {
+ return false
+ }
+
+ labels := dv.Labels()
+ if !filter.MatchKVList("label", labels) {
+ return false
+ }
+ if filter.Contains("label!") {
+ if filter.MatchKVList("label!", labels) {
+ return false
+ }
+ }
+ return true
+ })
+}
diff --git a/volume/service/convert.go b/volume/service/convert.go
new file mode 100644
index 0000000000..2967dc6722
--- /dev/null
+++ b/volume/service/convert.go
@@ -0,0 +1,132 @@
+package service
+
+import (
+ "context"
+ "time"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/pkg/directory"
+ "github.com/docker/docker/volume"
+ "github.com/sirupsen/logrus"
+)
+
+// convertOpts are used to pass options to `volumeToAPI`
+type convertOpt interface {
+ isConvertOpt()
+}
+
+type useCachedPath bool
+
+func (useCachedPath) isConvertOpt() {}
+
+type calcSize bool
+
+func (calcSize) isConvertOpt() {}
+
+type pathCacher interface {
+ CachedPath() string
+}
+
+func (s *VolumesService) volumesToAPI(ctx context.Context, volumes []volume.Volume, opts ...convertOpt) []*types.Volume {
+ var (
+ out = make([]*types.Volume, 0, len(volumes))
+ getSize bool
+ cachedPath bool
+ )
+
+ for _, o := range opts {
+ switch t := o.(type) {
+ case calcSize:
+ getSize = bool(t)
+ case useCachedPath:
+ cachedPath = bool(t)
+ }
+ }
+ for _, v := range volumes {
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ }
+ apiV := volumeToAPIType(v)
+
+ if cachedPath {
+ if vv, ok := v.(pathCacher); ok {
+ apiV.Mountpoint = vv.CachedPath()
+ }
+ } else {
+ apiV.Mountpoint = v.Path()
+ }
+
+ if getSize {
+ p := v.Path()
+ if apiV.Mountpoint == "" {
+ apiV.Mountpoint = p
+ }
+ sz, err := directory.Size(ctx, p)
+ if err != nil {
+ logrus.WithError(err).WithField("volume", v.Name()).Warnf("Failed to determine size of volume")
+ sz = -1
+ }
+ apiV.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(s.vs.CountReferences(v))}
+ }
+
+ out = append(out, &apiV)
+ }
+ return out
+}
+
+func volumeToAPIType(v volume.Volume) types.Volume {
+ createdAt, _ := v.CreatedAt()
+ tv := types.Volume{
+ Name: v.Name(),
+ Driver: v.DriverName(),
+ CreatedAt: createdAt.Format(time.RFC3339),
+ }
+ if v, ok := v.(volume.DetailedVolume); ok {
+ tv.Labels = v.Labels()
+ tv.Options = v.Options()
+ tv.Scope = v.Scope()
+ }
+ if cp, ok := v.(pathCacher); ok {
+ tv.Mountpoint = cp.CachedPath()
+ }
+ return tv
+}
+
+func filtersToBy(filter filters.Args, acceptedFilters map[string]bool) (By, error) {
+ if err := filter.Validate(acceptedFilters); err != nil {
+ return nil, err
+ }
+ var bys []By
+ if drivers := filter.Get("driver"); len(drivers) > 0 {
+ bys = append(bys, ByDriver(drivers...))
+ }
+ if filter.Contains("name") {
+ bys = append(bys, CustomFilter(func(v volume.Volume) bool {
+ return filter.Match("name", v.Name())
+ }))
+ }
+ bys = append(bys, byLabelFilter(filter))
+
+ if filter.Contains("dangling") {
+ var dangling bool
+ if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
+ dangling = true
+ } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
+ return nil, invalidFilter{"dangling", filter.Get("dangling")}
+ }
+ bys = append(bys, ByReferenced(!dangling))
+ }
+
+ var by By
+ switch len(bys) {
+ case 0:
+ case 1:
+ by = bys[0]
+ default:
+ by = And(bys...)
+ }
+ return by, nil
+}
diff --git a/volume/store/db.go b/volume/service/db.go
index 5a280ca2dc..3b31f7bf14 100644
--- a/volume/store/db.go
+++ b/volume/service/db.go
@@ -1,4 +1,4 @@
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
import (
"encoding/json"
diff --git a/volume/store/db_test.go b/volume/service/db_test.go
index 0a2727e749..14ad87a514 100644
--- a/volume/store/db_test.go
+++ b/volume/service/db_test.go
@@ -1,4 +1,4 @@
-package store
+package service // import "github.com/docker/docker/volume/service"
import (
"io/ioutil"
diff --git a/volume/service/default_driver.go b/volume/service/default_driver.go
new file mode 100644
index 0000000000..1c1d5c54bc
--- /dev/null
+++ b/volume/service/default_driver.go
@@ -0,0 +1,21 @@
+// +build linux windows
+
+package service // import "github.com/docker/docker/volume/service"
+import (
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/docker/volume"
+ "github.com/docker/docker/volume/drivers"
+ "github.com/docker/docker/volume/local"
+ "github.com/pkg/errors"
+)
+
+func setupDefaultDriver(store *drivers.Store, root string, rootIDs idtools.IDPair) error {
+ d, err := local.New(root, rootIDs)
+ if err != nil {
+ return errors.Wrap(err, "error setting up default driver")
+ }
+ if !store.Register(d, volume.DefaultDriverName) {
+ return errors.New("local volume driver could not be registered")
+ }
+ return nil
+}
diff --git a/volume/service/default_driver_stubs.go b/volume/service/default_driver_stubs.go
new file mode 100644
index 0000000000..fdb275eb9d
--- /dev/null
+++ b/volume/service/default_driver_stubs.go
@@ -0,0 +1,10 @@
+// +build !linux,!windows
+
+package service // import "github.com/docker/docker/volume/service"
+
+import (
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/docker/volume/drivers"
+)
+
+func setupDefaultDriver(_ *drivers.Store, _ string, _ idtools.IDPair) error { return nil }
diff --git a/volume/store/errors.go b/volume/service/errors.go
index 268a7ab2dd..ce2d678dab 100644
--- a/volume/store/errors.go
+++ b/volume/service/errors.go
@@ -1,6 +1,7 @@
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
import (
+ "fmt"
"strings"
)
@@ -93,3 +94,18 @@ func isErr(err error, expected error) bool {
}
return err == expected
}
+
+type invalidFilter struct {
+ filter string
+ value interface{}
+}
+
+func (e invalidFilter) Error() string {
+ msg := "Invalid filter '" + e.filter
+ if e.value != nil {
+ msg += fmt.Sprintf("=%s", e.value)
+ }
+ return msg + "'"
+}
+
+func (e invalidFilter) InvalidParameter() {}
diff --git a/volume/service/opts/opts.go b/volume/service/opts/opts.go
new file mode 100644
index 0000000000..6c7e5f4ea6
--- /dev/null
+++ b/volume/service/opts/opts.go
@@ -0,0 +1,89 @@
+package opts
+
+// CreateOption is used to pass options in when creating a volume
+type CreateOption func(*CreateConfig)
+
+// CreateConfig is the set of config options that can be set when creating
+// a volume
+type CreateConfig struct {
+ Options map[string]string
+ Labels map[string]string
+ Reference string
+}
+
+// WithCreateLabels creates a CreateOption which sets the labels to the
+// passed in value
+func WithCreateLabels(labels map[string]string) CreateOption {
+ return func(cfg *CreateConfig) {
+ cfg.Labels = labels
+ }
+}
+
+// WithCreateOptions creates a CreateOption which sets the options passed
+// to the volume driver when creating a volume to the options passed in.
+func WithCreateOptions(opts map[string]string) CreateOption {
+ return func(cfg *CreateConfig) {
+ cfg.Options = opts
+ }
+}
+
+// WithCreateReference creats a CreateOption which sets a reference to use
+// when creating a volume. This ensures that the volume is created with a reference
+// already attached to it to prevent race conditions with Create and volume cleanup.
+func WithCreateReference(ref string) CreateOption {
+ return func(cfg *CreateConfig) {
+ cfg.Reference = ref
+ }
+}
+
+// GetConfig is used with `GetOption` to set options for the volumes service's
+// `Get` implementation.
+type GetConfig struct {
+ Driver string
+ Reference string
+ ResolveStatus bool
+}
+
+// GetOption is passed to the service `Get` add extra details on the get request
+type GetOption func(*GetConfig)
+
+// WithGetDriver provides the driver to get the volume from
+// If no driver is provided to `Get`, first the available metadata is checked
+// to see which driver it belongs to, if that is not available all drivers are
+// probed to find the volume.
+func WithGetDriver(name string) GetOption {
+ return func(o *GetConfig) {
+ o.Driver = name
+ }
+}
+
+// WithGetReference indicates to `Get` to increment the reference count for the
+// retreived volume with the provided reference ID.
+func WithGetReference(ref string) GetOption {
+ return func(o *GetConfig) {
+ o.Reference = ref
+ }
+}
+
+// WithGetResolveStatus indicates to `Get` to also fetch the volume status.
+// This can cause significant overhead in the volume lookup.
+func WithGetResolveStatus(cfg *GetConfig) {
+ cfg.ResolveStatus = true
+}
+
+// RemoveConfig is used by `RemoveOption` to store config options for remove
+type RemoveConfig struct {
+ PurgeOnError bool
+}
+
+// RemoveOption is used to pass options to the volumes service `Remove` implementation
+type RemoveOption func(*RemoveConfig)
+
+// WithPurgeOnError is an option passed to `Remove` which will purge all cached
+// data about a volume even if there was an error while attempting to remove the
+// volume.
+func WithPurgeOnError(b bool) RemoveOption {
+ return func(o *RemoveConfig) {
+ o.PurgeOnError = b
+ }
+}
diff --git a/volume/store/restore.go b/volume/service/restore.go
index 2e072ec087..55c66c4f42 100644
--- a/volume/store/restore.go
+++ b/volume/service/restore.go
@@ -1,6 +1,7 @@
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
import (
+ "context"
"sync"
"github.com/boltdb/bolt"
@@ -20,6 +21,7 @@ func (s *VolumeStore) restore() {
ls = listMeta(tx)
return nil
})
+ ctx := context.Background()
chRemove := make(chan *volumeMetadata, len(ls))
var wg sync.WaitGroup
@@ -32,7 +34,7 @@ func (s *VolumeStore) restore() {
var v volume.Volume
var err error
if meta.Driver != "" {
- v, err = lookupVolume(s.drivers, meta.Driver, meta.Name)
+ v, err = lookupVolume(ctx, s.drivers, meta.Driver, meta.Name)
if err != nil && err != errNoSuchVolume {
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
return
@@ -43,7 +45,7 @@ func (s *VolumeStore) restore() {
return
}
} else {
- v, err = s.getVolume(meta.Name)
+ v, err = s.getVolume(ctx, meta.Name, meta.Driver)
if err != nil {
if err == errNoSuchVolume {
chRemove <- &meta
@@ -65,6 +67,7 @@ func (s *VolumeStore) restore() {
s.options[v.Name()] = meta.Options
s.labels[v.Name()] = meta.Labels
s.names[v.Name()] = v
+ s.refs[v.Name()] = make(map[string]struct{})
s.globalLock.Unlock()
}(meta)
}
diff --git a/volume/store/restore_test.go b/volume/service/restore_test.go
index 5c3c6df72c..d3c6c9f92c 100644
--- a/volume/store/restore_test.go
+++ b/volume/service/restore_test.go
@@ -1,12 +1,14 @@
-package store
+package service // import "github.com/docker/docker/volume/service"
import (
+ "context"
"io/ioutil"
"os"
"testing"
"github.com/docker/docker/volume"
volumedrivers "github.com/docker/docker/volume/drivers"
+ "github.com/docker/docker/volume/service/opts"
volumetestutils "github.com/docker/docker/volume/testutils"
"github.com/gotestyourself/gotestyourself/assert"
)
@@ -22,24 +24,25 @@ func TestRestore(t *testing.T) {
driverName := "test-restore"
drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
- s, err := New(dir, drivers)
+ s, err := NewStore(dir, drivers)
assert.NilError(t, err)
defer s.Shutdown()
- _, err = s.Create("test1", driverName, nil, nil)
+ ctx := context.Background()
+ _, err = s.Create(ctx, "test1", driverName)
assert.NilError(t, err)
testLabels := map[string]string{"a": "1"}
testOpts := map[string]string{"foo": "bar"}
- _, err = s.Create("test2", driverName, testOpts, testLabels)
+ _, err = s.Create(ctx, "test2", driverName, opts.WithCreateOptions(testOpts), opts.WithCreateLabels(testLabels))
assert.NilError(t, err)
s.Shutdown()
- s, err = New(dir, drivers)
+ s, err = NewStore(dir, drivers)
assert.NilError(t, err)
- v, err := s.Get("test1")
+ v, err := s.Get(ctx, "test1")
assert.NilError(t, err)
dv := v.(volume.DetailedVolume)
@@ -47,7 +50,7 @@ func TestRestore(t *testing.T) {
assert.DeepEqual(t, nilMap, dv.Options())
assert.DeepEqual(t, nilMap, dv.Labels())
- v, err = s.Get("test2")
+ v, err = s.Get(ctx, "test2")
assert.NilError(t, err)
dv = v.(volume.DetailedVolume)
assert.DeepEqual(t, testOpts, dv.Options())
diff --git a/volume/service/service.go b/volume/service/service.go
new file mode 100644
index 0000000000..a62a32de50
--- /dev/null
+++ b/volume/service/service.go
@@ -0,0 +1,243 @@
+package service // import "github.com/docker/docker/volume/service"
+
+import (
+ "context"
+ "sync/atomic"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/errdefs"
+ "github.com/docker/docker/pkg/directory"
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/docker/pkg/plugingetter"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/volume"
+ "github.com/docker/docker/volume/drivers"
+ "github.com/docker/docker/volume/service/opts"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type ds interface {
+ GetDriverList() []string
+}
+
+type volumeEventLogger interface {
+ LogVolumeEvent(volumeID, action string, attributes map[string]string)
+}
+
+// VolumesService manages access to volumes
+type VolumesService struct {
+ vs *VolumeStore
+ ds ds
+ pruneRunning int32
+ eventLogger volumeEventLogger
+}
+
+// NewVolumeService creates a new volume service
+func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.IDPair, logger volumeEventLogger) (*VolumesService, error) {
+ ds := drivers.NewStore(pg)
+ if err := setupDefaultDriver(ds, root, rootIDs); err != nil {
+ return nil, err
+ }
+
+ vs, err := NewStore(root, ds)
+ if err != nil {
+ return nil, err
+ }
+ return &VolumesService{vs: vs, ds: ds, eventLogger: logger}, nil
+}
+
+// GetDriverList gets the list of registered volume drivers
+func (s *VolumesService) GetDriverList() []string {
+ return s.ds.GetDriverList()
+}
+
+// Create creates a volume
+func (s *VolumesService) Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) {
+ if name == "" {
+ name = stringid.GenerateNonCryptoID()
+ }
+ v, err := s.vs.Create(ctx, name, driverName, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ s.eventLogger.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
+ apiV := volumeToAPIType(v)
+ return &apiV, nil
+}
+
+// Get gets a volume
+func (s *VolumesService) Get(ctx context.Context, name string, getOpts ...opts.GetOption) (*types.Volume, error) {
+ v, err := s.vs.Get(ctx, name, getOpts...)
+ if err != nil {
+ return nil, err
+ }
+ vol := volumeToAPIType(v)
+
+ var cfg opts.GetConfig
+ for _, o := range getOpts {
+ o(&cfg)
+ }
+
+ if cfg.ResolveStatus {
+ vol.Status = v.Status()
+ }
+ return &vol, nil
+}
+
+// Mount mounts the volume
+func (s *VolumesService) Mount(ctx context.Context, vol *types.Volume, ref string) (string, error) {
+ v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver))
+ if err != nil {
+ if IsNotExist(err) {
+ err = errdefs.NotFound(err)
+ }
+ return "", err
+ }
+ return v.Mount(ref)
+}
+
+// Unmount unmounts the volume.
+// Note that depending on the implementation, the volume may still be mounted due to other resources using it.
+func (s *VolumesService) Unmount(ctx context.Context, vol *types.Volume, ref string) error {
+ v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver))
+ if err != nil {
+ if IsNotExist(err) {
+ err = errdefs.NotFound(err)
+ }
+ return err
+ }
+ return v.Unmount(ref)
+}
+
+// Release releases a volume reference
+func (s *VolumesService) Release(ctx context.Context, name string, ref string) error {
+ return s.vs.Release(ctx, name, ref)
+}
+
+// Remove removes a volume
+func (s *VolumesService) Remove(ctx context.Context, name string, rmOpts ...opts.RemoveOption) error {
+ var cfg opts.RemoveConfig
+ for _, o := range rmOpts {
+ o(&cfg)
+ }
+
+ v, err := s.vs.Get(ctx, name)
+ if err != nil {
+ if IsNotExist(err) && cfg.PurgeOnError {
+ return nil
+ }
+ return err
+ }
+
+ err = s.vs.Remove(ctx, v, rmOpts...)
+ if IsNotExist(err) {
+ err = nil
+ } else if IsInUse(err) {
+ err = errdefs.Conflict(err)
+ } else if IsNotExist(err) && cfg.PurgeOnError {
+ err = nil
+ }
+
+ if err == nil {
+ s.eventLogger.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
+ }
+ return err
+}
+
+var acceptedPruneFilters = map[string]bool{
+ "label": true,
+ "label!": true,
+}
+
+var acceptedListFilters = map[string]bool{
+ "dangling": true,
+ "name": true,
+ "driver": true,
+ "label": true,
+}
+
+// LocalVolumesSize gets all local volumes and fetches their size on disk
+// Note that this intentionally skips volumes which have mount options. Typically
+// volumes with mount options are not really local even if they are using the
+// local driver.
+func (s *VolumesService) LocalVolumesSize(ctx context.Context) ([]*types.Volume, error) {
+ ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), CustomFilter(func(v volume.Volume) bool {
+ dv, ok := v.(volume.DetailedVolume)
+ return ok && len(dv.Options()) == 0
+ })))
+ if err != nil {
+ return nil, err
+ }
+ return s.volumesToAPI(ctx, ls, calcSize(true)), nil
+}
+
+// Prune removes (local) volumes which match the past in filter arguments.
+// Note that this intentionally skips volumes with mount options as there would
+// be no space reclaimed in this case.
+func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types.VolumesPruneReport, error) {
+ if !atomic.CompareAndSwapInt32(&s.pruneRunning, 0, 1) {
+ return nil, errdefs.Conflict(errors.New("a prune operation is already running"))
+ }
+ defer atomic.StoreInt32(&s.pruneRunning, 0)
+
+ by, err := filtersToBy(filter, acceptedPruneFilters)
+ if err != nil {
+ return nil, err
+ }
+ ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), ByReferenced(false), by, CustomFilter(func(v volume.Volume) bool {
+ dv, ok := v.(volume.DetailedVolume)
+ return ok && len(dv.Options()) == 0
+ })))
+ if err != nil {
+ return nil, err
+ }
+
+ rep := &types.VolumesPruneReport{VolumesDeleted: make([]string, 0, len(ls))}
+ for _, v := range ls {
+ select {
+ case <-ctx.Done():
+ err := ctx.Err()
+ if err == context.Canceled {
+ err = nil
+ }
+ return rep, err
+ default:
+ }
+
+ vSize, err := directory.Size(ctx, v.Path())
+ if err != nil {
+ logrus.WithField("volume", v.Name()).WithError(err).Warn("could not determine size of volume")
+ }
+ if err := s.vs.Remove(ctx, v); err != nil {
+ logrus.WithError(err).WithField("volume", v.Name()).Warnf("Could not determine size of volume")
+ continue
+ }
+ rep.SpaceReclaimed += uint64(vSize)
+ rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name())
+ }
+ return rep, nil
+}
+
+// List gets the list of volumes which match the past in filters
+// If filters is nil or empty all volumes are returned.
+func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) {
+ by, err := filtersToBy(filter, acceptedListFilters)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ volumes, warnings, err := s.vs.Find(ctx, by)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil
+}
+
+// Shutdown shuts down the image service and dependencies
+func (s *VolumesService) Shutdown() error {
+ return s.vs.Shutdown()
+}
diff --git a/volume/service/service_linux_test.go b/volume/service/service_linux_test.go
new file mode 100644
index 0000000000..e3c88741c6
--- /dev/null
+++ b/volume/service/service_linux_test.go
@@ -0,0 +1,67 @@
+package service
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/docker/docker/pkg/idtools"
+ "github.com/docker/docker/volume"
+ volumedrivers "github.com/docker/docker/volume/drivers"
+ "github.com/docker/docker/volume/local"
+ "github.com/docker/docker/volume/service/opts"
+ "github.com/docker/docker/volume/testutils"
+ "github.com/gotestyourself/gotestyourself/assert"
+ is "github.com/gotestyourself/gotestyourself/assert/cmp"
+)
+
+func TestLocalVolumeSize(t *testing.T) {
+ t.Parallel()
+
+ ds := volumedrivers.NewStore(nil)
+ dir, err := ioutil.TempDir("", t.Name())
+ assert.Assert(t, err)
+ defer os.RemoveAll(dir)
+
+ l, err := local.New(dir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()})
+ assert.Assert(t, err)
+ assert.Assert(t, ds.Register(l, volume.DefaultDriverName))
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("fake"), "fake"))
+
+ service, cleanup := newTestService(t, ds)
+ defer cleanup()
+
+ ctx := context.Background()
+ v1, err := service.Create(ctx, "test1", volume.DefaultDriverName, opts.WithCreateReference("foo"))
+ assert.Assert(t, err)
+ v2, err := service.Create(ctx, "test2", volume.DefaultDriverName)
+ assert.Assert(t, err)
+ _, err = service.Create(ctx, "test3", "fake")
+ assert.Assert(t, err)
+
+ data := make([]byte, 1024)
+ err = ioutil.WriteFile(filepath.Join(v1.Mountpoint, "data"), data, 0644)
+ assert.Assert(t, err)
+ err = ioutil.WriteFile(filepath.Join(v2.Mountpoint, "data"), data[:1], 0644)
+ assert.Assert(t, err)
+
+ ls, err := service.LocalVolumesSize(ctx)
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(ls, 2))
+
+ for _, v := range ls {
+ switch v.Name {
+ case "test1":
+ assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data))))
+ assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(1)))
+ case "test2":
+ assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data[:1]))))
+ assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(0)))
+ default:
+ t.Fatalf("got unexpected volume: %+v", v)
+ }
+ }
+ assert.Assert(t, is.Equal(ls[1].UsageData.Size, int64(len(data[:1]))))
+}
diff --git a/volume/service/service_test.go b/volume/service/service_test.go
new file mode 100644
index 0000000000..3c4130eab5
--- /dev/null
+++ b/volume/service/service_test.go
@@ -0,0 +1,253 @@
+package service
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/errdefs"
+ "github.com/docker/docker/volume"
+ volumedrivers "github.com/docker/docker/volume/drivers"
+ "github.com/docker/docker/volume/service/opts"
+ "github.com/docker/docker/volume/testutils"
+ "github.com/gotestyourself/gotestyourself/assert"
+ is "github.com/gotestyourself/gotestyourself/assert/cmp"
+)
+
+func TestServiceCreate(t *testing.T) {
+ t.Parallel()
+
+ ds := volumedrivers.NewStore(nil)
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
+
+ ctx := context.Background()
+ service, cleanup := newTestService(t, ds)
+ defer cleanup()
+
+ _, err := service.Create(ctx, "v1", "notexist")
+ assert.Assert(t, errdefs.IsNotFound(err), err)
+
+ v, err := service.Create(ctx, "v1", "d1")
+ assert.Assert(t, err)
+
+ vCopy, err := service.Create(ctx, "v1", "d1")
+ assert.Assert(t, err)
+ assert.Assert(t, is.DeepEqual(v, vCopy))
+
+ _, err = service.Create(ctx, "v1", "d2")
+ assert.Check(t, IsNameConflict(err), err)
+ assert.Check(t, errdefs.IsConflict(err), err)
+
+ assert.Assert(t, service.Remove(ctx, "v1"))
+ _, err = service.Create(ctx, "v1", "d2")
+ assert.Assert(t, err)
+ _, err = service.Create(ctx, "v1", "d2")
+ assert.Assert(t, err)
+
+}
+
+func TestServiceList(t *testing.T) {
+ t.Parallel()
+
+ ds := volumedrivers.NewStore(nil)
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
+
+ service, cleanup := newTestService(t, ds)
+ defer cleanup()
+
+ ctx := context.Background()
+
+ _, err := service.Create(ctx, "v1", "d1")
+ assert.Assert(t, err)
+ _, err = service.Create(ctx, "v2", "d1")
+ assert.Assert(t, err)
+ _, err = service.Create(ctx, "v3", "d2")
+ assert.Assert(t, err)
+
+ ls, _, err := service.List(ctx, filters.NewArgs(filters.Arg("driver", "d1")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 2))
+
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "d2")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 1))
+
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "notexist")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 0))
+
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 3))
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 0))
+
+ _, err = service.Get(ctx, "v1", opts.WithGetReference("foo"))
+ assert.Assert(t, err)
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 2))
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 1))
+
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false"), filters.Arg("driver", "d2")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 0))
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true"), filters.Arg("driver", "d2")))
+ assert.Assert(t, err)
+ assert.Check(t, is.Len(ls, 1))
+}
+
+func TestServiceRemove(t *testing.T) {
+ t.Parallel()
+
+ ds := volumedrivers.NewStore(nil)
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
+
+ service, cleanup := newTestService(t, ds)
+ defer cleanup()
+ ctx := context.Background()
+
+ _, err := service.Create(ctx, "test", "d1")
+ assert.Assert(t, err)
+
+ assert.Assert(t, service.Remove(ctx, "test"))
+ assert.Assert(t, service.Remove(ctx, "test", opts.WithPurgeOnError(true)))
+}
+
+func TestServiceGet(t *testing.T) {
+ t.Parallel()
+
+ ds := volumedrivers.NewStore(nil)
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
+
+ service, cleanup := newTestService(t, ds)
+ defer cleanup()
+ ctx := context.Background()
+
+ v, err := service.Get(ctx, "notexist")
+ assert.Assert(t, IsNotExist(err))
+ assert.Check(t, v == nil)
+
+ created, err := service.Create(ctx, "test", "d1")
+ assert.Assert(t, err)
+ assert.Assert(t, created != nil)
+
+ v, err = service.Get(ctx, "test")
+ assert.Assert(t, err)
+ assert.Assert(t, is.DeepEqual(created, v))
+
+ v, err = service.Get(ctx, "test", opts.WithGetResolveStatus)
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(v.Status, 1), v.Status)
+
+ v, err = service.Get(ctx, "test", opts.WithGetDriver("notarealdriver"))
+ assert.Assert(t, errdefs.IsConflict(err), err)
+ v, err = service.Get(ctx, "test", opts.WithGetDriver("d1"))
+ assert.Assert(t, err == nil)
+ assert.Assert(t, is.DeepEqual(created, v))
+
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
+ v, err = service.Get(ctx, "test", opts.WithGetDriver("d2"))
+ assert.Assert(t, errdefs.IsConflict(err), err)
+}
+
+func TestServicePrune(t *testing.T) {
+ t.Parallel()
+
+ ds := volumedrivers.NewStore(nil)
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver(volume.DefaultDriverName), volume.DefaultDriverName))
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("other"), "other"))
+
+ service, cleanup := newTestService(t, ds)
+ defer cleanup()
+ ctx := context.Background()
+
+ _, err := service.Create(ctx, "test", volume.DefaultDriverName)
+ assert.Assert(t, err)
+ _, err = service.Create(ctx, "test2", "other")
+ assert.Assert(t, err)
+
+ pr, err := service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana")))
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
+
+ pr, err = service.Prune(ctx, filters.NewArgs())
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
+
+ _, err = service.Get(ctx, "test")
+ assert.Assert(t, IsNotExist(err), err)
+
+ v, err := service.Get(ctx, "test2")
+ assert.Assert(t, err)
+ assert.Assert(t, is.Equal(v.Driver, "other"))
+
+ _, err = service.Create(ctx, "test", volume.DefaultDriverName)
+ assert.Assert(t, err)
+
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
+ v, err = service.Get(ctx, "test2")
+ assert.Assert(t, err)
+ assert.Assert(t, is.Equal(v.Driver, "other"))
+
+ _, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": ""}))
+ assert.Assert(t, err)
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
+
+ _, err = service.Create(ctx, "test3", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": "split"}))
+ assert.Assert(t, err)
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana=split")))
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
+
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana=split")))
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test3"))
+
+ v, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateReference(t.Name()))
+ assert.Assert(t, err)
+
+ pr, err = service.Prune(ctx, filters.NewArgs())
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
+ assert.Assert(t, service.Release(ctx, v.Name, t.Name()))
+
+ pr, err = service.Prune(ctx, filters.NewArgs())
+ assert.Assert(t, err)
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
+}
+
+func newTestService(t *testing.T, ds *volumedrivers.Store) (*VolumesService, func()) {
+ t.Helper()
+
+ dir, err := ioutil.TempDir("", t.Name())
+ assert.Assert(t, err)
+
+ store, err := NewStore(dir, ds)
+ assert.Assert(t, err)
+ s := &VolumesService{vs: store, eventLogger: dummyEventLogger{}}
+ return s, func() {
+ assert.Check(t, s.Shutdown())
+ assert.Check(t, os.RemoveAll(dir))
+ }
+}
+
+type dummyEventLogger struct{}
+
+func (dummyEventLogger) LogVolumeEvent(_, _ string, _ map[string]string) {}
diff --git a/volume/store/store.go b/volume/service/store.go
index 990bc3077b..e7e9d8a320 100644
--- a/volume/store/store.go
+++ b/volume/service/store.go
@@ -1,6 +1,8 @@
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
import (
+ "context"
+ "fmt"
"net"
"os"
"path/filepath"
@@ -11,10 +13,12 @@ import (
"github.com/pkg/errors"
"github.com/boltdb/bolt"
+ "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/locker"
"github.com/docker/docker/volume"
"github.com/docker/docker/volume/drivers"
volumemounts "github.com/docker/docker/volume/mounts"
+ "github.com/docker/docker/volume/service/opts"
"github.com/sirupsen/logrus"
)
@@ -65,9 +69,8 @@ func (v volumeWrapper) CachedPath() string {
return v.Volume.Path()
}
-// New initializes a VolumeStore to keep
-// reference counting of volumes in the system.
-func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
+// NewStore creates a new volume store at the given path
+func NewStore(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
vs := &VolumeStore{
locks: &locker.Locker{},
names: make(map[string]volume.Volume),
@@ -84,10 +87,8 @@ func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
return nil, err
}
- dbPath := filepath.Join(volPath, "metadata.db")
-
var err error
- vs.db, err = bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
+ vs.db, err = bolt.Open(filepath.Join(volPath, "metadata.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
return nil, errors.Wrap(err, "error while opening volume store metadata database")
}
@@ -152,10 +153,18 @@ func (s *VolumeStore) getRefs(name string) []string {
return refs
}
-// Purge allows the cleanup of internal data on docker in case
+// purge allows the cleanup of internal data on docker in case
// the internal data is out of sync with volumes driver plugins.
-func (s *VolumeStore) Purge(name string) {
+func (s *VolumeStore) purge(ctx context.Context, name string) error {
s.globalLock.Lock()
+ defer s.globalLock.Unlock()
+
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+
v, exists := s.names[name]
if exists {
driverName := v.DriverName()
@@ -170,7 +179,7 @@ func (s *VolumeStore) Purge(name string) {
delete(s.refs, name)
delete(s.labels, name)
delete(s.options, name)
- s.globalLock.Unlock()
+ return nil
}
// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
@@ -193,14 +202,137 @@ type VolumeStore struct {
db *bolt.DB
}
-// List proxies to all registered volume drivers to get the full list of volumes
+func filterByDriver(names []string) filterFunc {
+ return func(v volume.Volume) bool {
+ for _, name := range names {
+ if name == v.DriverName() {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+func (s *VolumeStore) byReferenced(referenced bool) filterFunc {
+ return func(v volume.Volume) bool {
+ return s.hasRef(v.Name()) == referenced
+ }
+}
+
+func (s *VolumeStore) filter(ctx context.Context, vols *[]volume.Volume, by By) (warnings []string, err error) {
+ // note that this specifically does not support the `FromList` By type.
+ switch f := by.(type) {
+ case nil:
+ if *vols == nil {
+ var ls []volume.Volume
+ ls, warnings, err = s.list(ctx)
+ if err != nil {
+ return warnings, err
+ }
+ *vols = ls
+ }
+ case byDriver:
+ if *vols != nil {
+ filter(vols, filterByDriver([]string(f)))
+ return nil, nil
+ }
+ var ls []volume.Volume
+ ls, warnings, err = s.list(ctx, []string(f)...)
+ if err != nil {
+ return nil, err
+ }
+ *vols = ls
+ case ByReferenced:
+ // TODO(@cpuguy83): It would be nice to optimize this by looking at the list
+ // of referenced volumes, however the locking strategy makes this difficult
+ // without either providing inconsistent data or deadlocks.
+ if *vols == nil {
+ var ls []volume.Volume
+ ls, warnings, err = s.list(ctx)
+ if err != nil {
+ return nil, err
+ }
+ *vols = ls
+ }
+ filter(vols, s.byReferenced(bool(f)))
+ case andCombinator:
+ for _, by := range f {
+ w, err := s.filter(ctx, vols, by)
+ if err != nil {
+ return warnings, err
+ }
+ warnings = append(warnings, w...)
+ }
+ case orCombinator:
+ for _, by := range f {
+ switch by.(type) {
+ case byDriver:
+ var ls []volume.Volume
+ w, err := s.filter(ctx, &ls, by)
+ if err != nil {
+ return warnings, err
+ }
+ warnings = append(warnings, w...)
+ default:
+ ls, w, err := s.list(ctx)
+ if err != nil {
+ return warnings, err
+ }
+ warnings = append(warnings, w...)
+ w, err = s.filter(ctx, &ls, by)
+ if err != nil {
+ return warnings, err
+ }
+ warnings = append(warnings, w...)
+ *vols = append(*vols, ls...)
+ }
+ }
+ unique(vols)
+ case CustomFilter:
+ if *vols == nil {
+ var ls []volume.Volume
+ ls, warnings, err = s.list(ctx)
+ if err != nil {
+ return nil, err
+ }
+ *vols = ls
+ }
+ filter(vols, filterFunc(f))
+ default:
+ return nil, errdefs.InvalidParameter(errors.Errorf("unsupported filter: %T", f))
+ }
+ return warnings, nil
+}
+
+func unique(ls *[]volume.Volume) {
+ names := make(map[string]bool, len(*ls))
+ filter(ls, func(v volume.Volume) bool {
+ if names[v.Name()] {
+ return false
+ }
+ names[v.Name()] = true
+ return true
+ })
+}
+
+// Find lists volumes filtered by the past in filter.
// If a driver returns a volume that has name which conflicts with another volume from a different driver,
// the first volume is chosen and the conflicting volume is dropped.
-func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
- vols, warnings, err := s.list()
+func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) {
+ logrus.WithField("ByType", fmt.Sprintf("%T", by)).WithField("ByValue", fmt.Sprintf("%+v", by)).Debug("VolumeStore.Find")
+ switch f := by.(type) {
+ case nil, orCombinator, andCombinator, byDriver, ByReferenced, CustomFilter:
+ warnings, err = s.filter(ctx, &vols, by)
+ case fromList:
+ warnings, err = s.filter(ctx, f.ls, f.by)
+ default:
+ // Really shouldn't be possible, but makes sure that any new By's are added to this check.
+ err = errdefs.InvalidParameter(errors.Errorf("unsupported filter type: %T", f))
+ }
if err != nil {
return nil, nil, &OpErr{Err: err, Op: "list"}
}
+
var out []volume.Volume
for _, v := range vols {
@@ -222,26 +354,59 @@ func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
return out, warnings, nil
}
+type filterFunc func(volume.Volume) bool
+
+func filter(vols *[]volume.Volume, fn filterFunc) {
+ var evict []int
+ for i, v := range *vols {
+ if !fn(v) {
+ evict = append(evict, i)
+ }
+ }
+
+ for n, i := range evict {
+ copy((*vols)[i-n:], (*vols)[i-n+1:])
+ (*vols)[len(*vols)-1] = nil
+ *vols = (*vols)[:len(*vols)-1]
+ }
+}
+
// list goes through each volume driver and asks for its list of volumes.
-func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
+// TODO(@cpuguy83): plumb context through
+func (s *VolumeStore) list(ctx context.Context, driverNames ...string) ([]volume.Volume, []string, error) {
var (
- ls []volume.Volume
+ ls = []volume.Volume{} // do not return a nil value as this affects filtering
warnings []string
)
- drivers, err := s.drivers.GetAllDrivers()
+ var dls []volume.Driver
+
+ all, err := s.drivers.GetAllDrivers()
if err != nil {
return nil, nil, err
}
+ if len(driverNames) == 0 {
+ dls = all
+ } else {
+ idx := make(map[string]bool, len(driverNames))
+ for _, name := range driverNames {
+ idx[name] = true
+ }
+ for _, d := range all {
+ if idx[d.Name()] {
+ dls = append(dls, d)
+ }
+ }
+ }
type vols struct {
vols []volume.Volume
err error
driverName string
}
- chVols := make(chan vols, len(drivers))
+ chVols := make(chan vols, len(dls))
- for _, vd := range drivers {
+ for _, vd := range dls {
go func(d volume.Driver) {
vs, err := d.List()
if err != nil {
@@ -259,13 +424,12 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
}
badDrivers := make(map[string]struct{})
- for i := 0; i < len(drivers); i++ {
+ for i := 0; i < len(dls); i++ {
vs := <-chVols
if vs.err != nil {
warnings = append(warnings, vs.err.Error())
badDrivers[vs.driverName] = struct{}{}
- logrus.Warn(vs.err)
}
ls = append(ls, vs.vols...)
}
@@ -282,14 +446,26 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
return ls, warnings, nil
}
-// CreateWithRef creates a volume with the given name and driver and stores the ref
-// This ensures there's no race between creating a volume and then storing a reference.
-func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels map[string]string) (volume.Volume, error) {
+// Create creates a volume with the given name and driver
+// If the volume needs to be created with a reference to prevent race conditions
+// with volume cleanup, make sure to use the `CreateWithReference` option.
+func (s *VolumeStore) Create(ctx context.Context, name, driverName string, createOpts ...opts.CreateOption) (volume.Volume, error) {
+ var cfg opts.CreateConfig
+ for _, o := range createOpts {
+ o(&cfg)
+ }
+
name = normalizeVolumeName(name)
s.locks.Lock(name)
defer s.locks.Unlock(name)
- v, err := s.create(name, driverName, opts, labels)
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ v, err := s.create(ctx, name, driverName, cfg.Options, cfg.Labels)
if err != nil {
if _, ok := err.(*OpErr); ok {
return nil, err
@@ -297,16 +473,10 @@ func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels m
return nil, &OpErr{Err: err, Name: name, Op: "create"}
}
- s.setNamed(v, ref)
+ s.setNamed(v, cfg.Reference)
return v, nil
}
-// Create creates a volume with the given name and driver.
-// This is just like CreateWithRef() except we don't store a reference while holding the lock.
-func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
- return s.CreateWithRef(name, driverName, "", opts, labels)
-}
-
// checkConflict checks the local cache for name collisions with the passed in name,
// for existing volumes with the same name but in a different driver.
// This is used by `Create` as a best effort to prevent name collisions for volumes.
@@ -320,7 +490,7 @@ func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]st
// TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially
// use a connect timeout for this kind of check to ensure we aren't blocking for a
// long time.
-func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, error) {
+func (s *VolumeStore) checkConflict(ctx context.Context, name, driverName string) (volume.Volume, error) {
// check the local cache
v, _ := s.getNamed(name)
if v == nil {
@@ -344,7 +514,7 @@ func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, err
// let's check if the found volume ref
// is stale by checking with the driver if it still exists
- exists, err := volumeExists(s.drivers, v)
+ exists, err := volumeExists(ctx, s.drivers, v)
if err != nil {
return nil, errors.Wrapf(errNameConflict, "found reference to volume '%s' in driver '%s', but got an error while checking the driver: %v", name, vDriverName, err)
}
@@ -363,14 +533,14 @@ func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, err
}
// doesn't exist, so purge it from the cache
- s.Purge(name)
+ s.purge(ctx, name)
return nil, nil
}
// volumeExists returns if the volume is still present in the driver.
// An error is returned if there was an issue communicating with the driver.
-func volumeExists(store *drivers.Store, v volume.Volume) (bool, error) {
- exists, err := lookupVolume(store, v.DriverName(), v.Name())
+func volumeExists(ctx context.Context, store *drivers.Store, v volume.Volume) (bool, error) {
+ exists, err := lookupVolume(ctx, store, v.DriverName(), v.Name())
if err != nil {
return false, err
}
@@ -383,7 +553,7 @@ func volumeExists(store *drivers.Store, v volume.Volume) (bool, error) {
// for the given volume name, an error is returned after checking if the reference is stale.
// If the reference is stale, it will be purged and this create can continue.
// It is expected that callers of this function hold any necessary locks.
-func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
+func (s *VolumeStore) create(ctx context.Context, name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
// Validate the name in a platform-specific manner
// volume name validation is specific to the host os and not on container image
@@ -394,7 +564,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
return nil, err
}
- v, err := s.checkConflict(name, driverName)
+ v, err := s.checkConflict(ctx, name, driverName)
if err != nil {
return nil, err
}
@@ -409,7 +579,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
// Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name
if driverName == "" {
- v, _ = s.getVolume(name)
+ v, _ = s.getVolume(ctx, name, "")
if v != nil {
return v, nil
}
@@ -453,61 +623,57 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
return volumeWrapper{v, labels, vd.Scope(), opts}, nil
}
-// GetWithRef gets a volume with the given name from the passed in driver and stores the ref
-// This is just like Get(), but we store the reference while holding the lock.
-// This makes sure there are no races between checking for the existence of a volume and adding a reference for it
-func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, error) {
- name = normalizeVolumeName(name)
- s.locks.Lock(name)
- defer s.locks.Unlock(name)
-
- if driverName == "" {
- driverName = volume.DefaultDriverName
- }
- vd, err := s.drivers.GetDriver(driverName)
- if err != nil {
- return nil, &OpErr{Err: err, Name: name, Op: "get"}
- }
-
- v, err := vd.Get(name)
- if err != nil {
- return nil, &OpErr{Err: err, Name: name, Op: "get"}
- }
-
- s.setNamed(v, ref)
-
- s.globalLock.RLock()
- defer s.globalLock.RUnlock()
- return volumeWrapper{v, s.labels[name], vd.Scope(), s.options[name]}, nil
-}
-
// Get looks if a volume with the given name exists and returns it if so
-func (s *VolumeStore) Get(name string) (volume.Volume, error) {
+func (s *VolumeStore) Get(ctx context.Context, name string, getOptions ...opts.GetOption) (volume.Volume, error) {
+ var cfg opts.GetConfig
+ for _, o := range getOptions {
+ o(&cfg)
+ }
name = normalizeVolumeName(name)
s.locks.Lock(name)
defer s.locks.Unlock(name)
- v, err := s.getVolume(name)
+ v, err := s.getVolume(ctx, name, cfg.Driver)
if err != nil {
return nil, &OpErr{Err: err, Name: name, Op: "get"}
}
- s.setNamed(v, "")
+ if cfg.Driver != "" && v.DriverName() != cfg.Driver {
+ return nil, &OpErr{Name: name, Op: "get", Err: errdefs.Conflict(errors.New("found volume driver does not match passed in driver"))}
+ }
+ s.setNamed(v, cfg.Reference)
return v, nil
}
// getVolume requests the volume, if the driver info is stored it just accesses that driver,
// if the driver is unknown it probes all drivers until it finds the first volume with that name.
// it is expected that callers of this function hold any necessary locks
-func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
+func (s *VolumeStore) getVolume(ctx context.Context, name, driverName string) (volume.Volume, error) {
var meta volumeMetadata
meta, err := s.getMeta(name)
if err != nil {
return nil, err
}
- driverName := meta.Driver
+ if driverName != "" {
+ if meta.Driver == "" {
+ meta.Driver = driverName
+ }
+ if driverName != meta.Driver {
+ return nil, errdefs.Conflict(errors.New("provided volume driver does not match stored driver"))
+ }
+ }
+
+ if driverName == "" {
+ driverName = meta.Driver
+ }
if driverName == "" {
s.globalLock.RLock()
+ select {
+ case <-ctx.Done():
+ s.globalLock.RUnlock()
+ return nil, ctx.Err()
+ default:
+ }
v, exists := s.names[name]
s.globalLock.RUnlock()
if exists {
@@ -519,12 +685,12 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
}
if meta.Driver != "" {
- vol, err := lookupVolume(s.drivers, meta.Driver, name)
+ vol, err := lookupVolume(ctx, s.drivers, meta.Driver, name)
if err != nil {
return nil, err
}
if vol == nil {
- s.Purge(name)
+ s.purge(ctx, name)
return nil, errNoSuchVolume
}
@@ -543,6 +709,11 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
}
for _, d := range drivers {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
v, err := d.Get(name)
if err != nil || v == nil {
continue
@@ -561,7 +732,8 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
// If the driver returns an error that is not communication related the
// error is logged but not returned.
// If the volume is not found it will return `nil, nil``
-func lookupVolume(store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
+// TODO(@cpuguy83): plumb through the context to lower level components
+func lookupVolume(ctx context.Context, store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
if driverName == "" {
driverName = volume.DefaultDriverName
}
@@ -582,19 +754,35 @@ func lookupVolume(store *drivers.Store, driverName, volumeName string) (volume.V
// At this point, the error could be anything from the driver, such as "no such volume"
// Let's not check an error here, and instead check if the driver returned a volume
- logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Warnf("Error while looking up volume")
+ logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Debug("Error while looking up volume")
}
return v, nil
}
// Remove removes the requested volume. A volume is not removed if it has any refs
-func (s *VolumeStore) Remove(v volume.Volume) error {
- name := normalizeVolumeName(v.Name())
+func (s *VolumeStore) Remove(ctx context.Context, v volume.Volume, rmOpts ...opts.RemoveOption) error {
+ var cfg opts.RemoveConfig
+ for _, o := range rmOpts {
+ o(&cfg)
+ }
+
+ name := v.Name()
s.locks.Lock(name)
defer s.locks.Unlock(name)
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+
if s.hasRef(name) {
- return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: s.getRefs(name)}
+ return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove", Refs: s.getRefs(name)}
+ }
+
+ v, err := s.getVolume(ctx, name, v.DriverName())
+ if err != nil {
+ return err
}
vd, err := s.drivers.GetDriver(v.DriverName())
@@ -604,85 +792,55 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
vol := unwrapVolume(v)
- if err := vd.Remove(vol); err != nil {
- return &OpErr{Err: err, Name: name, Op: "remove"}
+
+ err = vd.Remove(vol)
+ if err != nil {
+ err = &OpErr{Err: err, Name: name, Op: "remove"}
}
- s.Purge(name)
- return nil
+ if err == nil || cfg.PurgeOnError {
+ if e := s.purge(ctx, name); e != nil && err == nil {
+ err = e
+ }
+ }
+ return err
}
-// Dereference removes the specified reference to the volume
-func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
- name := v.Name()
-
+// Release releases the specified reference to the volume
+func (s *VolumeStore) Release(ctx context.Context, name string, ref string) error {
s.locks.Lock(name)
defer s.locks.Unlock(name)
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
s.globalLock.Lock()
defer s.globalLock.Unlock()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+
if s.refs[name] != nil {
delete(s.refs[name], ref)
}
+ return nil
}
-// Refs gets the current list of refs for the given volume
-func (s *VolumeStore) Refs(v volume.Volume) []string {
- name := v.Name()
+// CountReferences gives a count of all references for a given volume.
+func (s *VolumeStore) CountReferences(v volume.Volume) int {
+ name := normalizeVolumeName(v.Name())
s.locks.Lock(name)
defer s.locks.Unlock(name)
+ s.globalLock.Lock()
+ defer s.globalLock.Unlock()
- return s.getRefs(name)
-}
-
-// FilterByDriver returns the available volumes filtered by driver name
-func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
- vd, err := s.drivers.GetDriver(name)
- if err != nil {
- return nil, &OpErr{Err: err, Name: name, Op: "list"}
- }
- ls, err := vd.List()
- if err != nil {
- return nil, &OpErr{Err: err, Name: name, Op: "list"}
- }
- for i, v := range ls {
- options := map[string]string{}
- s.globalLock.RLock()
- for key, value := range s.options[v.Name()] {
- options[key] = value
- }
- ls[i] = volumeWrapper{v, s.labels[v.Name()], vd.Scope(), options}
- s.globalLock.RUnlock()
- }
- return ls, nil
-}
-
-// FilterByUsed returns the available volumes filtered by if they are in use or not.
-// `used=true` returns only volumes that are being used, while `used=false` returns
-// only volumes that are not being used.
-func (s *VolumeStore) FilterByUsed(vols []volume.Volume, used bool) []volume.Volume {
- return s.filter(vols, func(v volume.Volume) bool {
- s.locks.Lock(v.Name())
- hasRef := s.hasRef(v.Name())
- s.locks.Unlock(v.Name())
- return used == hasRef
- })
-}
-
-// filterFunc defines a function to allow filter volumes in the store
-type filterFunc func(vol volume.Volume) bool
-
-// filter returns the available volumes filtered by a filterFunc function
-func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume {
- var ls []volume.Volume
- for _, v := range vols {
- if f(v) {
- ls = append(ls, v)
- }
- }
- return ls
+ return len(s.refs[name])
}
func unwrapVolume(v volume.Volume) volume.Volume {
@@ -698,10 +856,3 @@ func unwrapVolume(v volume.Volume) volume.Volume {
func (s *VolumeStore) Shutdown() error {
return s.db.Close()
}
-
-// GetDriverList gets the list of volume drivers from the configured volume driver
-// store.
-// TODO(@cpuguy83): This should be factored out into a separate service.
-func (s *VolumeStore) GetDriverList() []string {
- return s.drivers.GetDriverList()
-}
diff --git a/volume/store/store_test.go b/volume/service/store_test.go
index 288a4ce824..b6b0829952 100644
--- a/volume/store/store_test.go
+++ b/volume/service/store_test.go
@@ -1,6 +1,7 @@
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
import (
+ "context"
"errors"
"fmt"
"io/ioutil"
@@ -11,6 +12,7 @@ import (
"github.com/docker/docker/volume"
volumedrivers "github.com/docker/docker/volume/drivers"
+ "github.com/docker/docker/volume/service/opts"
volumetestutils "github.com/docker/docker/volume/testutils"
"github.com/google/go-cmp/cmp"
"github.com/gotestyourself/gotestyourself/assert"
@@ -24,22 +26,23 @@ func TestCreate(t *testing.T) {
defer cleanup()
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
- v, err := s.Create("fake1", "fake", nil, nil)
+ ctx := context.Background()
+ v, err := s.Create(ctx, "fake1", "fake")
if err != nil {
t.Fatal(err)
}
if v.Name() != "fake1" {
t.Fatalf("Expected fake1 volume, got %v", v)
}
- if l, _, _ := s.List(); len(l) != 1 {
+ if l, _, _ := s.Find(ctx, nil); len(l) != 1 {
t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
}
- if _, err := s.Create("none", "none", nil, nil); err == nil {
+ if _, err := s.Create(ctx, "none", "none"); err == nil {
t.Fatalf("Expected unknown driver error, got nil")
}
- _, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"}, nil)
+ _, err = s.Create(ctx, "fakeerror", "fake", opts.WithCreateOptions(map[string]string{"error": "create error"}))
expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
if err != nil && err.Error() != expected.Error() {
t.Fatalf("Expected create fakeError: create error, got %v", err)
@@ -55,25 +58,28 @@ func TestRemove(t *testing.T) {
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
+ ctx := context.Background()
+
// doing string compare here since this error comes directly from the driver
expected := "no such volume"
- if err := s.Remove(volumetestutils.NoopVolume{}); err == nil || !strings.Contains(err.Error(), expected) {
+ var v volume.Volume = volumetestutils.NoopVolume{}
+ if err := s.Remove(ctx, v); err == nil || !strings.Contains(err.Error(), expected) {
t.Fatalf("Expected error %q, got %v", expected, err)
}
- v, err := s.CreateWithRef("fake1", "fake", "fake", nil, nil)
+ v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("fake"))
if err != nil {
t.Fatal(err)
}
- if err := s.Remove(v); !IsInUse(err) {
+ if err := s.Remove(ctx, v); !IsInUse(err) {
t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
}
- s.Dereference(v, "fake")
- if err := s.Remove(v); err != nil {
+ s.Release(ctx, v.Name(), "fake")
+ if err := s.Remove(ctx, v); err != nil {
t.Fatal(err)
}
- if l, _, _ := s.List(); len(l) != 0 {
+ if l, _, _ := s.Find(ctx, nil); len(l) != 0 {
t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
}
}
@@ -89,17 +95,18 @@ func TestList(t *testing.T) {
drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
- s, err := New(dir, drivers)
+ s, err := NewStore(dir, drivers)
assert.NilError(t, err)
- if _, err := s.Create("test", "fake", nil, nil); err != nil {
+ ctx := context.Background()
+ if _, err := s.Create(ctx, "test", "fake"); err != nil {
t.Fatal(err)
}
- if _, err := s.Create("test2", "fake2", nil, nil); err != nil {
+ if _, err := s.Create(ctx, "test2", "fake2"); err != nil {
t.Fatal(err)
}
- ls, _, err := s.List()
+ ls, _, err := s.Find(ctx, nil)
if err != nil {
t.Fatal(err)
}
@@ -111,11 +118,11 @@ func TestList(t *testing.T) {
}
// and again with a new store
- s, err = New(dir, drivers)
+ s, err = NewStore(dir, drivers)
if err != nil {
t.Fatal(err)
}
- ls, _, err = s.List()
+ ls, _, err = s.Find(ctx, nil)
if err != nil {
t.Fatal(err)
}
@@ -124,34 +131,38 @@ func TestList(t *testing.T) {
}
}
-func TestFilterByDriver(t *testing.T) {
+func TestFindByDriver(t *testing.T) {
t.Parallel()
s, cleanup := setupTest(t)
defer cleanup()
- s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
- s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
+ assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake"))
+ assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop"))
- if _, err := s.Create("fake1", "fake", nil, nil); err != nil {
- t.Fatal(err)
- }
- if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
- t.Fatal(err)
- }
- if _, err := s.Create("fake3", "noop", nil, nil); err != nil {
- t.Fatal(err)
- }
+ ctx := context.Background()
+ _, err := s.Create(ctx, "fake1", "fake")
+ assert.NilError(t, err)
- if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
- t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
- }
+ _, err = s.Create(ctx, "fake2", "fake")
+ assert.NilError(t, err)
- if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
- t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
- }
+ _, err = s.Create(ctx, "fake3", "noop")
+ assert.NilError(t, err)
+
+ l, _, err := s.Find(ctx, ByDriver("fake"))
+ assert.NilError(t, err)
+ assert.Equal(t, len(l), 2)
+
+ l, _, err = s.Find(ctx, ByDriver("noop"))
+ assert.NilError(t, err)
+ assert.Equal(t, len(l), 1)
+
+ l, _, err = s.Find(ctx, ByDriver("nosuchdriver"))
+ assert.NilError(t, err)
+ assert.Equal(t, len(l), 0)
}
-func TestFilterByUsed(t *testing.T) {
+func TestFindByReferenced(t *testing.T) {
t.Parallel()
s, cleanup := setupTest(t)
defer cleanup()
@@ -159,33 +170,23 @@ func TestFilterByUsed(t *testing.T) {
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
- if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil); err != nil {
+ ctx := context.Background()
+ if _, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference")); err != nil {
t.Fatal(err)
}
- if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
+ if _, err := s.Create(ctx, "fake2", "fake"); err != nil {
t.Fatal(err)
}
- vols, _, err := s.List()
- if err != nil {
- t.Fatal(err)
- }
+ dangling, _, err := s.Find(ctx, ByReferenced(false))
+ assert.Assert(t, err)
+ assert.Assert(t, len(dangling) == 1)
+ assert.Check(t, dangling[0].Name() == "fake2")
- dangling := s.FilterByUsed(vols, false)
- if len(dangling) != 1 {
- t.Fatalf("expected 1 dangling volume, got %v", len(dangling))
- }
- if dangling[0].Name() != "fake2" {
- t.Fatalf("expected dangling volume fake2, got %s", dangling[0].Name())
- }
-
- used := s.FilterByUsed(vols, true)
- if len(used) != 1 {
- t.Fatalf("expected 1 used volume, got %v", len(used))
- }
- if used[0].Name() != "fake1" {
- t.Fatalf("expected used volume fake1, got %s", used[0].Name())
- }
+ used, _, err := s.Find(ctx, ByReferenced(true))
+ assert.Assert(t, err)
+ assert.Assert(t, len(used) == 1)
+ assert.Check(t, used[0].Name() == "fake1")
}
func TestDerefMultipleOfSameRef(t *testing.T) {
@@ -194,17 +195,18 @@ func TestDerefMultipleOfSameRef(t *testing.T) {
defer cleanup()
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
- v, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil)
+ ctx := context.Background()
+ v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference"))
if err != nil {
t.Fatal(err)
}
- if _, err := s.GetWithRef("fake1", "fake", "volReference"); err != nil {
+ if _, err := s.Get(ctx, "fake1", opts.WithGetDriver("fake"), opts.WithGetReference("volReference")); err != nil {
t.Fatal(err)
}
- s.Dereference(v, "volReference")
- if err := s.Remove(v); err != nil {
+ s.Release(ctx, v.Name(), "volReference")
+ if err := s.Remove(ctx, v); err != nil {
t.Fatal(err)
}
}
@@ -222,7 +224,8 @@ func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
t.Fatal(err)
}
- v, err := s.Create("foo", "fake", nil, map[string]string{"hello": "world"})
+ ctx := context.Background()
+ v, err := s.Create(ctx, "foo", "fake", opts.WithCreateLabels(map[string]string{"hello": "world"}))
if err != nil {
t.Fatal(err)
}
@@ -265,14 +268,15 @@ func TestDefererencePluginOnCreateError(t *testing.T) {
pg := volumetestutils.NewFakePluginGetter(p)
s.drivers = volumedrivers.NewStore(pg)
+ ctx := context.Background()
// create a good volume so we have a plugin reference
- _, err = s.Create("fake1", d.Name(), nil, nil)
+ _, err = s.Create(ctx, "fake1", d.Name())
if err != nil {
t.Fatal(err)
}
// Now create another one expecting an error
- _, err = s.Create("fake2", d.Name(), map[string]string{"error": "some error"}, nil)
+ _, err = s.Create(ctx, "fake2", d.Name(), opts.WithCreateOptions(map[string]string{"error": "some error"}))
if err == nil || !strings.Contains(err.Error(), "some error") {
t.Fatalf("expected an error on create: %v", err)
}
@@ -291,15 +295,16 @@ func TestRefDerefRemove(t *testing.T) {
defer cleanup()
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
- v, err := s.CreateWithRef("test", driverName, "test-ref", nil, nil)
+ ctx := context.Background()
+ v, err := s.Create(ctx, "test", driverName, opts.WithCreateReference("test-ref"))
assert.NilError(t, err)
- err = s.Remove(v)
+ err = s.Remove(ctx, v)
assert.Assert(t, is.ErrorContains(err, ""))
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
- s.Dereference(v, "test-ref")
- err = s.Remove(v)
+ s.Release(ctx, v.Name(), "test-ref")
+ err = s.Remove(ctx, v)
assert.NilError(t, err)
}
@@ -311,25 +316,26 @@ func TestGet(t *testing.T) {
defer cleanup()
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
- _, err := s.Get("not-exist")
+ ctx := context.Background()
+ _, err := s.Get(ctx, "not-exist")
assert.Assert(t, is.ErrorContains(err, ""))
assert.Equal(t, errNoSuchVolume, err.(*OpErr).Err)
- v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"})
+ v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
assert.NilError(t, err)
- v2, err := s.Get("test")
+ v2, err := s.Get(ctx, "test")
assert.NilError(t, err)
assert.DeepEqual(t, v1, v2, cmpVolume)
dv := v2.(volume.DetailedVolume)
assert.Equal(t, "1", dv.Labels()["a"])
- err = s.Remove(v1)
+ err = s.Remove(ctx, v1)
assert.NilError(t, err)
}
-func TestGetWithRef(t *testing.T) {
+func TestGetWithReference(t *testing.T) {
t.Parallel()
driverName := "test-get-with-ref"
@@ -337,22 +343,23 @@ func TestGetWithRef(t *testing.T) {
defer cleanup()
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
- _, err := s.GetWithRef("not-exist", driverName, "test-ref")
+ ctx := context.Background()
+ _, err := s.Get(ctx, "not-exist", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
assert.Assert(t, is.ErrorContains(err, ""))
- v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"})
+ v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
assert.NilError(t, err)
- v2, err := s.GetWithRef("test", driverName, "test-ref")
+ v2, err := s.Get(ctx, "test", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
assert.NilError(t, err)
assert.DeepEqual(t, v1, v2, cmpVolume)
- err = s.Remove(v2)
+ err = s.Remove(ctx, v2)
assert.Assert(t, is.ErrorContains(err, ""))
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
- s.Dereference(v2, "test-ref")
- err = s.Remove(v2)
+ s.Release(ctx, v2.Name(), "test-ref")
+ err = s.Remove(ctx, v2)
assert.NilError(t, err)
}
@@ -366,14 +373,49 @@ func setupTest(t *testing.T) (*VolumeStore, func()) {
assert.NilError(t, err)
cleanup := func() {
+ t.Helper()
err := os.RemoveAll(dir)
assert.Check(t, err)
}
- s, err := New(dir, volumedrivers.NewStore(nil))
+ s, err := NewStore(dir, volumedrivers.NewStore(nil))
assert.Check(t, err)
return s, func() {
s.Shutdown()
cleanup()
}
}
+
+func TestFilterFunc(t *testing.T) {
+ testDriver := volumetestutils.NewFakeDriver("test")
+ testVolume, err := testDriver.Create("test", nil)
+ assert.NilError(t, err)
+ testVolume2, err := testDriver.Create("test2", nil)
+ assert.NilError(t, err)
+ testVolume3, err := testDriver.Create("test3", nil)
+ assert.NilError(t, err)
+
+ for _, test := range []struct {
+ vols []volume.Volume
+ fn filterFunc
+ desc string
+ expect []volume.Volume
+ }{
+ {desc: "test nil list", vols: nil, expect: nil, fn: func(volume.Volume) bool { return true }},
+ {desc: "test empty list", vols: []volume.Volume{}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return true }},
+ {desc: "test filter non-empty to empty", vols: []volume.Volume{testVolume}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return false }},
+ {desc: "test nothing to fitler non-empty list", vols: []volume.Volume{testVolume}, expect: []volume.Volume{testVolume}, fn: func(volume.Volume) bool { return true }},
+ {desc: "test filter some", vols: []volume.Volume{testVolume, testVolume2}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() == testVolume.Name() }},
+ {desc: "test filter middle", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume, testVolume3}, fn: func(v volume.Volume) bool { return v.Name() != testVolume2.Name() }},
+ {desc: "test filter middle and last", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() != testVolume2.Name() && v.Name() != testVolume3.Name() }},
+ {desc: "test filter first and last", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume2}, fn: func(v volume.Volume) bool { return v.Name() != testVolume.Name() && v.Name() != testVolume3.Name() }},
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ test := test
+ t.Parallel()
+
+ filter(&test.vols, test.fn)
+ assert.DeepEqual(t, test.vols, test.expect, cmpVolume)
+ })
+ }
+}
diff --git a/volume/store/store_unix.go b/volume/service/store_unix.go
index a071e0c2ba..4ccc4b9999 100644
--- a/volume/store/store_unix.go
+++ b/volume/service/store_unix.go
@@ -1,6 +1,6 @@
-// +build linux freebsd
+// +build linux freebsd darwin
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
// normalizeVolumeName is a platform specific function to normalize the name
// of a volume. This is a no-op on Unix-like platforms
diff --git a/volume/store/store_windows.go b/volume/service/store_windows.go
index e4ef71c610..bd46a6893e 100644
--- a/volume/store/store_windows.go
+++ b/volume/service/store_windows.go
@@ -1,4 +1,4 @@
-package store // import "github.com/docker/docker/volume/store"
+package service // import "github.com/docker/docker/volume/service"
import "strings"
diff --git a/volume/testutils/testutils.go b/volume/testutils/testutils.go
index a328db460d..5bb38e3f33 100644
--- a/volume/testutils/testutils.go
+++ b/volume/testutils/testutils.go
@@ -64,7 +64,9 @@ func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
func (FakeVolume) Unmount(_ string) error { return nil }
// Status provides low-level details about the volume
-func (FakeVolume) Status() map[string]interface{} { return nil }
+func (FakeVolume) Status() map[string]interface{} {
+ return map[string]interface{}{"datakey": "datavalue"}
+}
// CreatedAt provides the time the volume (directory) was created at
func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }