This cleans up some of the package API's used for interacting with
volumes, and simplifies management.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -3,6 +3,7 @@ package volume // import "github.com/docker/docker/api/server/router/volume" |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
|
| 6 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 6 | 7 |
// TODO return types need to be refactored into pkg |
| 7 | 8 |
"github.com/docker/docker/api/types" |
| 8 | 9 |
"github.com/docker/docker/api/types/filters" |
| ... | ... |
@@ -11,9 +12,9 @@ import ( |
| 11 | 11 |
// Backend is the methods that need to be implemented to provide |
| 12 | 12 |
// volume specific functionality |
| 13 | 13 |
type Backend interface {
|
| 14 |
- Volumes(filter string) ([]*types.Volume, []string, error) |
|
| 15 |
- VolumeInspect(name string) (*types.Volume, error) |
|
| 16 |
- VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) |
|
| 17 |
- VolumeRm(name string, force bool) error |
|
| 18 |
- VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) |
|
| 14 |
+ List(ctx context.Context, filter filters.Args) ([]*types.Volume, []string, error) |
|
| 15 |
+ Get(ctx context.Context, name string, opts ...opts.GetOption) (*types.Volume, error) |
|
| 16 |
+ Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) |
|
| 17 |
+ Remove(ctx context.Context, name string, opts ...opts.RemoveOption) error |
|
| 18 |
+ Prune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) |
|
| 19 | 19 |
} |
| ... | ... |
@@ -3,7 +3,6 @@ package volume // import "github.com/docker/docker/api/server/router/volume" |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"encoding/json" |
| 6 |
- "errors" |
|
| 7 | 6 |
"io" |
| 8 | 7 |
"net/http" |
| 9 | 8 |
|
| ... | ... |
@@ -11,6 +10,8 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/api/types/filters" |
| 12 | 12 |
volumetypes "github.com/docker/docker/api/types/volume" |
| 13 | 13 |
"github.com/docker/docker/errdefs" |
| 14 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 15 |
+ "github.com/pkg/errors" |
|
| 14 | 16 |
) |
| 15 | 17 |
|
| 16 | 18 |
func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| ... | ... |
@@ -18,7 +19,11 @@ func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter |
| 18 | 18 |
return err |
| 19 | 19 |
} |
| 20 | 20 |
|
| 21 |
- volumes, warnings, err := v.backend.Volumes(r.Form.Get("filters"))
|
|
| 21 |
+ filters, err := filters.FromJSON(r.Form.Get("filters"))
|
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ return errdefs.InvalidParameter(errors.Wrap(err, "error reading volume filters")) |
|
| 24 |
+ } |
|
| 25 |
+ volumes, warnings, err := v.backend.List(ctx, filters) |
|
| 22 | 26 |
if err != nil {
|
| 23 | 27 |
return err |
| 24 | 28 |
} |
| ... | ... |
@@ -30,7 +35,7 @@ func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWrite |
| 30 | 30 |
return err |
| 31 | 31 |
} |
| 32 | 32 |
|
| 33 |
- volume, err := v.backend.VolumeInspect(vars["name"]) |
|
| 33 |
+ volume, err := v.backend.Get(ctx, vars["name"], opts.WithGetResolveStatus) |
|
| 34 | 34 |
if err != nil {
|
| 35 | 35 |
return err |
| 36 | 36 |
} |
| ... | ... |
@@ -54,7 +59,7 @@ func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWri |
| 54 | 54 |
return err |
| 55 | 55 |
} |
| 56 | 56 |
|
| 57 |
- volume, err := v.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels) |
|
| 57 |
+ volume, err := v.backend.Create(ctx, req.Name, req.Driver, opts.WithCreateOptions(req.DriverOpts), opts.WithCreateLabels(req.Labels)) |
|
| 58 | 58 |
if err != nil {
|
| 59 | 59 |
return err |
| 60 | 60 |
} |
| ... | ... |
@@ -66,7 +71,7 @@ func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter, |
| 66 | 66 |
return err |
| 67 | 67 |
} |
| 68 | 68 |
force := httputils.BoolValue(r, "force") |
| 69 |
- if err := v.backend.VolumeRm(vars["name"], force); err != nil {
|
|
| 69 |
+ if err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force)); err != nil {
|
|
| 70 | 70 |
return err |
| 71 | 71 |
} |
| 72 | 72 |
w.WriteHeader(http.StatusNoContent) |
| ... | ... |
@@ -83,7 +88,7 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit |
| 83 | 83 |
return err |
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 |
- pruneReport, err := v.backend.VolumesPrune(ctx, pruneFilters) |
|
| 86 |
+ pruneReport, err := v.backend.Prune(ctx, pruneFilters) |
|
| 87 | 87 |
if err != nil {
|
| 88 | 88 |
return err |
| 89 | 89 |
} |
| ... | ... |
@@ -453,7 +453,7 @@ func initRouter(opts routerOptions) {
|
| 453 | 453 |
container.NewRouter(opts.daemon, decoder), |
| 454 | 454 |
image.NewRouter(opts.daemon.ImageService()), |
| 455 | 455 |
systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildCache), |
| 456 |
- volume.NewRouter(opts.daemon), |
|
| 456 |
+ volume.NewRouter(opts.daemon.VolumesService()), |
|
| 457 | 457 |
build.NewRouter(opts.buildBackend, opts.daemon), |
| 458 | 458 |
sessionrouter.NewRouter(opts.sessionManager), |
| 459 | 459 |
swarmrouter.NewRouter(opts.cluster), |
| ... | ... |
@@ -595,6 +595,7 @@ func createAndStartCluster(cli *DaemonCli, d *daemon.Daemon) (*cluster.Cluster, |
| 595 | 595 |
Root: cli.Config.Root, |
| 596 | 596 |
Name: name, |
| 597 | 597 |
Backend: d, |
| 598 |
+ VolumeBackend: d.VolumesService(), |
|
| 598 | 599 |
ImageBackend: d.ImageService(), |
| 599 | 600 |
PluginBackend: d.PluginManager(), |
| 600 | 601 |
NetworkSubnetsProvider: d, |
| ... | ... |
@@ -127,7 +127,7 @@ func (container *Container) CopyImagePathContent(v volume.Volume, destination st |
| 127 | 127 |
return err |
| 128 | 128 |
} |
| 129 | 129 |
|
| 130 |
- if _, err = ioutil.ReadDir(rootfs); err != nil {
|
|
| 130 |
+ if _, err := os.Stat(rootfs); err != nil {
|
|
| 131 | 131 |
if os.IsNotExist(err) {
|
| 132 | 132 |
return nil |
| 133 | 133 |
} |
| ... | ... |
@@ -85,6 +85,7 @@ type Config struct {
|
| 85 | 85 |
Backend executorpkg.Backend |
| 86 | 86 |
ImageBackend executorpkg.ImageBackend |
| 87 | 87 |
PluginBackend plugin.Backend |
| 88 |
+ VolumeBackend executorpkg.VolumeBackend |
|
| 88 | 89 |
NetworkSubnetsProvider NetworkSubnetsProvider |
| 89 | 90 |
|
| 90 | 91 |
// DefaultAdvertiseAddr is the default host/IP or network interface to use |
| ... | ... |
@@ -18,6 +18,7 @@ import ( |
| 18 | 18 |
clustertypes "github.com/docker/docker/daemon/cluster/provider" |
| 19 | 19 |
networkSettings "github.com/docker/docker/daemon/network" |
| 20 | 20 |
"github.com/docker/docker/plugin" |
| 21 |
+ volumeopts "github.com/docker/docker/volume/service/opts" |
|
| 21 | 22 |
"github.com/docker/libnetwork" |
| 22 | 23 |
"github.com/docker/libnetwork/cluster" |
| 23 | 24 |
networktypes "github.com/docker/libnetwork/types" |
| ... | ... |
@@ -47,7 +48,6 @@ type Backend interface {
|
| 47 | 47 |
SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error |
| 48 | 48 |
SetContainerConfigReferences(name string, refs []*swarmtypes.ConfigReference) error |
| 49 | 49 |
SystemInfo() (*types.Info, error) |
| 50 |
- VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) |
|
| 51 | 50 |
Containers(config *types.ContainerListOptions) ([]*types.Container, error) |
| 52 | 51 |
SetNetworkBootstrapKeys([]*networktypes.EncryptionKey) error |
| 53 | 52 |
DaemonJoinsCluster(provider cluster.Provider) |
| ... | ... |
@@ -62,6 +62,11 @@ type Backend interface {
|
| 62 | 62 |
GetAttachmentStore() *networkSettings.AttachmentStore |
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 |
+// VolumeBackend is used by an executor to perform volume operations |
|
| 66 |
+type VolumeBackend interface {
|
|
| 67 |
+ Create(ctx context.Context, name, driverName string, opts ...volumeopts.CreateOption) (*types.Volume, error) |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 65 | 70 |
// ImageBackend is used by an executor to perform image operations |
| 66 | 71 |
type ImageBackend interface {
|
| 67 | 72 |
PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error |
| ... | ... |
@@ -22,6 +22,7 @@ import ( |
| 22 | 22 |
"github.com/docker/docker/daemon" |
| 23 | 23 |
"github.com/docker/docker/daemon/cluster/convert" |
| 24 | 24 |
executorpkg "github.com/docker/docker/daemon/cluster/executor" |
| 25 |
+ volumeopts "github.com/docker/docker/volume/service/opts" |
|
| 25 | 26 |
"github.com/docker/libnetwork" |
| 26 | 27 |
"github.com/docker/swarmkit/agent/exec" |
| 27 | 28 |
"github.com/docker/swarmkit/api" |
| ... | ... |
@@ -36,23 +37,25 @@ import ( |
| 36 | 36 |
// are mostly naked calls to the client API, seeded with information from |
| 37 | 37 |
// containerConfig. |
| 38 | 38 |
type containerAdapter struct {
|
| 39 |
- backend executorpkg.Backend |
|
| 40 |
- imageBackend executorpkg.ImageBackend |
|
| 41 |
- container *containerConfig |
|
| 42 |
- dependencies exec.DependencyGetter |
|
| 39 |
+ backend executorpkg.Backend |
|
| 40 |
+ imageBackend executorpkg.ImageBackend |
|
| 41 |
+ volumeBackend executorpkg.VolumeBackend |
|
| 42 |
+ container *containerConfig |
|
| 43 |
+ dependencies exec.DependencyGetter |
|
| 43 | 44 |
} |
| 44 | 45 |
|
| 45 |
-func newContainerAdapter(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*containerAdapter, error) {
|
|
| 46 |
+func newContainerAdapter(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*containerAdapter, error) {
|
|
| 46 | 47 |
ctnr, err := newContainerConfig(task, node) |
| 47 | 48 |
if err != nil {
|
| 48 | 49 |
return nil, err |
| 49 | 50 |
} |
| 50 | 51 |
|
| 51 | 52 |
return &containerAdapter{
|
| 52 |
- container: ctnr, |
|
| 53 |
- backend: b, |
|
| 54 |
- imageBackend: i, |
|
| 55 |
- dependencies: dependencies, |
|
| 53 |
+ container: ctnr, |
|
| 54 |
+ backend: b, |
|
| 55 |
+ imageBackend: i, |
|
| 56 |
+ volumeBackend: v, |
|
| 57 |
+ dependencies: dependencies, |
|
| 56 | 58 |
}, nil |
| 57 | 59 |
} |
| 58 | 60 |
|
| ... | ... |
@@ -388,7 +391,10 @@ func (c *containerAdapter) createVolumes(ctx context.Context) error {
|
| 388 | 388 |
req := c.container.volumeCreateRequest(&mount) |
| 389 | 389 |
|
| 390 | 390 |
// Check if this volume exists on the engine |
| 391 |
- if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil {
|
|
| 391 |
+ if _, err := c.volumeBackend.Create(ctx, req.Name, req.Driver, |
|
| 392 |
+ volumeopts.WithCreateOptions(req.DriverOpts), |
|
| 393 |
+ volumeopts.WithCreateLabels(req.Labels), |
|
| 394 |
+ ); err != nil {
|
|
| 392 | 395 |
// TODO(amitshukla): Today, volume create through the engine api does not return an error |
| 393 | 396 |
// when the named volume with the same parameters already exists. |
| 394 | 397 |
// It returns an error if the driver name is different - that is a valid error |
| ... | ... |
@@ -21,8 +21,8 @@ type networkAttacherController struct {
|
| 21 | 21 |
closed chan struct{}
|
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
-func newNetworkAttacherController(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*networkAttacherController, error) {
|
|
| 25 |
- adapter, err := newContainerAdapter(b, i, task, node, dependencies) |
|
| 24 |
+func newNetworkAttacherController(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*networkAttacherController, error) {
|
|
| 25 |
+ adapter, err := newContainerAdapter(b, i, v, task, node, dependencies) |
|
| 26 | 26 |
if err != nil {
|
| 27 | 27 |
return nil, err |
| 28 | 28 |
} |
| ... | ... |
@@ -40,8 +40,8 @@ type controller struct {
|
| 40 | 40 |
var _ exec.Controller = &controller{}
|
| 41 | 41 |
|
| 42 | 42 |
// NewController returns a docker exec runner for the provided task. |
| 43 |
-func newController(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*controller, error) {
|
|
| 44 |
- adapter, err := newContainerAdapter(b, i, task, node, dependencies) |
|
| 43 |
+func newController(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*controller, error) {
|
|
| 44 |
+ adapter, err := newContainerAdapter(b, i, v, task, node, dependencies) |
|
| 45 | 45 |
if err != nil {
|
| 46 | 46 |
return nil, err |
| 47 | 47 |
} |
| ... | ... |
@@ -28,17 +28,19 @@ type executor struct {
|
| 28 | 28 |
backend executorpkg.Backend |
| 29 | 29 |
imageBackend executorpkg.ImageBackend |
| 30 | 30 |
pluginBackend plugin.Backend |
| 31 |
+ volumeBackend executorpkg.VolumeBackend |
|
| 31 | 32 |
dependencies exec.DependencyManager |
| 32 | 33 |
mutex sync.Mutex // This mutex protects the following node field |
| 33 | 34 |
node *api.NodeDescription |
| 34 | 35 |
} |
| 35 | 36 |
|
| 36 | 37 |
// NewExecutor returns an executor from the docker client. |
| 37 |
-func NewExecutor(b executorpkg.Backend, p plugin.Backend, i executorpkg.ImageBackend) exec.Executor {
|
|
| 38 |
+func NewExecutor(b executorpkg.Backend, p plugin.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend) exec.Executor {
|
|
| 38 | 39 |
return &executor{
|
| 39 | 40 |
backend: b, |
| 40 | 41 |
pluginBackend: p, |
| 41 | 42 |
imageBackend: i, |
| 43 |
+ volumeBackend: v, |
|
| 42 | 44 |
dependencies: agent.NewDependencyManager(), |
| 43 | 45 |
} |
| 44 | 46 |
} |
| ... | ... |
@@ -211,7 +213,7 @@ func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
|
| 211 | 211 |
e.mutex.Unlock() |
| 212 | 212 |
|
| 213 | 213 |
if t.Spec.GetAttachment() != nil {
|
| 214 |
- return newNetworkAttacherController(e.backend, e.imageBackend, t, nodeDescription, dependencyGetter) |
|
| 214 |
+ return newNetworkAttacherController(e.backend, e.imageBackend, e.volumeBackend, t, nodeDescription, dependencyGetter) |
|
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 | 217 |
var ctlr exec.Controller |
| ... | ... |
@@ -240,7 +242,7 @@ func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
|
| 240 | 240 |
return ctlr, fmt.Errorf("unsupported runtime type: %q", runtimeKind)
|
| 241 | 241 |
} |
| 242 | 242 |
case *api.TaskSpec_Container: |
| 243 |
- c, err := newController(e.backend, e.imageBackend, t, nodeDescription, dependencyGetter) |
|
| 243 |
+ c, err := newController(e.backend, e.imageBackend, e.volumeBackend, t, nodeDescription, dependencyGetter) |
|
| 244 | 244 |
if err != nil {
|
| 245 | 245 |
return ctlr, err |
| 246 | 246 |
} |
| ... | ... |
@@ -52,7 +52,7 @@ func TestHealthStates(t *testing.T) {
|
| 52 | 52 |
EventsService: e, |
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 |
- controller, err := newController(daemon, nil, task, nil, nil) |
|
| 55 |
+ controller, err := newController(daemon, nil, nil, task, nil, nil) |
|
| 56 | 56 |
if err != nil {
|
| 57 | 57 |
t.Fatalf("create controller fail %v", err)
|
| 58 | 58 |
} |
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 | 14 |
func newTestControllerWithMount(m api.Mount) (*controller, error) {
|
| 15 |
- return newController(&daemon.Daemon{}, nil, &api.Task{
|
|
| 15 |
+ return newController(&daemon.Daemon{}, nil, nil, &api.Task{
|
|
| 16 | 16 |
ID: stringid.GenerateRandomID(), |
| 17 | 17 |
ServiceID: stringid.GenerateRandomID(), |
| 18 | 18 |
Spec: api.TaskSpec{
|
| ... | ... |
@@ -123,7 +123,9 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
|
| 123 | 123 |
Executor: container.NewExecutor( |
| 124 | 124 |
n.cluster.config.Backend, |
| 125 | 125 |
n.cluster.config.PluginBackend, |
| 126 |
- n.cluster.config.ImageBackend), |
|
| 126 |
+ n.cluster.config.ImageBackend, |
|
| 127 |
+ n.cluster.config.VolumeBackend, |
|
| 128 |
+ ), |
|
| 127 | 129 |
HeartbeatTick: n.cluster.config.RaftHeartbeatTick, |
| 128 | 130 |
// Recommended value in etcd/raft is 10 x (HeartbeatTick). |
| 129 | 131 |
// Lower values were seen to have caused instability because of |
| ... | ... |
@@ -7,8 +7,6 @@ import ( |
| 7 | 7 |
"strings" |
| 8 | 8 |
"time" |
| 9 | 9 |
|
| 10 |
- "github.com/pkg/errors" |
|
| 11 |
- |
|
| 12 | 10 |
"github.com/docker/docker/api/types" |
| 13 | 11 |
containertypes "github.com/docker/docker/api/types/container" |
| 14 | 12 |
networktypes "github.com/docker/docker/api/types/network" |
| ... | ... |
@@ -16,10 +14,10 @@ import ( |
| 16 | 16 |
"github.com/docker/docker/errdefs" |
| 17 | 17 |
"github.com/docker/docker/image" |
| 18 | 18 |
"github.com/docker/docker/pkg/idtools" |
| 19 |
- "github.com/docker/docker/pkg/stringid" |
|
| 20 | 19 |
"github.com/docker/docker/pkg/system" |
| 21 | 20 |
"github.com/docker/docker/runconfig" |
| 22 | 21 |
"github.com/opencontainers/selinux/go-selinux/label" |
| 22 |
+ "github.com/pkg/errors" |
|
| 23 | 23 |
"github.com/sirupsen/logrus" |
| 24 | 24 |
) |
| 25 | 25 |
|
| ... | ... |
@@ -255,24 +253,6 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig) |
| 255 | 255 |
return nil, nil |
| 256 | 256 |
} |
| 257 | 257 |
|
| 258 |
-// VolumeCreate creates a volume with the specified name, driver, and opts |
|
| 259 |
-// This is called directly from the Engine API |
|
| 260 |
-func (daemon *Daemon) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) {
|
|
| 261 |
- if name == "" {
|
|
| 262 |
- name = stringid.GenerateNonCryptoID() |
|
| 263 |
- } |
|
| 264 |
- |
|
| 265 |
- v, err := daemon.volumes.Create(name, driverName, opts, labels) |
|
| 266 |
- if err != nil {
|
|
| 267 |
- return nil, err |
|
| 268 |
- } |
|
| 269 |
- |
|
| 270 |
- daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
|
|
| 271 |
- apiV := volumeToAPIType(v) |
|
| 272 |
- apiV.Mountpoint = v.Path() |
|
| 273 |
- return apiV, nil |
|
| 274 |
-} |
|
| 275 |
- |
|
| 276 | 258 |
func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error {
|
| 277 | 259 |
if img != nil && img.Config != nil {
|
| 278 | 260 |
if err := merge(config, img.Config); err != nil {
|
| ... | ... |
@@ -3,6 +3,7 @@ |
| 3 | 3 |
package daemon // import "github.com/docker/docker/daemon" |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ "context" |
|
| 6 | 7 |
"fmt" |
| 7 | 8 |
"os" |
| 8 | 9 |
"path/filepath" |
| ... | ... |
@@ -11,6 +12,7 @@ import ( |
| 11 | 11 |
mounttypes "github.com/docker/docker/api/types/mount" |
| 12 | 12 |
"github.com/docker/docker/container" |
| 13 | 13 |
"github.com/docker/docker/pkg/stringid" |
| 14 |
+ volumeopts "github.com/docker/docker/volume/service/opts" |
|
| 14 | 15 |
"github.com/opencontainers/selinux/go-selinux/label" |
| 15 | 16 |
"github.com/sirupsen/logrus" |
| 16 | 17 |
) |
| ... | ... |
@@ -46,16 +48,16 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con |
| 46 | 46 |
return fmt.Errorf("cannot mount volume over existing file, file exists %s", path)
|
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
- v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil, nil) |
|
| 49 |
+ v, err := daemon.volumes.Create(context.TODO(), name, hostConfig.VolumeDriver, volumeopts.WithCreateReference(container.ID)) |
|
| 50 | 50 |
if err != nil {
|
| 51 | 51 |
return err |
| 52 | 52 |
} |
| 53 | 53 |
|
| 54 |
- if err := label.Relabel(v.Path(), container.MountLabel, true); err != nil {
|
|
| 54 |
+ if err := label.Relabel(v.Mountpoint, container.MountLabel, true); err != nil {
|
|
| 55 | 55 |
return err |
| 56 | 56 |
} |
| 57 | 57 |
|
| 58 |
- container.AddMountPointWithVolume(destination, v, true) |
|
| 58 |
+ container.AddMountPointWithVolume(destination, &volumeWrapper{v: v, s: daemon.volumes}, true)
|
|
| 59 | 59 |
} |
| 60 | 60 |
return daemon.populateVolumes(container) |
| 61 | 61 |
} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package daemon // import "github.com/docker/docker/daemon" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "context" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"runtime" |
| 6 | 7 |
|
| ... | ... |
@@ -8,6 +9,7 @@ import ( |
| 8 | 8 |
"github.com/docker/docker/container" |
| 9 | 9 |
"github.com/docker/docker/pkg/stringid" |
| 10 | 10 |
volumemounts "github.com/docker/docker/volume/mounts" |
| 11 |
+ volumeopts "github.com/docker/docker/volume/service/opts" |
|
| 11 | 12 |
) |
| 12 | 13 |
|
| 13 | 14 |
// createContainerOSSpecificSettings performs host-OS specific container create functionality |
| ... | ... |
@@ -49,7 +51,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con |
| 49 | 49 |
|
| 50 | 50 |
// Create the volume in the volume driver. If it doesn't exist, |
| 51 | 51 |
// a new one will be created. |
| 52 |
- v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil, nil) |
|
| 52 |
+ v, err := daemon.volumes.Create(context.TODO(), mp.Name, volumeDriver, volumeopts.WithCreateReference(container.ID)) |
|
| 53 | 53 |
if err != nil {
|
| 54 | 54 |
return err |
| 55 | 55 |
} |
| ... | ... |
@@ -85,7 +87,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con |
| 85 | 85 |
// } |
| 86 | 86 |
|
| 87 | 87 |
// Add it to container.MountPoints |
| 88 |
- container.AddMountPointWithVolume(mp.Destination, v, mp.RW) |
|
| 88 |
+ container.AddMountPointWithVolume(mp.Destination, &volumeWrapper{v: v, s: daemon.volumes}, mp.RW)
|
|
| 89 | 89 |
} |
| 90 | 90 |
return nil |
| 91 | 91 |
} |
| ... | ... |
@@ -52,9 +52,7 @@ import ( |
| 52 | 52 |
refstore "github.com/docker/docker/reference" |
| 53 | 53 |
"github.com/docker/docker/registry" |
| 54 | 54 |
"github.com/docker/docker/runconfig" |
| 55 |
- volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 56 |
- "github.com/docker/docker/volume/local" |
|
| 57 |
- "github.com/docker/docker/volume/store" |
|
| 55 |
+ volumesservice "github.com/docker/docker/volume/service" |
|
| 58 | 56 |
"github.com/docker/libnetwork" |
| 59 | 57 |
"github.com/docker/libnetwork/cluster" |
| 60 | 58 |
nwconfig "github.com/docker/libnetwork/config" |
| ... | ... |
@@ -83,7 +81,7 @@ type Daemon struct {
|
| 83 | 83 |
RegistryService registry.Service |
| 84 | 84 |
EventsService *events.Events |
| 85 | 85 |
netController libnetwork.NetworkController |
| 86 |
- volumes *store.VolumeStore |
|
| 86 |
+ volumes *volumesservice.VolumesService |
|
| 87 | 87 |
discoveryWatcher discovery.Reloader |
| 88 | 88 |
root string |
| 89 | 89 |
seccompEnabled bool |
| ... | ... |
@@ -784,8 +782,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe |
| 784 | 784 |
return nil, err |
| 785 | 785 |
} |
| 786 | 786 |
|
| 787 |
- // Configure the volumes driver |
|
| 788 |
- volStore, err := d.configureVolumes(rootIDs) |
|
| 787 |
+ d.volumes, err = volumesservice.NewVolumeService(config.Root, d.PluginStore, rootIDs, d) |
|
| 789 | 788 |
if err != nil {
|
| 790 | 789 |
return nil, err |
| 791 | 790 |
} |
| ... | ... |
@@ -855,7 +852,6 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe |
| 855 | 855 |
d.statsCollector = d.newStatsCollector(1 * time.Second) |
| 856 | 856 |
|
| 857 | 857 |
d.EventsService = events.New() |
| 858 |
- d.volumes = volStore |
|
| 859 | 858 |
d.root = config.Root |
| 860 | 859 |
d.idMappings = idMappings |
| 861 | 860 |
d.seccompEnabled = sysInfo.Seccomp |
| ... | ... |
@@ -1144,18 +1140,6 @@ func setDefaultMtu(conf *config.Config) {
|
| 1144 | 1144 |
conf.Mtu = config.DefaultNetworkMtu |
| 1145 | 1145 |
} |
| 1146 | 1146 |
|
| 1147 |
-func (daemon *Daemon) configureVolumes(rootIDs idtools.IDPair) (*store.VolumeStore, error) {
|
|
| 1148 |
- volumeDriver, err := local.New(daemon.configStore.Root, rootIDs) |
|
| 1149 |
- if err != nil {
|
|
| 1150 |
- return nil, err |
|
| 1151 |
- } |
|
| 1152 |
- drivers := volumedrivers.NewStore(daemon.PluginStore) |
|
| 1153 |
- if !drivers.Register(volumeDriver, volumeDriver.Name()) {
|
|
| 1154 |
- return nil, errors.New("local volume driver could not be registered")
|
|
| 1155 |
- } |
|
| 1156 |
- return store.New(daemon.configStore.Root, drivers) |
|
| 1157 |
-} |
|
| 1158 |
- |
|
| 1159 | 1147 |
// IsShuttingDown tells whether the daemon is shutting down or not |
| 1160 | 1148 |
func (daemon *Daemon) IsShuttingDown() bool {
|
| 1161 | 1149 |
return daemon.shutdown |
| ... | ... |
@@ -13,9 +13,7 @@ import ( |
| 13 | 13 |
_ "github.com/docker/docker/pkg/discovery/memory" |
| 14 | 14 |
"github.com/docker/docker/pkg/idtools" |
| 15 | 15 |
"github.com/docker/docker/pkg/truncindex" |
| 16 |
- volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 17 |
- "github.com/docker/docker/volume/local" |
|
| 18 |
- "github.com/docker/docker/volume/store" |
|
| 16 |
+ volumesservice "github.com/docker/docker/volume/service" |
|
| 19 | 17 |
"github.com/docker/go-connections/nat" |
| 20 | 18 |
"github.com/docker/libnetwork" |
| 21 | 19 |
"github.com/gotestyourself/gotestyourself/assert" |
| ... | ... |
@@ -120,18 +118,10 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
|
| 120 | 120 |
repository: tmp, |
| 121 | 121 |
root: tmp, |
| 122 | 122 |
} |
| 123 |
- drivers := volumedrivers.NewStore(nil) |
|
| 124 |
- daemon.volumes, err = store.New(tmp, drivers) |
|
| 123 |
+ daemon.volumes, err = volumesservice.NewVolumeService(tmp, nil, idtools.IDPair{UID: 0, GID: 0}, daemon)
|
|
| 125 | 124 |
if err != nil {
|
| 126 | 125 |
return nil, err |
| 127 | 126 |
} |
| 128 |
- |
|
| 129 |
- volumesDriver, err := local.New(tmp, idtools.IDPair{UID: 0, GID: 0})
|
|
| 130 |
- if err != nil {
|
|
| 131 |
- return nil, err |
|
| 132 |
- } |
|
| 133 |
- drivers.Register(volumesDriver, volumesDriver.Name()) |
|
| 134 |
- |
|
| 135 | 127 |
return daemon, nil |
| 136 | 128 |
} |
| 137 | 129 |
|
| ... | ... |
@@ -11,8 +11,6 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/container" |
| 12 | 12 |
"github.com/docker/docker/errdefs" |
| 13 | 13 |
"github.com/docker/docker/pkg/system" |
| 14 |
- "github.com/docker/docker/volume" |
|
| 15 |
- volumestore "github.com/docker/docker/volume/store" |
|
| 16 | 14 |
"github.com/pkg/errors" |
| 17 | 15 |
"github.com/sirupsen/logrus" |
| 18 | 16 |
) |
| ... | ... |
@@ -152,35 +150,3 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo |
| 152 | 152 |
daemon.LogContainerEvent(container, "destroy") |
| 153 | 153 |
return nil |
| 154 | 154 |
} |
| 155 |
- |
|
| 156 |
-// VolumeRm removes the volume with the given name. |
|
| 157 |
-// If the volume is referenced by a container it is not removed |
|
| 158 |
-// This is called directly from the Engine API |
|
| 159 |
-func (daemon *Daemon) VolumeRm(name string, force bool) error {
|
|
| 160 |
- v, err := daemon.volumes.Get(name) |
|
| 161 |
- if err != nil {
|
|
| 162 |
- if force && volumestore.IsNotExist(err) {
|
|
| 163 |
- return nil |
|
| 164 |
- } |
|
| 165 |
- return err |
|
| 166 |
- } |
|
| 167 |
- |
|
| 168 |
- err = daemon.volumeRm(v) |
|
| 169 |
- if err != nil && volumestore.IsInUse(err) {
|
|
| 170 |
- return errdefs.Conflict(err) |
|
| 171 |
- } |
|
| 172 |
- |
|
| 173 |
- if err == nil || force {
|
|
| 174 |
- daemon.volumes.Purge(name) |
|
| 175 |
- return nil |
|
| 176 |
- } |
|
| 177 |
- return err |
|
| 178 |
-} |
|
| 179 |
- |
|
| 180 |
-func (daemon *Daemon) volumeRm(v volume.Volume) error {
|
|
| 181 |
- if err := daemon.volumes.Remove(v); err != nil {
|
|
| 182 |
- return errors.Wrap(err, "unable to remove volume") |
|
| 183 |
- } |
|
| 184 |
- daemon.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
|
|
| 185 |
- return nil |
|
| 186 |
-} |
| ... | ... |
@@ -7,9 +7,6 @@ import ( |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/api/types" |
| 9 | 9 |
"github.com/docker/docker/api/types/filters" |
| 10 |
- "github.com/docker/docker/pkg/directory" |
|
| 11 |
- "github.com/docker/docker/volume" |
|
| 12 |
- "github.com/sirupsen/logrus" |
|
| 13 | 10 |
) |
| 14 | 11 |
|
| 15 | 12 |
// SystemDiskUsage returns information about the daemon data disk usage |
| ... | ... |
@@ -34,39 +31,11 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er |
| 34 | 34 |
return nil, fmt.Errorf("failed to retrieve image list: %v", err)
|
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 |
- volumes, err := daemon.volumes.FilterByDriver(volume.DefaultDriverName) |
|
| 37 |
+ localVolumes, err := daemon.volumes.LocalVolumesSize(ctx) |
|
| 38 | 38 |
if err != nil {
|
| 39 | 39 |
return nil, err |
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
- var allVolumes []*types.Volume |
|
| 43 |
- for _, v := range volumes {
|
|
| 44 |
- select {
|
|
| 45 |
- case <-ctx.Done(): |
|
| 46 |
- return nil, ctx.Err() |
|
| 47 |
- default: |
|
| 48 |
- } |
|
| 49 |
- if d, ok := v.(volume.DetailedVolume); ok {
|
|
| 50 |
- if len(d.Options()) > 0 {
|
|
| 51 |
- // skip local volumes with mount options since these could have external |
|
| 52 |
- // mounted filesystems that will be slow to enumerate. |
|
| 53 |
- continue |
|
| 54 |
- } |
|
| 55 |
- } |
|
| 56 |
- |
|
| 57 |
- name := v.Name() |
|
| 58 |
- refs := daemon.volumes.Refs(v) |
|
| 59 |
- |
|
| 60 |
- tv := volumeToAPIType(v) |
|
| 61 |
- sz, err := directory.Size(ctx, v.Path()) |
|
| 62 |
- if err != nil {
|
|
| 63 |
- logrus.Warnf("failed to determine size of volume %v", name)
|
|
| 64 |
- sz = -1 |
|
| 65 |
- } |
|
| 66 |
- tv.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(len(refs))}
|
|
| 67 |
- allVolumes = append(allVolumes, tv) |
|
| 68 |
- } |
|
| 69 |
- |
|
| 70 | 42 |
allLayersSize, err := daemon.imageService.LayerDiskUsage(ctx) |
| 71 | 43 |
if err != nil {
|
| 72 | 44 |
return nil, err |
| ... | ... |
@@ -75,7 +44,7 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er |
| 75 | 75 |
return &types.DiskUsage{
|
| 76 | 76 |
LayersSize: allLayersSize, |
| 77 | 77 |
Containers: allContainers, |
| 78 |
- Volumes: allVolumes, |
|
| 78 |
+ Volumes: localVolumes, |
|
| 79 | 79 |
Images: allImages, |
| 80 | 80 |
}, nil |
| 81 | 81 |
} |
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/image" |
| 15 | 15 |
"github.com/docker/docker/layer" |
| 16 | 16 |
"github.com/opencontainers/go-digest" |
| 17 |
+ "github.com/pkg/errors" |
|
| 17 | 18 |
"github.com/sirupsen/logrus" |
| 18 | 19 |
) |
| 19 | 20 |
|
| ... | ... |
@@ -26,7 +27,7 @@ var imagesAcceptedFilters = map[string]bool{
|
| 26 | 26 |
|
| 27 | 27 |
// errPruneRunning is returned when a prune request is received while |
| 28 | 28 |
// one is in progress |
| 29 |
-var errPruneRunning = fmt.Errorf("a prune operation is already running")
|
|
| 29 |
+var errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
|
|
| 30 | 30 |
|
| 31 | 31 |
// ImagesPrune removes unused images |
| 32 | 32 |
func (i *ImageService) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
|
| ... | ... |
@@ -13,7 +13,6 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/container" |
| 14 | 14 |
"github.com/docker/docker/daemon/network" |
| 15 | 15 |
"github.com/docker/docker/errdefs" |
| 16 |
- volumestore "github.com/docker/docker/volume/store" |
|
| 17 | 16 |
"github.com/docker/go-connections/nat" |
| 18 | 17 |
) |
| 19 | 18 |
|
| ... | ... |
@@ -236,22 +235,6 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, err |
| 236 | 236 |
}, nil |
| 237 | 237 |
} |
| 238 | 238 |
|
| 239 |
-// VolumeInspect looks up a volume by name. An error is returned if |
|
| 240 |
-// the volume cannot be found. |
|
| 241 |
-func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
|
|
| 242 |
- v, err := daemon.volumes.Get(name) |
|
| 243 |
- if err != nil {
|
|
| 244 |
- if volumestore.IsNotExist(err) {
|
|
| 245 |
- return nil, volumeNotFound(name) |
|
| 246 |
- } |
|
| 247 |
- return nil, errdefs.System(err) |
|
| 248 |
- } |
|
| 249 |
- apiV := volumeToAPIType(v) |
|
| 250 |
- apiV.Mountpoint = v.Path() |
|
| 251 |
- apiV.Status = v.Status() |
|
| 252 |
- return apiV, nil |
|
| 253 |
-} |
|
| 254 |
- |
|
| 255 | 239 |
func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings {
|
| 256 | 240 |
result := &v1p20.NetworkSettings{
|
| 257 | 241 |
NetworkSettingsBase: types.NetworkSettingsBase{
|
| ... | ... |
@@ -12,19 +12,11 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/daemon/images" |
| 13 | 13 |
"github.com/docker/docker/errdefs" |
| 14 | 14 |
"github.com/docker/docker/image" |
| 15 |
- "github.com/docker/docker/volume" |
|
| 16 | 15 |
"github.com/docker/go-connections/nat" |
| 17 | 16 |
"github.com/pkg/errors" |
| 18 | 17 |
"github.com/sirupsen/logrus" |
| 19 | 18 |
) |
| 20 | 19 |
|
| 21 |
-var acceptedVolumeFilterTags = map[string]bool{
|
|
| 22 |
- "dangling": true, |
|
| 23 |
- "name": true, |
|
| 24 |
- "driver": true, |
|
| 25 |
- "label": true, |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 | 20 |
var acceptedPsFilterTags = map[string]bool{
|
| 29 | 21 |
"ancestor": true, |
| 30 | 22 |
"before": true, |
| ... | ... |
@@ -605,87 +597,6 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty |
| 605 | 605 |
return &c, nil |
| 606 | 606 |
} |
| 607 | 607 |
|
| 608 |
-// Volumes lists known volumes, using the filter to restrict the range |
|
| 609 |
-// of volumes returned. |
|
| 610 |
-func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) {
|
|
| 611 |
- var ( |
|
| 612 |
- volumesOut []*types.Volume |
|
| 613 |
- ) |
|
| 614 |
- volFilters, err := filters.FromJSON(filter) |
|
| 615 |
- if err != nil {
|
|
| 616 |
- return nil, nil, err |
|
| 617 |
- } |
|
| 618 |
- |
|
| 619 |
- if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil {
|
|
| 620 |
- return nil, nil, err |
|
| 621 |
- } |
|
| 622 |
- |
|
| 623 |
- volumes, warnings, err := daemon.volumes.List() |
|
| 624 |
- if err != nil {
|
|
| 625 |
- return nil, nil, err |
|
| 626 |
- } |
|
| 627 |
- |
|
| 628 |
- filterVolumes, err := daemon.filterVolumes(volumes, volFilters) |
|
| 629 |
- if err != nil {
|
|
| 630 |
- return nil, nil, err |
|
| 631 |
- } |
|
| 632 |
- for _, v := range filterVolumes {
|
|
| 633 |
- apiV := volumeToAPIType(v) |
|
| 634 |
- if vv, ok := v.(interface {
|
|
| 635 |
- CachedPath() string |
|
| 636 |
- }); ok {
|
|
| 637 |
- apiV.Mountpoint = vv.CachedPath() |
|
| 638 |
- } else {
|
|
| 639 |
- apiV.Mountpoint = v.Path() |
|
| 640 |
- } |
|
| 641 |
- volumesOut = append(volumesOut, apiV) |
|
| 642 |
- } |
|
| 643 |
- return volumesOut, warnings, nil |
|
| 644 |
-} |
|
| 645 |
- |
|
| 646 |
-// filterVolumes filters volume list according to user specified filter |
|
| 647 |
-// and returns user chosen volumes |
|
| 648 |
-func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ([]volume.Volume, error) {
|
|
| 649 |
- // if filter is empty, return original volume list |
|
| 650 |
- if filter.Len() == 0 {
|
|
| 651 |
- return vols, nil |
|
| 652 |
- } |
|
| 653 |
- |
|
| 654 |
- var retVols []volume.Volume |
|
| 655 |
- for _, vol := range vols {
|
|
| 656 |
- if filter.Contains("name") {
|
|
| 657 |
- if !filter.Match("name", vol.Name()) {
|
|
| 658 |
- continue |
|
| 659 |
- } |
|
| 660 |
- } |
|
| 661 |
- if filter.Contains("driver") {
|
|
| 662 |
- if !filter.ExactMatch("driver", vol.DriverName()) {
|
|
| 663 |
- continue |
|
| 664 |
- } |
|
| 665 |
- } |
|
| 666 |
- if filter.Contains("label") {
|
|
| 667 |
- v, ok := vol.(volume.DetailedVolume) |
|
| 668 |
- if !ok {
|
|
| 669 |
- continue |
|
| 670 |
- } |
|
| 671 |
- if !filter.MatchKVList("label", v.Labels()) {
|
|
| 672 |
- continue |
|
| 673 |
- } |
|
| 674 |
- } |
|
| 675 |
- retVols = append(retVols, vol) |
|
| 676 |
- } |
|
| 677 |
- danglingOnly := false |
|
| 678 |
- if filter.Contains("dangling") {
|
|
| 679 |
- if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
|
|
| 680 |
- danglingOnly = true |
|
| 681 |
- } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
|
|
| 682 |
- return nil, invalidFilter{"dangling", filter.Get("dangling")}
|
|
| 683 |
- } |
|
| 684 |
- retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly) |
|
| 685 |
- } |
|
| 686 |
- return retVols, nil |
|
| 687 |
-} |
|
| 688 |
- |
|
| 689 | 608 |
func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
|
| 690 | 609 |
if !ancestorMap[imageID] {
|
| 691 | 610 |
for _, id := range getChildren(imageID) {
|
| ... | ... |
@@ -1,12 +1,13 @@ |
| 1 | 1 |
package daemon // import "github.com/docker/docker/daemon" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "context" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"strings" |
| 6 | 7 |
|
| 7 | 8 |
mounttypes "github.com/docker/docker/api/types/mount" |
| 8 | 9 |
"github.com/docker/docker/container" |
| 9 |
- volumestore "github.com/docker/docker/volume/store" |
|
| 10 |
+ volumesservice "github.com/docker/docker/volume/service" |
|
| 10 | 11 |
) |
| 11 | 12 |
|
| 12 | 13 |
func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
|
| ... | ... |
@@ -20,11 +21,12 @@ func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
|
| 20 | 20 |
|
| 21 | 21 |
func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool) error {
|
| 22 | 22 |
var rmErrors []string |
| 23 |
+ ctx := context.TODO() |
|
| 23 | 24 |
for _, m := range container.MountPoints {
|
| 24 | 25 |
if m.Type != mounttypes.TypeVolume || m.Volume == nil {
|
| 25 | 26 |
continue |
| 26 | 27 |
} |
| 27 |
- daemon.volumes.Dereference(m.Volume, container.ID) |
|
| 28 |
+ daemon.volumes.Release(ctx, m.Volume.Name(), container.ID) |
|
| 28 | 29 |
if !rm {
|
| 29 | 30 |
continue |
| 30 | 31 |
} |
| ... | ... |
@@ -35,13 +37,13 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool) |
| 35 | 35 |
continue |
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 |
- err := daemon.volumes.Remove(m.Volume) |
|
| 38 |
+ err := daemon.volumes.Remove(ctx, m.Volume.Name()) |
|
| 39 | 39 |
// Ignore volume in use errors because having this |
| 40 | 40 |
// volume being referenced by other container is |
| 41 | 41 |
// not an error, but an implementation detail. |
| 42 | 42 |
// This prevents docker from logging "ERROR: Volume in use" |
| 43 | 43 |
// where there is another container using the volume. |
| 44 |
- if err != nil && !volumestore.IsInUse(err) {
|
|
| 44 |
+ if err != nil && !volumesservice.IsInUse(err) {
|
|
| 45 | 45 |
rmErrors = append(rmErrors, err.Error()) |
| 46 | 46 |
} |
| 47 | 47 |
} |
| ... | ... |
@@ -10,27 +10,23 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/api/types" |
| 11 | 11 |
"github.com/docker/docker/api/types/filters" |
| 12 | 12 |
timetypes "github.com/docker/docker/api/types/time" |
| 13 |
- "github.com/docker/docker/pkg/directory" |
|
| 13 |
+ "github.com/docker/docker/errdefs" |
|
| 14 | 14 |
"github.com/docker/docker/runconfig" |
| 15 |
- "github.com/docker/docker/volume" |
|
| 16 | 15 |
"github.com/docker/libnetwork" |
| 16 |
+ "github.com/pkg/errors" |
|
| 17 | 17 |
"github.com/sirupsen/logrus" |
| 18 | 18 |
) |
| 19 | 19 |
|
| 20 | 20 |
var ( |
| 21 | 21 |
// errPruneRunning is returned when a prune request is received while |
| 22 | 22 |
// one is in progress |
| 23 |
- errPruneRunning = fmt.Errorf("a prune operation is already running")
|
|
| 23 |
+ errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
|
|
| 24 | 24 |
|
| 25 | 25 |
containersAcceptedFilters = map[string]bool{
|
| 26 | 26 |
"label": true, |
| 27 | 27 |
"label!": true, |
| 28 | 28 |
"until": true, |
| 29 | 29 |
} |
| 30 |
- volumesAcceptedFilters = map[string]bool{
|
|
| 31 |
- "label": true, |
|
| 32 |
- "label!": true, |
|
| 33 |
- } |
|
| 34 | 30 |
|
| 35 | 31 |
networksAcceptedFilters = map[string]bool{
|
| 36 | 32 |
"label": true, |
| ... | ... |
@@ -92,67 +88,6 @@ func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters. |
| 92 | 92 |
return rep, nil |
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
-// VolumesPrune removes unused local volumes |
|
| 96 |
-func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
|
|
| 97 |
- if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
|
|
| 98 |
- return nil, errPruneRunning |
|
| 99 |
- } |
|
| 100 |
- defer atomic.StoreInt32(&daemon.pruneRunning, 0) |
|
| 101 |
- |
|
| 102 |
- // make sure that only accepted filters have been received |
|
| 103 |
- err := pruneFilters.Validate(volumesAcceptedFilters) |
|
| 104 |
- if err != nil {
|
|
| 105 |
- return nil, err |
|
| 106 |
- } |
|
| 107 |
- |
|
| 108 |
- rep := &types.VolumesPruneReport{}
|
|
| 109 |
- |
|
| 110 |
- volumes, err := daemon.volumes.FilterByDriver(volume.DefaultDriverName) |
|
| 111 |
- if err != nil {
|
|
| 112 |
- return nil, err |
|
| 113 |
- } |
|
| 114 |
- |
|
| 115 |
- for _, v := range volumes {
|
|
| 116 |
- select {
|
|
| 117 |
- case <-ctx.Done(): |
|
| 118 |
- logrus.Debugf("VolumesPrune operation cancelled: %#v", *rep)
|
|
| 119 |
- err := ctx.Err() |
|
| 120 |
- if err == context.Canceled {
|
|
| 121 |
- return rep, nil |
|
| 122 |
- } |
|
| 123 |
- return rep, err |
|
| 124 |
- default: |
|
| 125 |
- } |
|
| 126 |
- |
|
| 127 |
- name := v.Name() |
|
| 128 |
- refs := daemon.volumes.Refs(v) |
|
| 129 |
- |
|
| 130 |
- if len(refs) == 0 {
|
|
| 131 |
- detailedVolume, ok := v.(volume.DetailedVolume) |
|
| 132 |
- if ok {
|
|
| 133 |
- if !matchLabels(pruneFilters, detailedVolume.Labels()) {
|
|
| 134 |
- continue |
|
| 135 |
- } |
|
| 136 |
- } |
|
| 137 |
- |
|
| 138 |
- vSize, err := directory.Size(ctx, v.Path()) |
|
| 139 |
- if err != nil {
|
|
| 140 |
- logrus.Warnf("could not determine size of volume %s: %v", name, err)
|
|
| 141 |
- } |
|
| 142 |
- err = daemon.volumeRm(v) |
|
| 143 |
- if err != nil {
|
|
| 144 |
- logrus.Warnf("could not remove volume %s: %v", name, err)
|
|
| 145 |
- continue |
|
| 146 |
- } |
|
| 147 |
- rep.SpaceReclaimed += uint64(vSize) |
|
| 148 |
- rep.VolumesDeleted = append(rep.VolumesDeleted, name) |
|
| 149 |
- } |
|
| 150 |
- |
|
| 151 |
- } |
|
| 152 |
- |
|
| 153 |
- return rep, nil |
|
| 154 |
-} |
|
| 155 |
- |
|
| 156 | 95 |
// localNetworksPrune removes unused local networks |
| 157 | 96 |
func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
|
| 158 | 97 |
rep := &types.NetworksPruneReport{}
|
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package daemon // import "github.com/docker/docker/daemon" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "context" |
|
| 4 | 5 |
"os" |
| 5 | 6 |
"path/filepath" |
| 6 | 7 |
"reflect" |
| ... | ... |
@@ -15,6 +16,8 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/errdefs" |
| 16 | 16 |
"github.com/docker/docker/volume" |
| 17 | 17 |
volumemounts "github.com/docker/docker/volume/mounts" |
| 18 |
+ "github.com/docker/docker/volume/service" |
|
| 19 |
+ volumeopts "github.com/docker/docker/volume/service/opts" |
|
| 18 | 20 |
"github.com/pkg/errors" |
| 19 | 21 |
"github.com/sirupsen/logrus" |
| 20 | 22 |
) |
| ... | ... |
@@ -27,23 +30,6 @@ var ( |
| 27 | 27 |
|
| 28 | 28 |
type mounts []container.Mount |
| 29 | 29 |
|
| 30 |
-// volumeToAPIType converts a volume.Volume to the type used by the Engine API |
|
| 31 |
-func volumeToAPIType(v volume.Volume) *types.Volume {
|
|
| 32 |
- createdAt, _ := v.CreatedAt() |
|
| 33 |
- tv := &types.Volume{
|
|
| 34 |
- Name: v.Name(), |
|
| 35 |
- Driver: v.DriverName(), |
|
| 36 |
- CreatedAt: createdAt.Format(time.RFC3339), |
|
| 37 |
- } |
|
| 38 |
- if v, ok := v.(volume.DetailedVolume); ok {
|
|
| 39 |
- tv.Labels = v.Labels() |
|
| 40 |
- tv.Options = v.Options() |
|
| 41 |
- tv.Scope = v.Scope() |
|
| 42 |
- } |
|
| 43 |
- |
|
| 44 |
- return tv |
|
| 45 |
-} |
|
| 46 |
- |
|
| 47 | 30 |
// Len returns the number of mounts. Used in sorting. |
| 48 | 31 |
func (m mounts) Len() int {
|
| 49 | 32 |
return len(m) |
| ... | ... |
@@ -78,6 +64,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 78 | 78 |
mountPoints := map[string]*volumemounts.MountPoint{}
|
| 79 | 79 |
parser := volumemounts.NewParser(container.OS) |
| 80 | 80 |
|
| 81 |
+ ctx := context.TODO() |
|
| 81 | 82 |
defer func() {
|
| 82 | 83 |
// clean up the container mountpoints once return with error |
| 83 | 84 |
if retErr != nil {
|
| ... | ... |
@@ -85,7 +72,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 85 | 85 |
if m.Volume == nil {
|
| 86 | 86 |
continue |
| 87 | 87 |
} |
| 88 |
- daemon.volumes.Dereference(m.Volume, container.ID) |
|
| 88 |
+ daemon.volumes.Release(ctx, m.Volume.Name(), container.ID) |
|
| 89 | 89 |
} |
| 90 | 90 |
} |
| 91 | 91 |
}() |
| ... | ... |
@@ -94,7 +81,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 94 | 94 |
if v, ok := mountPoints[destination]; ok {
|
| 95 | 95 |
logrus.Debugf("Duplicate mount point '%s'", destination)
|
| 96 | 96 |
if v.Volume != nil {
|
| 97 |
- daemon.volumes.Dereference(v.Volume, container.ID) |
|
| 97 |
+ daemon.volumes.Release(ctx, v.Volume.Name(), container.ID) |
|
| 98 | 98 |
} |
| 99 | 99 |
} |
| 100 | 100 |
} |
| ... | ... |
@@ -130,11 +117,11 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 | 132 |
if len(cp.Source) == 0 {
|
| 133 |
- v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID) |
|
| 133 |
+ v, err := daemon.volumes.Get(ctx, cp.Name, volumeopts.WithGetDriver(cp.Driver), volumeopts.WithGetReference(container.ID)) |
|
| 134 | 134 |
if err != nil {
|
| 135 | 135 |
return err |
| 136 | 136 |
} |
| 137 |
- cp.Volume = v |
|
| 137 |
+ cp.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
|
| 138 | 138 |
} |
| 139 | 139 |
dereferenceIfExists(cp.Destination) |
| 140 | 140 |
mountPoints[cp.Destination] = cp |
| ... | ... |
@@ -163,14 +150,14 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 163 | 163 |
|
| 164 | 164 |
if bind.Type == mounttypes.TypeVolume {
|
| 165 | 165 |
// create the volume |
| 166 |
- v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil) |
|
| 166 |
+ v, err := daemon.volumes.Create(ctx, bind.Name, bind.Driver, volumeopts.WithCreateReference(container.ID)) |
|
| 167 | 167 |
if err != nil {
|
| 168 | 168 |
return err |
| 169 | 169 |
} |
| 170 |
- bind.Volume = v |
|
| 171 |
- bind.Source = v.Path() |
|
| 170 |
+ bind.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
|
| 171 |
+ bind.Source = v.Mountpoint |
|
| 172 | 172 |
// bind.Name is an already existing volume, we need to use that here |
| 173 |
- bind.Driver = v.DriverName() |
|
| 173 |
+ bind.Driver = v.Driver |
|
| 174 | 174 |
if bind.Driver == volume.DefaultDriverName {
|
| 175 | 175 |
setBindModeIfNull(bind) |
| 176 | 176 |
} |
| ... | ... |
@@ -199,30 +186,30 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 199 | 199 |
} |
| 200 | 200 |
|
| 201 | 201 |
if mp.Type == mounttypes.TypeVolume {
|
| 202 |
- var v volume.Volume |
|
| 202 |
+ var v *types.Volume |
|
| 203 | 203 |
if cfg.VolumeOptions != nil {
|
| 204 | 204 |
var driverOpts map[string]string |
| 205 | 205 |
if cfg.VolumeOptions.DriverConfig != nil {
|
| 206 | 206 |
driverOpts = cfg.VolumeOptions.DriverConfig.Options |
| 207 | 207 |
} |
| 208 |
- v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, driverOpts, cfg.VolumeOptions.Labels) |
|
| 208 |
+ v, err = daemon.volumes.Create(ctx, |
|
| 209 |
+ mp.Name, |
|
| 210 |
+ mp.Driver, |
|
| 211 |
+ volumeopts.WithCreateReference(container.ID), |
|
| 212 |
+ volumeopts.WithCreateOptions(driverOpts), |
|
| 213 |
+ volumeopts.WithCreateLabels(cfg.VolumeOptions.Labels), |
|
| 214 |
+ ) |
|
| 209 | 215 |
} else {
|
| 210 |
- v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, nil, nil) |
|
| 216 |
+ v, err = daemon.volumes.Create(ctx, mp.Name, mp.Driver, volumeopts.WithCreateReference(container.ID)) |
|
| 211 | 217 |
} |
| 212 | 218 |
if err != nil {
|
| 213 | 219 |
return err |
| 214 | 220 |
} |
| 215 | 221 |
|
| 216 |
- mp.Volume = v |
|
| 217 |
- mp.Name = v.Name() |
|
| 218 |
- mp.Driver = v.DriverName() |
|
| 222 |
+ mp.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
|
| 223 |
+ mp.Name = v.Name |
|
| 224 |
+ mp.Driver = v.Driver |
|
| 219 | 225 |
|
| 220 |
- // only use the cached path here since getting the path is not necessary right now and calling `Path()` may be slow |
|
| 221 |
- if cv, ok := v.(interface {
|
|
| 222 |
- CachedPath() string |
|
| 223 |
- }); ok {
|
|
| 224 |
- mp.Source = cv.CachedPath() |
|
| 225 |
- } |
|
| 226 | 226 |
if mp.Driver == volume.DefaultDriverName {
|
| 227 | 227 |
setBindModeIfNull(mp) |
| 228 | 228 |
} |
| ... | ... |
@@ -239,7 +226,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 239 | 239 |
for _, m := range mountPoints {
|
| 240 | 240 |
if parser.IsBackwardCompatible(m) {
|
| 241 | 241 |
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
|
| 242 |
- daemon.volumes.Dereference(mp.Volume, container.ID) |
|
| 242 |
+ daemon.volumes.Release(ctx, mp.Volume.Name(), container.ID) |
|
| 243 | 243 |
} |
| 244 | 244 |
} |
| 245 | 245 |
} |
| ... | ... |
@@ -254,11 +241,11 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 254 | 254 |
// This happens after a daemon restart. |
| 255 | 255 |
func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
|
| 256 | 256 |
if len(m.Driver) > 0 && m.Volume == nil {
|
| 257 |
- v, err := daemon.volumes.GetWithRef(m.Name, m.Driver, containerID) |
|
| 257 |
+ v, err := daemon.volumes.Get(context.TODO(), m.Name, volumeopts.WithGetDriver(m.Driver), volumeopts.WithGetReference(containerID)) |
|
| 258 | 258 |
if err != nil {
|
| 259 | 259 |
return err |
| 260 | 260 |
} |
| 261 |
- m.Volume = v |
|
| 261 |
+ m.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
|
| 262 | 262 |
} |
| 263 | 263 |
return nil |
| 264 | 264 |
} |
| ... | ... |
@@ -385,3 +372,46 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
|
| 385 | 385 |
cm.Spec.ReadOnly = !cm.RW |
| 386 | 386 |
} |
| 387 | 387 |
} |
| 388 |
+ |
|
| 389 |
+// VolumesService is used to perform volume operations |
|
| 390 |
+func (daemon *Daemon) VolumesService() *service.VolumesService {
|
|
| 391 |
+ return daemon.volumes |
|
| 392 |
+} |
|
| 393 |
+ |
|
| 394 |
+type volumeMounter interface {
|
|
| 395 |
+ Mount(ctx context.Context, v *types.Volume, ref string) (string, error) |
|
| 396 |
+ Unmount(ctx context.Context, v *types.Volume, ref string) error |
|
| 397 |
+} |
|
| 398 |
+ |
|
| 399 |
+type volumeWrapper struct {
|
|
| 400 |
+ v *types.Volume |
|
| 401 |
+ s volumeMounter |
|
| 402 |
+} |
|
| 403 |
+ |
|
| 404 |
+func (v *volumeWrapper) Name() string {
|
|
| 405 |
+ return v.v.Name |
|
| 406 |
+} |
|
| 407 |
+ |
|
| 408 |
+func (v *volumeWrapper) DriverName() string {
|
|
| 409 |
+ return v.v.Driver |
|
| 410 |
+} |
|
| 411 |
+ |
|
| 412 |
+func (v *volumeWrapper) Path() string {
|
|
| 413 |
+ return v.v.Mountpoint |
|
| 414 |
+} |
|
| 415 |
+ |
|
| 416 |
+func (v *volumeWrapper) Mount(ref string) (string, error) {
|
|
| 417 |
+ return v.s.Mount(context.TODO(), v.v, ref) |
|
| 418 |
+} |
|
| 419 |
+ |
|
| 420 |
+func (v *volumeWrapper) Unmount(ref string) error {
|
|
| 421 |
+ return v.s.Unmount(context.TODO(), v.v, ref) |
|
| 422 |
+} |
|
| 423 |
+ |
|
| 424 |
+func (v *volumeWrapper) CreatedAt() (time.Time, error) {
|
|
| 425 |
+ return time.Time{}, errors.New("not implemented")
|
|
| 426 |
+} |
|
| 427 |
+ |
|
| 428 |
+func (v *volumeWrapper) Status() map[string]interface{} {
|
|
| 429 |
+ return v.v.Status |
|
| 430 |
+} |
| ... | ... |
@@ -185,11 +185,12 @@ func (s *DockerSuite) TestVolumeEvents(c *check.C) {
|
| 185 | 185 |
c.Assert(len(events), checker.GreaterThan, 4) |
| 186 | 186 |
|
| 187 | 187 |
volumeEvents := eventActionsByIDAndType(c, events, "test-event-volume-local", "volume") |
| 188 |
- c.Assert(volumeEvents, checker.HasLen, 4) |
|
| 188 |
+ c.Assert(volumeEvents, checker.HasLen, 5) |
|
| 189 | 189 |
c.Assert(volumeEvents[0], checker.Equals, "create") |
| 190 |
- c.Assert(volumeEvents[1], checker.Equals, "mount") |
|
| 191 |
- c.Assert(volumeEvents[2], checker.Equals, "unmount") |
|
| 192 |
- c.Assert(volumeEvents[3], checker.Equals, "destroy") |
|
| 190 |
+ c.Assert(volumeEvents[1], checker.Equals, "create") |
|
| 191 |
+ c.Assert(volumeEvents[2], checker.Equals, "mount") |
|
| 192 |
+ c.Assert(volumeEvents[3], checker.Equals, "unmount") |
|
| 193 |
+ c.Assert(volumeEvents[4], checker.Equals, "destroy") |
|
| 193 | 194 |
} |
| 194 | 195 |
|
| 195 | 196 |
func (s *DockerSuite) TestNetworkEvents(c *check.C) {
|
| ... | ... |
@@ -517,22 +517,20 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c * |
| 517 | 517 |
} |
| 518 | 518 |
|
| 519 | 519 |
// Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path` |
| 520 |
+// |
|
| 521 |
+// TODO(@cpuguy83): This test is testing internal implementation. In all the cases here, there may not even be a path |
|
| 522 |
+// available because the volume is not even mounted. Consider removing this test. |
|
| 520 | 523 |
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
|
| 521 | 524 |
s.d.Start(c) |
| 522 | 525 |
c.Assert(s.ec.paths, checker.Equals, 0) |
| 523 | 526 |
|
| 524 | 527 |
out, err := s.d.Cmd("volume", "create", "test", "--driver=test-external-volume-driver")
|
| 525 | 528 |
c.Assert(err, checker.IsNil, check.Commentf(out)) |
| 526 |
- c.Assert(s.ec.paths, checker.Equals, 1) |
|
| 529 |
+ c.Assert(s.ec.paths, checker.Equals, 0) |
|
| 527 | 530 |
|
| 528 | 531 |
out, err = s.d.Cmd("volume", "ls")
|
| 529 | 532 |
c.Assert(err, checker.IsNil, check.Commentf(out)) |
| 530 |
- c.Assert(s.ec.paths, checker.Equals, 1) |
|
| 531 |
- |
|
| 532 |
- out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test")
|
|
| 533 |
- c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
| 534 |
- c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") |
|
| 535 |
- c.Assert(s.ec.paths, checker.Equals, 1) |
|
| 533 |
+ c.Assert(s.ec.paths, checker.Equals, 0) |
|
| 536 | 534 |
} |
| 537 | 535 |
|
| 538 | 536 |
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) {
|
| ... | ... |
@@ -94,7 +94,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
|
| 94 | 94 |
if err != nil {
|
| 95 | 95 |
// `GetCapabilities` is a not a required endpoint. |
| 96 | 96 |
// On error assume it's a local-only driver |
| 97 |
- logrus.Warnf("Volume driver %s returned an error while trying to query its capabilities, using default capabilities: %v", a.name, err)
|
|
| 97 |
+ logrus.WithError(err).WithField("driver", a.name).Debug("Volume driver returned an error while trying to query its capabilities, using default capabilities")
|
|
| 98 | 98 |
return volume.Capability{Scope: volume.LocalScope}
|
| 99 | 99 |
} |
| 100 | 100 |
|
| ... | ... |
@@ -105,7 +105,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
|
| 105 | 105 |
|
| 106 | 106 |
cap.Scope = strings.ToLower(cap.Scope) |
| 107 | 107 |
if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope {
|
| 108 |
- logrus.Warnf("Volume driver %q returned an invalid scope: %q", a.Name(), cap.Scope)
|
|
| 108 |
+ logrus.WithField("driver", a.Name()).WithField("scope", a.Scope).Warn("Volume driver returned an invalid scope")
|
|
| 109 | 109 |
cap.Scope = volume.LocalScope |
| 110 | 110 |
} |
| 111 | 111 |
|
| ... | ... |
@@ -167,10 +167,10 @@ func (s *Store) ReleaseDriver(name string) (volume.Driver, error) {
|
| 167 | 167 |
func (s *Store) GetDriverList() []string {
|
| 168 | 168 |
var driverList []string |
| 169 | 169 |
s.mu.Lock() |
| 170 |
+ defer s.mu.Unlock() |
|
| 170 | 171 |
for driverName := range s.extensions {
|
| 171 | 172 |
driverList = append(driverList, driverName) |
| 172 | 173 |
} |
| 173 |
- s.mu.Unlock() |
|
| 174 | 174 |
sort.Strings(driverList) |
| 175 | 175 |
return driverList |
| 176 | 176 |
} |
| 177 | 177 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,89 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/types/filters" |
|
| 4 |
+ "github.com/docker/docker/volume" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// By is an interface which is used to implement filtering on volumes. |
|
| 8 |
+type By interface {
|
|
| 9 |
+ isBy() |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+// ByDriver is `By` that filters based on the driver names that are passed in |
|
| 13 |
+func ByDriver(drivers ...string) By {
|
|
| 14 |
+ return byDriver(drivers) |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+type byDriver []string |
|
| 18 |
+ |
|
| 19 |
+func (byDriver) isBy() {}
|
|
| 20 |
+ |
|
| 21 |
+// ByReferenced is a `By` that filters based on if the volume has references |
|
| 22 |
+type ByReferenced bool |
|
| 23 |
+ |
|
| 24 |
+func (ByReferenced) isBy() {}
|
|
| 25 |
+ |
|
| 26 |
+// And creates a `By` combining all the passed in bys using AND logic. |
|
| 27 |
+func And(bys ...By) By {
|
|
| 28 |
+ and := make(andCombinator, 0, len(bys)) |
|
| 29 |
+ for _, by := range bys {
|
|
| 30 |
+ and = append(and, by) |
|
| 31 |
+ } |
|
| 32 |
+ return and |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+type andCombinator []By |
|
| 36 |
+ |
|
| 37 |
+func (andCombinator) isBy() {}
|
|
| 38 |
+ |
|
| 39 |
+// Or creates a `By` combining all the passed in bys using OR logic. |
|
| 40 |
+func Or(bys ...By) By {
|
|
| 41 |
+ or := make(orCombinator, 0, len(bys)) |
|
| 42 |
+ for _, by := range bys {
|
|
| 43 |
+ or = append(or, by) |
|
| 44 |
+ } |
|
| 45 |
+ return or |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+type orCombinator []By |
|
| 49 |
+ |
|
| 50 |
+func (orCombinator) isBy() {}
|
|
| 51 |
+ |
|
| 52 |
+// CustomFilter is a `By` that is used by callers to provide custom filtering |
|
| 53 |
+// logic. |
|
| 54 |
+type CustomFilter filterFunc |
|
| 55 |
+ |
|
| 56 |
+func (CustomFilter) isBy() {}
|
|
| 57 |
+ |
|
| 58 |
+// FromList returns a By which sets the initial list of volumes to use |
|
| 59 |
+func FromList(ls *[]volume.Volume, by By) By {
|
|
| 60 |
+ return &fromList{by: by, ls: ls}
|
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+type fromList struct {
|
|
| 64 |
+ by By |
|
| 65 |
+ ls *[]volume.Volume |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+func (fromList) isBy() {}
|
|
| 69 |
+ |
|
| 70 |
+func byLabelFilter(filter filters.Args) By {
|
|
| 71 |
+ return CustomFilter(func(v volume.Volume) bool {
|
|
| 72 |
+ dv, ok := v.(volume.DetailedVolume) |
|
| 73 |
+ if !ok {
|
|
| 74 |
+ return false |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ labels := dv.Labels() |
|
| 78 |
+ if !filter.MatchKVList("label", labels) {
|
|
| 79 |
+ return false |
|
| 80 |
+ } |
|
| 81 |
+ if filter.Contains("label!") {
|
|
| 82 |
+ if filter.MatchKVList("label!", labels) {
|
|
| 83 |
+ return false |
|
| 84 |
+ } |
|
| 85 |
+ } |
|
| 86 |
+ return true |
|
| 87 |
+ }) |
|
| 88 |
+} |
| 0 | 89 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,132 @@ |
| 0 |
+package service |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "time" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 |
+ "github.com/docker/docker/api/types/filters" |
|
| 8 |
+ "github.com/docker/docker/pkg/directory" |
|
| 9 |
+ "github.com/docker/docker/volume" |
|
| 10 |
+ "github.com/sirupsen/logrus" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// convertOpts are used to pass options to `volumeToAPI` |
|
| 14 |
+type convertOpt interface {
|
|
| 15 |
+ isConvertOpt() |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+type useCachedPath bool |
|
| 19 |
+ |
|
| 20 |
+func (useCachedPath) isConvertOpt() {}
|
|
| 21 |
+ |
|
| 22 |
+type calcSize bool |
|
| 23 |
+ |
|
| 24 |
+func (calcSize) isConvertOpt() {}
|
|
| 25 |
+ |
|
| 26 |
+type pathCacher interface {
|
|
| 27 |
+ CachedPath() string |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+func (s *VolumesService) volumesToAPI(ctx context.Context, volumes []volume.Volume, opts ...convertOpt) []*types.Volume {
|
|
| 31 |
+ var ( |
|
| 32 |
+ out = make([]*types.Volume, 0, len(volumes)) |
|
| 33 |
+ getSize bool |
|
| 34 |
+ cachedPath bool |
|
| 35 |
+ ) |
|
| 36 |
+ |
|
| 37 |
+ for _, o := range opts {
|
|
| 38 |
+ switch t := o.(type) {
|
|
| 39 |
+ case calcSize: |
|
| 40 |
+ getSize = bool(t) |
|
| 41 |
+ case useCachedPath: |
|
| 42 |
+ cachedPath = bool(t) |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ for _, v := range volumes {
|
|
| 46 |
+ select {
|
|
| 47 |
+ case <-ctx.Done(): |
|
| 48 |
+ return nil |
|
| 49 |
+ default: |
|
| 50 |
+ } |
|
| 51 |
+ apiV := volumeToAPIType(v) |
|
| 52 |
+ |
|
| 53 |
+ if cachedPath {
|
|
| 54 |
+ if vv, ok := v.(pathCacher); ok {
|
|
| 55 |
+ apiV.Mountpoint = vv.CachedPath() |
|
| 56 |
+ } |
|
| 57 |
+ } else {
|
|
| 58 |
+ apiV.Mountpoint = v.Path() |
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ if getSize {
|
|
| 62 |
+ p := v.Path() |
|
| 63 |
+ if apiV.Mountpoint == "" {
|
|
| 64 |
+ apiV.Mountpoint = p |
|
| 65 |
+ } |
|
| 66 |
+ sz, err := directory.Size(ctx, p) |
|
| 67 |
+ if err != nil {
|
|
| 68 |
+ logrus.WithError(err).WithField("volume", v.Name()).Warnf("Failed to determine size of volume")
|
|
| 69 |
+ sz = -1 |
|
| 70 |
+ } |
|
| 71 |
+ apiV.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(s.vs.CountReferences(v))}
|
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ out = append(out, &apiV) |
|
| 75 |
+ } |
|
| 76 |
+ return out |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func volumeToAPIType(v volume.Volume) types.Volume {
|
|
| 80 |
+ createdAt, _ := v.CreatedAt() |
|
| 81 |
+ tv := types.Volume{
|
|
| 82 |
+ Name: v.Name(), |
|
| 83 |
+ Driver: v.DriverName(), |
|
| 84 |
+ CreatedAt: createdAt.Format(time.RFC3339), |
|
| 85 |
+ } |
|
| 86 |
+ if v, ok := v.(volume.DetailedVolume); ok {
|
|
| 87 |
+ tv.Labels = v.Labels() |
|
| 88 |
+ tv.Options = v.Options() |
|
| 89 |
+ tv.Scope = v.Scope() |
|
| 90 |
+ } |
|
| 91 |
+ if cp, ok := v.(pathCacher); ok {
|
|
| 92 |
+ tv.Mountpoint = cp.CachedPath() |
|
| 93 |
+ } |
|
| 94 |
+ return tv |
|
| 95 |
+} |
|
| 96 |
+ |
|
| 97 |
+func filtersToBy(filter filters.Args, acceptedFilters map[string]bool) (By, error) {
|
|
| 98 |
+ if err := filter.Validate(acceptedFilters); err != nil {
|
|
| 99 |
+ return nil, err |
|
| 100 |
+ } |
|
| 101 |
+ var bys []By |
|
| 102 |
+ if drivers := filter.Get("driver"); len(drivers) > 0 {
|
|
| 103 |
+ bys = append(bys, ByDriver(drivers...)) |
|
| 104 |
+ } |
|
| 105 |
+ if filter.Contains("name") {
|
|
| 106 |
+ bys = append(bys, CustomFilter(func(v volume.Volume) bool {
|
|
| 107 |
+ return filter.Match("name", v.Name())
|
|
| 108 |
+ })) |
|
| 109 |
+ } |
|
| 110 |
+ bys = append(bys, byLabelFilter(filter)) |
|
| 111 |
+ |
|
| 112 |
+ if filter.Contains("dangling") {
|
|
| 113 |
+ var dangling bool |
|
| 114 |
+ if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
|
|
| 115 |
+ dangling = true |
|
| 116 |
+ } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
|
|
| 117 |
+ return nil, invalidFilter{"dangling", filter.Get("dangling")}
|
|
| 118 |
+ } |
|
| 119 |
+ bys = append(bys, ByReferenced(!dangling)) |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ var by By |
|
| 123 |
+ switch len(bys) {
|
|
| 124 |
+ case 0: |
|
| 125 |
+ case 1: |
|
| 126 |
+ by = bys[0] |
|
| 127 |
+ default: |
|
| 128 |
+ by = And(bys...) |
|
| 129 |
+ } |
|
| 130 |
+ return by, nil |
|
| 131 |
+} |
| 0 | 132 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,95 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/boltdb/bolt" |
|
| 6 |
+ "github.com/docker/docker/errdefs" |
|
| 7 |
+ "github.com/pkg/errors" |
|
| 8 |
+ "github.com/sirupsen/logrus" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+var volumeBucketName = []byte("volumes")
|
|
| 12 |
+ |
|
| 13 |
+type volumeMetadata struct {
|
|
| 14 |
+ Name string |
|
| 15 |
+ Driver string |
|
| 16 |
+ Labels map[string]string |
|
| 17 |
+ Options map[string]string |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func (s *VolumeStore) setMeta(name string, meta volumeMetadata) error {
|
|
| 21 |
+ return s.db.Update(func(tx *bolt.Tx) error {
|
|
| 22 |
+ return setMeta(tx, name, meta) |
|
| 23 |
+ }) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func setMeta(tx *bolt.Tx, name string, meta volumeMetadata) error {
|
|
| 27 |
+ metaJSON, err := json.Marshal(meta) |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ return err |
|
| 30 |
+ } |
|
| 31 |
+ b, err := tx.CreateBucketIfNotExists(volumeBucketName) |
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ return errors.Wrap(err, "error creating volume bucket") |
|
| 34 |
+ } |
|
| 35 |
+ return errors.Wrap(b.Put([]byte(name), metaJSON), "error setting volume metadata") |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+func (s *VolumeStore) getMeta(name string) (volumeMetadata, error) {
|
|
| 39 |
+ var meta volumeMetadata |
|
| 40 |
+ err := s.db.View(func(tx *bolt.Tx) error {
|
|
| 41 |
+ return getMeta(tx, name, &meta) |
|
| 42 |
+ }) |
|
| 43 |
+ return meta, err |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+func getMeta(tx *bolt.Tx, name string, meta *volumeMetadata) error {
|
|
| 47 |
+ b := tx.Bucket(volumeBucketName) |
|
| 48 |
+ if b == nil {
|
|
| 49 |
+ return errdefs.NotFound(errors.New("volume bucket does not exist"))
|
|
| 50 |
+ } |
|
| 51 |
+ val := b.Get([]byte(name)) |
|
| 52 |
+ if len(val) == 0 {
|
|
| 53 |
+ return nil |
|
| 54 |
+ } |
|
| 55 |
+ if err := json.Unmarshal(val, meta); err != nil {
|
|
| 56 |
+ return errors.Wrap(err, "error unmarshaling volume metadata") |
|
| 57 |
+ } |
|
| 58 |
+ return nil |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func (s *VolumeStore) removeMeta(name string) error {
|
|
| 62 |
+ return s.db.Update(func(tx *bolt.Tx) error {
|
|
| 63 |
+ return removeMeta(tx, name) |
|
| 64 |
+ }) |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+func removeMeta(tx *bolt.Tx, name string) error {
|
|
| 68 |
+ b := tx.Bucket(volumeBucketName) |
|
| 69 |
+ return errors.Wrap(b.Delete([]byte(name)), "error removing volume metadata") |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+// listMeta is used during restore to get the list of volume metadata |
|
| 73 |
+// from the on-disk database. |
|
| 74 |
+// Any errors that occur are only logged. |
|
| 75 |
+func listMeta(tx *bolt.Tx) []volumeMetadata {
|
|
| 76 |
+ var ls []volumeMetadata |
|
| 77 |
+ b := tx.Bucket(volumeBucketName) |
|
| 78 |
+ b.ForEach(func(k, v []byte) error {
|
|
| 79 |
+ if len(v) == 0 {
|
|
| 80 |
+ // don't try to unmarshal an empty value |
|
| 81 |
+ return nil |
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 84 |
+ var m volumeMetadata |
|
| 85 |
+ if err := json.Unmarshal(v, &m); err != nil {
|
|
| 86 |
+ // Just log the error |
|
| 87 |
+ logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(k), err)
|
|
| 88 |
+ return nil |
|
| 89 |
+ } |
|
| 90 |
+ ls = append(ls, m) |
|
| 91 |
+ return nil |
|
| 92 |
+ }) |
|
| 93 |
+ return ls |
|
| 94 |
+} |
| 0 | 95 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,52 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "testing" |
|
| 7 |
+ "time" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/boltdb/bolt" |
|
| 10 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 11 |
+ is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func TestSetGetMeta(t *testing.T) {
|
|
| 15 |
+ t.Parallel() |
|
| 16 |
+ |
|
| 17 |
+ dir, err := ioutil.TempDir("", "test-set-get")
|
|
| 18 |
+ assert.NilError(t, err) |
|
| 19 |
+ defer os.RemoveAll(dir) |
|
| 20 |
+ |
|
| 21 |
+ db, err := bolt.Open(filepath.Join(dir, "db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
| 22 |
+ assert.NilError(t, err) |
|
| 23 |
+ |
|
| 24 |
+ store := &VolumeStore{db: db}
|
|
| 25 |
+ |
|
| 26 |
+ _, err = store.getMeta("test")
|
|
| 27 |
+ assert.Assert(t, is.ErrorContains(err, "")) |
|
| 28 |
+ |
|
| 29 |
+ err = db.Update(func(tx *bolt.Tx) error {
|
|
| 30 |
+ _, err := tx.CreateBucket(volumeBucketName) |
|
| 31 |
+ return err |
|
| 32 |
+ }) |
|
| 33 |
+ assert.NilError(t, err) |
|
| 34 |
+ |
|
| 35 |
+ meta, err := store.getMeta("test")
|
|
| 36 |
+ assert.NilError(t, err) |
|
| 37 |
+ assert.DeepEqual(t, volumeMetadata{}, meta)
|
|
| 38 |
+ |
|
| 39 |
+ testMeta := volumeMetadata{
|
|
| 40 |
+ Name: "test", |
|
| 41 |
+ Driver: "fake", |
|
| 42 |
+ Labels: map[string]string{"a": "1", "b": "2"},
|
|
| 43 |
+ Options: map[string]string{"foo": "bar"},
|
|
| 44 |
+ } |
|
| 45 |
+ err = store.setMeta("test", testMeta)
|
|
| 46 |
+ assert.NilError(t, err) |
|
| 47 |
+ |
|
| 48 |
+ meta, err = store.getMeta("test")
|
|
| 49 |
+ assert.NilError(t, err) |
|
| 50 |
+ assert.DeepEqual(t, testMeta, meta) |
|
| 51 |
+} |
| 0 | 52 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,21 @@ |
| 0 |
+// +build linux windows |
|
| 1 |
+ |
|
| 2 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 3 |
+import ( |
|
| 4 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 5 |
+ "github.com/docker/docker/volume" |
|
| 6 |
+ "github.com/docker/docker/volume/drivers" |
|
| 7 |
+ "github.com/docker/docker/volume/local" |
|
| 8 |
+ "github.com/pkg/errors" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func setupDefaultDriver(store *drivers.Store, root string, rootIDs idtools.IDPair) error {
|
|
| 12 |
+ d, err := local.New(root, rootIDs) |
|
| 13 |
+ if err != nil {
|
|
| 14 |
+ return errors.Wrap(err, "error setting up default driver") |
|
| 15 |
+ } |
|
| 16 |
+ if !store.Register(d, volume.DefaultDriverName) {
|
|
| 17 |
+ return errors.New("local volume driver could not be registered")
|
|
| 18 |
+ } |
|
| 19 |
+ return nil |
|
| 20 |
+} |
| 0 | 21 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+// +build !linux,!windows |
|
| 1 |
+ |
|
| 2 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 6 |
+ "github.com/docker/docker/volume/drivers" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func setupDefaultDriver(_ *drivers.Store, _ string, _ idtools.IDPair) error { return nil }
|
| 0 | 10 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,111 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+const ( |
|
| 8 |
+ // errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container |
|
| 9 |
+ errVolumeInUse conflictError = "volume is in use" |
|
| 10 |
+ // errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store |
|
| 11 |
+ errNoSuchVolume notFoundError = "no such volume" |
|
| 12 |
+ // errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver |
|
| 13 |
+ errNameConflict conflictError = "volume name must be unique" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+type conflictError string |
|
| 17 |
+ |
|
| 18 |
+func (e conflictError) Error() string {
|
|
| 19 |
+ return string(e) |
|
| 20 |
+} |
|
| 21 |
+func (conflictError) Conflict() {}
|
|
| 22 |
+ |
|
| 23 |
+type notFoundError string |
|
| 24 |
+ |
|
| 25 |
+func (e notFoundError) Error() string {
|
|
| 26 |
+ return string(e) |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (notFoundError) NotFound() {}
|
|
| 30 |
+ |
|
| 31 |
+// OpErr is the error type returned by functions in the store package. It describes |
|
| 32 |
+// the operation, volume name, and error. |
|
| 33 |
+type OpErr struct {
|
|
| 34 |
+ // Err is the error that occurred during the operation. |
|
| 35 |
+ Err error |
|
| 36 |
+ // Op is the operation which caused the error, such as "create", or "list". |
|
| 37 |
+ Op string |
|
| 38 |
+ // Name is the name of the resource being requested for this op, typically the volume name or the driver name. |
|
| 39 |
+ Name string |
|
| 40 |
+ // Refs is the list of references associated with the resource. |
|
| 41 |
+ Refs []string |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+// Error satisfies the built-in error interface type. |
|
| 45 |
+func (e *OpErr) Error() string {
|
|
| 46 |
+ if e == nil {
|
|
| 47 |
+ return "<nil>" |
|
| 48 |
+ } |
|
| 49 |
+ s := e.Op |
|
| 50 |
+ if e.Name != "" {
|
|
| 51 |
+ s = s + " " + e.Name |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ s = s + ": " + e.Err.Error() |
|
| 55 |
+ if len(e.Refs) > 0 {
|
|
| 56 |
+ s = s + " - " + "[" + strings.Join(e.Refs, ", ") + "]" |
|
| 57 |
+ } |
|
| 58 |
+ return s |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+// Cause returns the error the caused this error |
|
| 62 |
+func (e *OpErr) Cause() error {
|
|
| 63 |
+ return e.Err |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+// IsInUse returns a boolean indicating whether the error indicates that a |
|
| 67 |
+// volume is in use |
|
| 68 |
+func IsInUse(err error) bool {
|
|
| 69 |
+ return isErr(err, errVolumeInUse) |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+// IsNotExist returns a boolean indicating whether the error indicates that the volume does not exist |
|
| 73 |
+func IsNotExist(err error) bool {
|
|
| 74 |
+ return isErr(err, errNoSuchVolume) |
|
| 75 |
+} |
|
| 76 |
+ |
|
| 77 |
+// IsNameConflict returns a boolean indicating whether the error indicates that a |
|
| 78 |
+// volume name is already taken |
|
| 79 |
+func IsNameConflict(err error) bool {
|
|
| 80 |
+ return isErr(err, errNameConflict) |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+type causal interface {
|
|
| 84 |
+ Cause() error |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func isErr(err error, expected error) bool {
|
|
| 88 |
+ switch pe := err.(type) {
|
|
| 89 |
+ case nil: |
|
| 90 |
+ return false |
|
| 91 |
+ case causal: |
|
| 92 |
+ return isErr(pe.Cause(), expected) |
|
| 93 |
+ } |
|
| 94 |
+ return err == expected |
|
| 95 |
+} |
|
| 96 |
+ |
|
| 97 |
+type invalidFilter struct {
|
|
| 98 |
+ filter string |
|
| 99 |
+ value interface{}
|
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func (e invalidFilter) Error() string {
|
|
| 103 |
+ msg := "Invalid filter '" + e.filter |
|
| 104 |
+ if e.value != nil {
|
|
| 105 |
+ msg += fmt.Sprintf("=%s", e.value)
|
|
| 106 |
+ } |
|
| 107 |
+ return msg + "'" |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func (e invalidFilter) InvalidParameter() {}
|
| 0 | 111 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,89 @@ |
| 0 |
+package opts |
|
| 1 |
+ |
|
| 2 |
+// CreateOption is used to pass options in when creating a volume |
|
| 3 |
+type CreateOption func(*CreateConfig) |
|
| 4 |
+ |
|
| 5 |
+// CreateConfig is the set of config options that can be set when creating |
|
| 6 |
+// a volume |
|
| 7 |
+type CreateConfig struct {
|
|
| 8 |
+ Options map[string]string |
|
| 9 |
+ Labels map[string]string |
|
| 10 |
+ Reference string |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+// WithCreateLabels creates a CreateOption which sets the labels to the |
|
| 14 |
+// passed in value |
|
| 15 |
+func WithCreateLabels(labels map[string]string) CreateOption {
|
|
| 16 |
+ return func(cfg *CreateConfig) {
|
|
| 17 |
+ cfg.Labels = labels |
|
| 18 |
+ } |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// WithCreateOptions creates a CreateOption which sets the options passed |
|
| 22 |
+// to the volume driver when creating a volume to the options passed in. |
|
| 23 |
+func WithCreateOptions(opts map[string]string) CreateOption {
|
|
| 24 |
+ return func(cfg *CreateConfig) {
|
|
| 25 |
+ cfg.Options = opts |
|
| 26 |
+ } |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// WithCreateReference creats a CreateOption which sets a reference to use |
|
| 30 |
+// when creating a volume. This ensures that the volume is created with a reference |
|
| 31 |
+// already attached to it to prevent race conditions with Create and volume cleanup. |
|
| 32 |
+func WithCreateReference(ref string) CreateOption {
|
|
| 33 |
+ return func(cfg *CreateConfig) {
|
|
| 34 |
+ cfg.Reference = ref |
|
| 35 |
+ } |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// GetConfig is used with `GetOption` to set options for the volumes service's |
|
| 39 |
+// `Get` implementation. |
|
| 40 |
+type GetConfig struct {
|
|
| 41 |
+ Driver string |
|
| 42 |
+ Reference string |
|
| 43 |
+ ResolveStatus bool |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// GetOption is passed to the service `Get` add extra details on the get request |
|
| 47 |
+type GetOption func(*GetConfig) |
|
| 48 |
+ |
|
| 49 |
+// WithGetDriver provides the driver to get the volume from |
|
| 50 |
+// If no driver is provided to `Get`, first the available metadata is checked |
|
| 51 |
+// to see which driver it belongs to, if that is not available all drivers are |
|
| 52 |
+// probed to find the volume. |
|
| 53 |
+func WithGetDriver(name string) GetOption {
|
|
| 54 |
+ return func(o *GetConfig) {
|
|
| 55 |
+ o.Driver = name |
|
| 56 |
+ } |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// WithGetReference indicates to `Get` to increment the reference count for the |
|
| 60 |
+// retreived volume with the provided reference ID. |
|
| 61 |
+func WithGetReference(ref string) GetOption {
|
|
| 62 |
+ return func(o *GetConfig) {
|
|
| 63 |
+ o.Reference = ref |
|
| 64 |
+ } |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+// WithGetResolveStatus indicates to `Get` to also fetch the volume status. |
|
| 68 |
+// This can cause significant overhead in the volume lookup. |
|
| 69 |
+func WithGetResolveStatus(cfg *GetConfig) {
|
|
| 70 |
+ cfg.ResolveStatus = true |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+// RemoveConfig is used by `RemoveOption` to store config options for remove |
|
| 74 |
+type RemoveConfig struct {
|
|
| 75 |
+ PurgeOnError bool |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+// RemoveOption is used to pass options to the volumes service `Remove` implementation |
|
| 79 |
+type RemoveOption func(*RemoveConfig) |
|
| 80 |
+ |
|
| 81 |
+// WithPurgeOnError is an option passed to `Remove` which will purge all cached |
|
| 82 |
+// data about a volume even if there was an error while attempting to remove the |
|
| 83 |
+// volume. |
|
| 84 |
+func WithPurgeOnError(b bool) RemoveOption {
|
|
| 85 |
+ return func(o *RemoveConfig) {
|
|
| 86 |
+ o.PurgeOnError = b |
|
| 87 |
+ } |
|
| 88 |
+} |
| 0 | 89 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,85 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "sync" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/boltdb/bolt" |
|
| 7 |
+ "github.com/docker/docker/volume" |
|
| 8 |
+ "github.com/sirupsen/logrus" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// restore is called when a new volume store is created. |
|
| 12 |
+// It's primary purpose is to ensure that all drivers' refcounts are set based |
|
| 13 |
+// on known volumes after a restart. |
|
| 14 |
+// This only attempts to track volumes that are actually stored in the on-disk db. |
|
| 15 |
+// It does not probe the available drivers to find anything that may have been added |
|
| 16 |
+// out of band. |
|
| 17 |
+func (s *VolumeStore) restore() {
|
|
| 18 |
+ var ls []volumeMetadata |
|
| 19 |
+ s.db.View(func(tx *bolt.Tx) error {
|
|
| 20 |
+ ls = listMeta(tx) |
|
| 21 |
+ return nil |
|
| 22 |
+ }) |
|
| 23 |
+ ctx := context.Background() |
|
| 24 |
+ |
|
| 25 |
+ chRemove := make(chan *volumeMetadata, len(ls)) |
|
| 26 |
+ var wg sync.WaitGroup |
|
| 27 |
+ for _, meta := range ls {
|
|
| 28 |
+ wg.Add(1) |
|
| 29 |
+ // this is potentially a very slow operation, so do it in a goroutine |
|
| 30 |
+ go func(meta volumeMetadata) {
|
|
| 31 |
+ defer wg.Done() |
|
| 32 |
+ |
|
| 33 |
+ var v volume.Volume |
|
| 34 |
+ var err error |
|
| 35 |
+ if meta.Driver != "" {
|
|
| 36 |
+ v, err = lookupVolume(ctx, s.drivers, meta.Driver, meta.Name) |
|
| 37 |
+ if err != nil && err != errNoSuchVolume {
|
|
| 38 |
+ logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
|
|
| 39 |
+ return |
|
| 40 |
+ } |
|
| 41 |
+ if v == nil {
|
|
| 42 |
+ // doesn't exist in the driver, remove it from the db |
|
| 43 |
+ chRemove <- &meta |
|
| 44 |
+ return |
|
| 45 |
+ } |
|
| 46 |
+ } else {
|
|
| 47 |
+ v, err = s.getVolume(ctx, meta.Name, meta.Driver) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ if err == errNoSuchVolume {
|
|
| 50 |
+ chRemove <- &meta |
|
| 51 |
+ } |
|
| 52 |
+ return |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ meta.Driver = v.DriverName() |
|
| 56 |
+ if err := s.setMeta(v.Name(), meta); err != nil {
|
|
| 57 |
+ logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", v.Name()).Warn("Error updating volume metadata on restore")
|
|
| 58 |
+ } |
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ // increment driver refcount |
|
| 62 |
+ s.drivers.CreateDriver(meta.Driver) |
|
| 63 |
+ |
|
| 64 |
+ // cache the volume |
|
| 65 |
+ s.globalLock.Lock() |
|
| 66 |
+ s.options[v.Name()] = meta.Options |
|
| 67 |
+ s.labels[v.Name()] = meta.Labels |
|
| 68 |
+ s.names[v.Name()] = v |
|
| 69 |
+ s.refs[v.Name()] = make(map[string]struct{})
|
|
| 70 |
+ s.globalLock.Unlock() |
|
| 71 |
+ }(meta) |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ wg.Wait() |
|
| 75 |
+ close(chRemove) |
|
| 76 |
+ s.db.Update(func(tx *bolt.Tx) error {
|
|
| 77 |
+ for meta := range chRemove {
|
|
| 78 |
+ if err := removeMeta(tx, meta.Name); err != nil {
|
|
| 79 |
+ logrus.WithField("volume", meta.Name).Warnf("Error removing stale entry from volume db: %v", err)
|
|
| 80 |
+ } |
|
| 81 |
+ } |
|
| 82 |
+ return nil |
|
| 83 |
+ }) |
|
| 84 |
+} |
| 0 | 85 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,58 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/volume" |
|
| 9 |
+ volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 10 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 11 |
+ volumetestutils "github.com/docker/docker/volume/testutils" |
|
| 12 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func TestRestore(t *testing.T) {
|
|
| 16 |
+ t.Parallel() |
|
| 17 |
+ |
|
| 18 |
+ dir, err := ioutil.TempDir("", "test-restore")
|
|
| 19 |
+ assert.NilError(t, err) |
|
| 20 |
+ defer os.RemoveAll(dir) |
|
| 21 |
+ |
|
| 22 |
+ drivers := volumedrivers.NewStore(nil) |
|
| 23 |
+ driverName := "test-restore" |
|
| 24 |
+ drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 25 |
+ |
|
| 26 |
+ s, err := NewStore(dir, drivers) |
|
| 27 |
+ assert.NilError(t, err) |
|
| 28 |
+ defer s.Shutdown() |
|
| 29 |
+ |
|
| 30 |
+ ctx := context.Background() |
|
| 31 |
+ _, err = s.Create(ctx, "test1", driverName) |
|
| 32 |
+ assert.NilError(t, err) |
|
| 33 |
+ |
|
| 34 |
+ testLabels := map[string]string{"a": "1"}
|
|
| 35 |
+ testOpts := map[string]string{"foo": "bar"}
|
|
| 36 |
+ _, err = s.Create(ctx, "test2", driverName, opts.WithCreateOptions(testOpts), opts.WithCreateLabels(testLabels)) |
|
| 37 |
+ assert.NilError(t, err) |
|
| 38 |
+ |
|
| 39 |
+ s.Shutdown() |
|
| 40 |
+ |
|
| 41 |
+ s, err = NewStore(dir, drivers) |
|
| 42 |
+ assert.NilError(t, err) |
|
| 43 |
+ |
|
| 44 |
+ v, err := s.Get(ctx, "test1") |
|
| 45 |
+ assert.NilError(t, err) |
|
| 46 |
+ |
|
| 47 |
+ dv := v.(volume.DetailedVolume) |
|
| 48 |
+ var nilMap map[string]string |
|
| 49 |
+ assert.DeepEqual(t, nilMap, dv.Options()) |
|
| 50 |
+ assert.DeepEqual(t, nilMap, dv.Labels()) |
|
| 51 |
+ |
|
| 52 |
+ v, err = s.Get(ctx, "test2") |
|
| 53 |
+ assert.NilError(t, err) |
|
| 54 |
+ dv = v.(volume.DetailedVolume) |
|
| 55 |
+ assert.DeepEqual(t, testOpts, dv.Options()) |
|
| 56 |
+ assert.DeepEqual(t, testLabels, dv.Labels()) |
|
| 57 |
+} |
| 0 | 58 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,243 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "sync/atomic" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 |
+ "github.com/docker/docker/api/types/filters" |
|
| 8 |
+ "github.com/docker/docker/errdefs" |
|
| 9 |
+ "github.com/docker/docker/pkg/directory" |
|
| 10 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 11 |
+ "github.com/docker/docker/pkg/plugingetter" |
|
| 12 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 13 |
+ "github.com/docker/docker/volume" |
|
| 14 |
+ "github.com/docker/docker/volume/drivers" |
|
| 15 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 16 |
+ "github.com/pkg/errors" |
|
| 17 |
+ "github.com/sirupsen/logrus" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+type ds interface {
|
|
| 21 |
+ GetDriverList() []string |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+type volumeEventLogger interface {
|
|
| 25 |
+ LogVolumeEvent(volumeID, action string, attributes map[string]string) |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// VolumesService manages access to volumes |
|
| 29 |
+type VolumesService struct {
|
|
| 30 |
+ vs *VolumeStore |
|
| 31 |
+ ds ds |
|
| 32 |
+ pruneRunning int32 |
|
| 33 |
+ eventLogger volumeEventLogger |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+// NewVolumeService creates a new volume service |
|
| 37 |
+func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.IDPair, logger volumeEventLogger) (*VolumesService, error) {
|
|
| 38 |
+ ds := drivers.NewStore(pg) |
|
| 39 |
+ if err := setupDefaultDriver(ds, root, rootIDs); err != nil {
|
|
| 40 |
+ return nil, err |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ vs, err := NewStore(root, ds) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return nil, err |
|
| 46 |
+ } |
|
| 47 |
+ return &VolumesService{vs: vs, ds: ds, eventLogger: logger}, nil
|
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// GetDriverList gets the list of registered volume drivers |
|
| 51 |
+func (s *VolumesService) GetDriverList() []string {
|
|
| 52 |
+ return s.ds.GetDriverList() |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+// Create creates a volume |
|
| 56 |
+func (s *VolumesService) Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) {
|
|
| 57 |
+ if name == "" {
|
|
| 58 |
+ name = stringid.GenerateNonCryptoID() |
|
| 59 |
+ } |
|
| 60 |
+ v, err := s.vs.Create(ctx, name, driverName, opts...) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return nil, err |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ s.eventLogger.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
|
|
| 66 |
+ apiV := volumeToAPIType(v) |
|
| 67 |
+ return &apiV, nil |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+// Get gets a volume |
|
| 71 |
+func (s *VolumesService) Get(ctx context.Context, name string, getOpts ...opts.GetOption) (*types.Volume, error) {
|
|
| 72 |
+ v, err := s.vs.Get(ctx, name, getOpts...) |
|
| 73 |
+ if err != nil {
|
|
| 74 |
+ return nil, err |
|
| 75 |
+ } |
|
| 76 |
+ vol := volumeToAPIType(v) |
|
| 77 |
+ |
|
| 78 |
+ var cfg opts.GetConfig |
|
| 79 |
+ for _, o := range getOpts {
|
|
| 80 |
+ o(&cfg) |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ if cfg.ResolveStatus {
|
|
| 84 |
+ vol.Status = v.Status() |
|
| 85 |
+ } |
|
| 86 |
+ return &vol, nil |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+// Mount mounts the volume |
|
| 90 |
+func (s *VolumesService) Mount(ctx context.Context, vol *types.Volume, ref string) (string, error) {
|
|
| 91 |
+ v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver)) |
|
| 92 |
+ if err != nil {
|
|
| 93 |
+ if IsNotExist(err) {
|
|
| 94 |
+ err = errdefs.NotFound(err) |
|
| 95 |
+ } |
|
| 96 |
+ return "", err |
|
| 97 |
+ } |
|
| 98 |
+ return v.Mount(ref) |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+// Unmount unmounts the volume. |
|
| 102 |
+// Note that depending on the implementation, the volume may still be mounted due to other resources using it. |
|
| 103 |
+func (s *VolumesService) Unmount(ctx context.Context, vol *types.Volume, ref string) error {
|
|
| 104 |
+ v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver)) |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ if IsNotExist(err) {
|
|
| 107 |
+ err = errdefs.NotFound(err) |
|
| 108 |
+ } |
|
| 109 |
+ return err |
|
| 110 |
+ } |
|
| 111 |
+ return v.Unmount(ref) |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// Release releases a volume reference |
|
| 115 |
+func (s *VolumesService) Release(ctx context.Context, name string, ref string) error {
|
|
| 116 |
+ return s.vs.Release(ctx, name, ref) |
|
| 117 |
+} |
|
| 118 |
+ |
|
| 119 |
+// Remove removes a volume |
|
| 120 |
+func (s *VolumesService) Remove(ctx context.Context, name string, rmOpts ...opts.RemoveOption) error {
|
|
| 121 |
+ var cfg opts.RemoveConfig |
|
| 122 |
+ for _, o := range rmOpts {
|
|
| 123 |
+ o(&cfg) |
|
| 124 |
+ } |
|
| 125 |
+ |
|
| 126 |
+ v, err := s.vs.Get(ctx, name) |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ if IsNotExist(err) && cfg.PurgeOnError {
|
|
| 129 |
+ return nil |
|
| 130 |
+ } |
|
| 131 |
+ return err |
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ err = s.vs.Remove(ctx, v, rmOpts...) |
|
| 135 |
+ if IsNotExist(err) {
|
|
| 136 |
+ err = nil |
|
| 137 |
+ } else if IsInUse(err) {
|
|
| 138 |
+ err = errdefs.Conflict(err) |
|
| 139 |
+ } else if IsNotExist(err) && cfg.PurgeOnError {
|
|
| 140 |
+ err = nil |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ if err == nil {
|
|
| 144 |
+ s.eventLogger.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
|
|
| 145 |
+ } |
|
| 146 |
+ return err |
|
| 147 |
+} |
|
| 148 |
+ |
|
| 149 |
+var acceptedPruneFilters = map[string]bool{
|
|
| 150 |
+ "label": true, |
|
| 151 |
+ "label!": true, |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+var acceptedListFilters = map[string]bool{
|
|
| 155 |
+ "dangling": true, |
|
| 156 |
+ "name": true, |
|
| 157 |
+ "driver": true, |
|
| 158 |
+ "label": true, |
|
| 159 |
+} |
|
| 160 |
+ |
|
| 161 |
+// LocalVolumesSize gets all local volumes and fetches their size on disk |
|
| 162 |
+// Note that this intentionally skips volumes which have mount options. Typically |
|
| 163 |
+// volumes with mount options are not really local even if they are using the |
|
| 164 |
+// local driver. |
|
| 165 |
+func (s *VolumesService) LocalVolumesSize(ctx context.Context) ([]*types.Volume, error) {
|
|
| 166 |
+ ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), CustomFilter(func(v volume.Volume) bool {
|
|
| 167 |
+ dv, ok := v.(volume.DetailedVolume) |
|
| 168 |
+ return ok && len(dv.Options()) == 0 |
|
| 169 |
+ }))) |
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ return nil, err |
|
| 172 |
+ } |
|
| 173 |
+ return s.volumesToAPI(ctx, ls, calcSize(true)), nil |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+// Prune removes (local) volumes which match the past in filter arguments. |
|
| 177 |
+// Note that this intentionally skips volumes with mount options as there would |
|
| 178 |
+// be no space reclaimed in this case. |
|
| 179 |
+func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types.VolumesPruneReport, error) {
|
|
| 180 |
+ if !atomic.CompareAndSwapInt32(&s.pruneRunning, 0, 1) {
|
|
| 181 |
+ return nil, errdefs.Conflict(errors.New("a prune operation is already running"))
|
|
| 182 |
+ } |
|
| 183 |
+ defer atomic.StoreInt32(&s.pruneRunning, 0) |
|
| 184 |
+ |
|
| 185 |
+ by, err := filtersToBy(filter, acceptedPruneFilters) |
|
| 186 |
+ if err != nil {
|
|
| 187 |
+ return nil, err |
|
| 188 |
+ } |
|
| 189 |
+ ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), ByReferenced(false), by, CustomFilter(func(v volume.Volume) bool {
|
|
| 190 |
+ dv, ok := v.(volume.DetailedVolume) |
|
| 191 |
+ return ok && len(dv.Options()) == 0 |
|
| 192 |
+ }))) |
|
| 193 |
+ if err != nil {
|
|
| 194 |
+ return nil, err |
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ rep := &types.VolumesPruneReport{VolumesDeleted: make([]string, 0, len(ls))}
|
|
| 198 |
+ for _, v := range ls {
|
|
| 199 |
+ select {
|
|
| 200 |
+ case <-ctx.Done(): |
|
| 201 |
+ err := ctx.Err() |
|
| 202 |
+ if err == context.Canceled {
|
|
| 203 |
+ err = nil |
|
| 204 |
+ } |
|
| 205 |
+ return rep, err |
|
| 206 |
+ default: |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ vSize, err := directory.Size(ctx, v.Path()) |
|
| 210 |
+ if err != nil {
|
|
| 211 |
+ logrus.WithField("volume", v.Name()).WithError(err).Warn("could not determine size of volume")
|
|
| 212 |
+ } |
|
| 213 |
+ if err := s.vs.Remove(ctx, v); err != nil {
|
|
| 214 |
+ logrus.WithError(err).WithField("volume", v.Name()).Warnf("Could not determine size of volume")
|
|
| 215 |
+ continue |
|
| 216 |
+ } |
|
| 217 |
+ rep.SpaceReclaimed += uint64(vSize) |
|
| 218 |
+ rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name()) |
|
| 219 |
+ } |
|
| 220 |
+ return rep, nil |
|
| 221 |
+} |
|
| 222 |
+ |
|
| 223 |
+// List gets the list of volumes which match the past in filters |
|
| 224 |
+// If filters is nil or empty all volumes are returned. |
|
| 225 |
+func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) {
|
|
| 226 |
+ by, err := filtersToBy(filter, acceptedListFilters) |
|
| 227 |
+ if err != nil {
|
|
| 228 |
+ return nil, nil, err |
|
| 229 |
+ } |
|
| 230 |
+ |
|
| 231 |
+ volumes, warnings, err := s.vs.Find(ctx, by) |
|
| 232 |
+ if err != nil {
|
|
| 233 |
+ return nil, nil, err |
|
| 234 |
+ } |
|
| 235 |
+ |
|
| 236 |
+ return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil |
|
| 237 |
+} |
|
| 238 |
+ |
|
| 239 |
+// Shutdown shuts down the image service and dependencies |
|
| 240 |
+func (s *VolumesService) Shutdown() error {
|
|
| 241 |
+ return s.vs.Shutdown() |
|
| 242 |
+} |
| 0 | 243 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,67 @@ |
| 0 |
+package service |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "testing" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 10 |
+ "github.com/docker/docker/volume" |
|
| 11 |
+ volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 12 |
+ "github.com/docker/docker/volume/local" |
|
| 13 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 14 |
+ "github.com/docker/docker/volume/testutils" |
|
| 15 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 16 |
+ is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+func TestLocalVolumeSize(t *testing.T) {
|
|
| 20 |
+ t.Parallel() |
|
| 21 |
+ |
|
| 22 |
+ ds := volumedrivers.NewStore(nil) |
|
| 23 |
+ dir, err := ioutil.TempDir("", t.Name())
|
|
| 24 |
+ assert.Assert(t, err) |
|
| 25 |
+ defer os.RemoveAll(dir) |
|
| 26 |
+ |
|
| 27 |
+ l, err := local.New(dir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()})
|
|
| 28 |
+ assert.Assert(t, err) |
|
| 29 |
+ assert.Assert(t, ds.Register(l, volume.DefaultDriverName)) |
|
| 30 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("fake"), "fake"))
|
|
| 31 |
+ |
|
| 32 |
+ service, cleanup := newTestService(t, ds) |
|
| 33 |
+ defer cleanup() |
|
| 34 |
+ |
|
| 35 |
+ ctx := context.Background() |
|
| 36 |
+ v1, err := service.Create(ctx, "test1", volume.DefaultDriverName, opts.WithCreateReference("foo"))
|
|
| 37 |
+ assert.Assert(t, err) |
|
| 38 |
+ v2, err := service.Create(ctx, "test2", volume.DefaultDriverName) |
|
| 39 |
+ assert.Assert(t, err) |
|
| 40 |
+ _, err = service.Create(ctx, "test3", "fake") |
|
| 41 |
+ assert.Assert(t, err) |
|
| 42 |
+ |
|
| 43 |
+ data := make([]byte, 1024) |
|
| 44 |
+ err = ioutil.WriteFile(filepath.Join(v1.Mountpoint, "data"), data, 0644) |
|
| 45 |
+ assert.Assert(t, err) |
|
| 46 |
+ err = ioutil.WriteFile(filepath.Join(v2.Mountpoint, "data"), data[:1], 0644) |
|
| 47 |
+ assert.Assert(t, err) |
|
| 48 |
+ |
|
| 49 |
+ ls, err := service.LocalVolumesSize(ctx) |
|
| 50 |
+ assert.Assert(t, err) |
|
| 51 |
+ assert.Assert(t, is.Len(ls, 2)) |
|
| 52 |
+ |
|
| 53 |
+ for _, v := range ls {
|
|
| 54 |
+ switch v.Name {
|
|
| 55 |
+ case "test1": |
|
| 56 |
+ assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data)))) |
|
| 57 |
+ assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(1))) |
|
| 58 |
+ case "test2": |
|
| 59 |
+ assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data[:1])))) |
|
| 60 |
+ assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(0))) |
|
| 61 |
+ default: |
|
| 62 |
+ t.Fatalf("got unexpected volume: %+v", v)
|
|
| 63 |
+ } |
|
| 64 |
+ } |
|
| 65 |
+ assert.Assert(t, is.Equal(ls[1].UsageData.Size, int64(len(data[:1])))) |
|
| 66 |
+} |
| 0 | 67 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,253 @@ |
| 0 |
+package service |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types/filters" |
|
| 9 |
+ "github.com/docker/docker/errdefs" |
|
| 10 |
+ "github.com/docker/docker/volume" |
|
| 11 |
+ volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 12 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 13 |
+ "github.com/docker/docker/volume/testutils" |
|
| 14 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 15 |
+ is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+func TestServiceCreate(t *testing.T) {
|
|
| 19 |
+ t.Parallel() |
|
| 20 |
+ |
|
| 21 |
+ ds := volumedrivers.NewStore(nil) |
|
| 22 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
|
| 23 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
|
|
| 24 |
+ |
|
| 25 |
+ ctx := context.Background() |
|
| 26 |
+ service, cleanup := newTestService(t, ds) |
|
| 27 |
+ defer cleanup() |
|
| 28 |
+ |
|
| 29 |
+ _, err := service.Create(ctx, "v1", "notexist") |
|
| 30 |
+ assert.Assert(t, errdefs.IsNotFound(err), err) |
|
| 31 |
+ |
|
| 32 |
+ v, err := service.Create(ctx, "v1", "d1") |
|
| 33 |
+ assert.Assert(t, err) |
|
| 34 |
+ |
|
| 35 |
+ vCopy, err := service.Create(ctx, "v1", "d1") |
|
| 36 |
+ assert.Assert(t, err) |
|
| 37 |
+ assert.Assert(t, is.DeepEqual(v, vCopy)) |
|
| 38 |
+ |
|
| 39 |
+ _, err = service.Create(ctx, "v1", "d2") |
|
| 40 |
+ assert.Check(t, IsNameConflict(err), err) |
|
| 41 |
+ assert.Check(t, errdefs.IsConflict(err), err) |
|
| 42 |
+ |
|
| 43 |
+ assert.Assert(t, service.Remove(ctx, "v1")) |
|
| 44 |
+ _, err = service.Create(ctx, "v1", "d2") |
|
| 45 |
+ assert.Assert(t, err) |
|
| 46 |
+ _, err = service.Create(ctx, "v1", "d2") |
|
| 47 |
+ assert.Assert(t, err) |
|
| 48 |
+ |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+func TestServiceList(t *testing.T) {
|
|
| 52 |
+ t.Parallel() |
|
| 53 |
+ |
|
| 54 |
+ ds := volumedrivers.NewStore(nil) |
|
| 55 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
|
| 56 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
|
|
| 57 |
+ |
|
| 58 |
+ service, cleanup := newTestService(t, ds) |
|
| 59 |
+ defer cleanup() |
|
| 60 |
+ |
|
| 61 |
+ ctx := context.Background() |
|
| 62 |
+ |
|
| 63 |
+ _, err := service.Create(ctx, "v1", "d1") |
|
| 64 |
+ assert.Assert(t, err) |
|
| 65 |
+ _, err = service.Create(ctx, "v2", "d1") |
|
| 66 |
+ assert.Assert(t, err) |
|
| 67 |
+ _, err = service.Create(ctx, "v3", "d2") |
|
| 68 |
+ assert.Assert(t, err) |
|
| 69 |
+ |
|
| 70 |
+ ls, _, err := service.List(ctx, filters.NewArgs(filters.Arg("driver", "d1")))
|
|
| 71 |
+ assert.Assert(t, err) |
|
| 72 |
+ assert.Check(t, is.Len(ls, 2)) |
|
| 73 |
+ |
|
| 74 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "d2")))
|
|
| 75 |
+ assert.Assert(t, err) |
|
| 76 |
+ assert.Check(t, is.Len(ls, 1)) |
|
| 77 |
+ |
|
| 78 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "notexist")))
|
|
| 79 |
+ assert.Assert(t, err) |
|
| 80 |
+ assert.Check(t, is.Len(ls, 0)) |
|
| 81 |
+ |
|
| 82 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
|
|
| 83 |
+ assert.Assert(t, err) |
|
| 84 |
+ assert.Check(t, is.Len(ls, 3)) |
|
| 85 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
|
|
| 86 |
+ assert.Assert(t, err) |
|
| 87 |
+ assert.Check(t, is.Len(ls, 0)) |
|
| 88 |
+ |
|
| 89 |
+ _, err = service.Get(ctx, "v1", opts.WithGetReference("foo"))
|
|
| 90 |
+ assert.Assert(t, err) |
|
| 91 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
|
|
| 92 |
+ assert.Assert(t, err) |
|
| 93 |
+ assert.Check(t, is.Len(ls, 2)) |
|
| 94 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
|
|
| 95 |
+ assert.Assert(t, err) |
|
| 96 |
+ assert.Check(t, is.Len(ls, 1)) |
|
| 97 |
+ |
|
| 98 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false"), filters.Arg("driver", "d2")))
|
|
| 99 |
+ assert.Assert(t, err) |
|
| 100 |
+ assert.Check(t, is.Len(ls, 0)) |
|
| 101 |
+ ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true"), filters.Arg("driver", "d2")))
|
|
| 102 |
+ assert.Assert(t, err) |
|
| 103 |
+ assert.Check(t, is.Len(ls, 1)) |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+func TestServiceRemove(t *testing.T) {
|
|
| 107 |
+ t.Parallel() |
|
| 108 |
+ |
|
| 109 |
+ ds := volumedrivers.NewStore(nil) |
|
| 110 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
|
| 111 |
+ |
|
| 112 |
+ service, cleanup := newTestService(t, ds) |
|
| 113 |
+ defer cleanup() |
|
| 114 |
+ ctx := context.Background() |
|
| 115 |
+ |
|
| 116 |
+ _, err := service.Create(ctx, "test", "d1") |
|
| 117 |
+ assert.Assert(t, err) |
|
| 118 |
+ |
|
| 119 |
+ assert.Assert(t, service.Remove(ctx, "test")) |
|
| 120 |
+ assert.Assert(t, service.Remove(ctx, "test", opts.WithPurgeOnError(true))) |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func TestServiceGet(t *testing.T) {
|
|
| 124 |
+ t.Parallel() |
|
| 125 |
+ |
|
| 126 |
+ ds := volumedrivers.NewStore(nil) |
|
| 127 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
|
|
| 128 |
+ |
|
| 129 |
+ service, cleanup := newTestService(t, ds) |
|
| 130 |
+ defer cleanup() |
|
| 131 |
+ ctx := context.Background() |
|
| 132 |
+ |
|
| 133 |
+ v, err := service.Get(ctx, "notexist") |
|
| 134 |
+ assert.Assert(t, IsNotExist(err)) |
|
| 135 |
+ assert.Check(t, v == nil) |
|
| 136 |
+ |
|
| 137 |
+ created, err := service.Create(ctx, "test", "d1") |
|
| 138 |
+ assert.Assert(t, err) |
|
| 139 |
+ assert.Assert(t, created != nil) |
|
| 140 |
+ |
|
| 141 |
+ v, err = service.Get(ctx, "test") |
|
| 142 |
+ assert.Assert(t, err) |
|
| 143 |
+ assert.Assert(t, is.DeepEqual(created, v)) |
|
| 144 |
+ |
|
| 145 |
+ v, err = service.Get(ctx, "test", opts.WithGetResolveStatus) |
|
| 146 |
+ assert.Assert(t, err) |
|
| 147 |
+ assert.Assert(t, is.Len(v.Status, 1), v.Status) |
|
| 148 |
+ |
|
| 149 |
+ v, err = service.Get(ctx, "test", opts.WithGetDriver("notarealdriver"))
|
|
| 150 |
+ assert.Assert(t, errdefs.IsConflict(err), err) |
|
| 151 |
+ v, err = service.Get(ctx, "test", opts.WithGetDriver("d1"))
|
|
| 152 |
+ assert.Assert(t, err == nil) |
|
| 153 |
+ assert.Assert(t, is.DeepEqual(created, v)) |
|
| 154 |
+ |
|
| 155 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
|
|
| 156 |
+ v, err = service.Get(ctx, "test", opts.WithGetDriver("d2"))
|
|
| 157 |
+ assert.Assert(t, errdefs.IsConflict(err), err) |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func TestServicePrune(t *testing.T) {
|
|
| 161 |
+ t.Parallel() |
|
| 162 |
+ |
|
| 163 |
+ ds := volumedrivers.NewStore(nil) |
|
| 164 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver(volume.DefaultDriverName), volume.DefaultDriverName)) |
|
| 165 |
+ assert.Assert(t, ds.Register(testutils.NewFakeDriver("other"), "other"))
|
|
| 166 |
+ |
|
| 167 |
+ service, cleanup := newTestService(t, ds) |
|
| 168 |
+ defer cleanup() |
|
| 169 |
+ ctx := context.Background() |
|
| 170 |
+ |
|
| 171 |
+ _, err := service.Create(ctx, "test", volume.DefaultDriverName) |
|
| 172 |
+ assert.Assert(t, err) |
|
| 173 |
+ _, err = service.Create(ctx, "test2", "other") |
|
| 174 |
+ assert.Assert(t, err) |
|
| 175 |
+ |
|
| 176 |
+ pr, err := service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana")))
|
|
| 177 |
+ assert.Assert(t, err) |
|
| 178 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 0)) |
|
| 179 |
+ |
|
| 180 |
+ pr, err = service.Prune(ctx, filters.NewArgs()) |
|
| 181 |
+ assert.Assert(t, err) |
|
| 182 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1)) |
|
| 183 |
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test")) |
|
| 184 |
+ |
|
| 185 |
+ _, err = service.Get(ctx, "test") |
|
| 186 |
+ assert.Assert(t, IsNotExist(err), err) |
|
| 187 |
+ |
|
| 188 |
+ v, err := service.Get(ctx, "test2") |
|
| 189 |
+ assert.Assert(t, err) |
|
| 190 |
+ assert.Assert(t, is.Equal(v.Driver, "other")) |
|
| 191 |
+ |
|
| 192 |
+ _, err = service.Create(ctx, "test", volume.DefaultDriverName) |
|
| 193 |
+ assert.Assert(t, err) |
|
| 194 |
+ |
|
| 195 |
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
|
|
| 196 |
+ assert.Assert(t, err) |
|
| 197 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1)) |
|
| 198 |
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test")) |
|
| 199 |
+ v, err = service.Get(ctx, "test2") |
|
| 200 |
+ assert.Assert(t, err) |
|
| 201 |
+ assert.Assert(t, is.Equal(v.Driver, "other")) |
|
| 202 |
+ |
|
| 203 |
+ _, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": ""}))
|
|
| 204 |
+ assert.Assert(t, err) |
|
| 205 |
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
|
|
| 206 |
+ assert.Assert(t, err) |
|
| 207 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 0)) |
|
| 208 |
+ |
|
| 209 |
+ _, err = service.Create(ctx, "test3", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": "split"}))
|
|
| 210 |
+ assert.Assert(t, err) |
|
| 211 |
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana=split")))
|
|
| 212 |
+ assert.Assert(t, err) |
|
| 213 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1)) |
|
| 214 |
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test")) |
|
| 215 |
+ |
|
| 216 |
+ pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana=split")))
|
|
| 217 |
+ assert.Assert(t, err) |
|
| 218 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1)) |
|
| 219 |
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test3")) |
|
| 220 |
+ |
|
| 221 |
+ v, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateReference(t.Name())) |
|
| 222 |
+ assert.Assert(t, err) |
|
| 223 |
+ |
|
| 224 |
+ pr, err = service.Prune(ctx, filters.NewArgs()) |
|
| 225 |
+ assert.Assert(t, err) |
|
| 226 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 0)) |
|
| 227 |
+ assert.Assert(t, service.Release(ctx, v.Name, t.Name())) |
|
| 228 |
+ |
|
| 229 |
+ pr, err = service.Prune(ctx, filters.NewArgs()) |
|
| 230 |
+ assert.Assert(t, err) |
|
| 231 |
+ assert.Assert(t, is.Len(pr.VolumesDeleted, 1)) |
|
| 232 |
+ assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test")) |
|
| 233 |
+} |
|
| 234 |
+ |
|
| 235 |
+func newTestService(t *testing.T, ds *volumedrivers.Store) (*VolumesService, func()) {
|
|
| 236 |
+ t.Helper() |
|
| 237 |
+ |
|
| 238 |
+ dir, err := ioutil.TempDir("", t.Name())
|
|
| 239 |
+ assert.Assert(t, err) |
|
| 240 |
+ |
|
| 241 |
+ store, err := NewStore(dir, ds) |
|
| 242 |
+ assert.Assert(t, err) |
|
| 243 |
+ s := &VolumesService{vs: store, eventLogger: dummyEventLogger{}}
|
|
| 244 |
+ return s, func() {
|
|
| 245 |
+ assert.Check(t, s.Shutdown()) |
|
| 246 |
+ assert.Check(t, os.RemoveAll(dir)) |
|
| 247 |
+ } |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 250 |
+type dummyEventLogger struct{}
|
|
| 251 |
+ |
|
| 252 |
+func (dummyEventLogger) LogVolumeEvent(_, _ string, _ map[string]string) {}
|
| 0 | 253 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,858 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "net" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "runtime" |
|
| 9 |
+ "sync" |
|
| 10 |
+ "time" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/pkg/errors" |
|
| 13 |
+ |
|
| 14 |
+ "github.com/boltdb/bolt" |
|
| 15 |
+ "github.com/docker/docker/errdefs" |
|
| 16 |
+ "github.com/docker/docker/pkg/locker" |
|
| 17 |
+ "github.com/docker/docker/volume" |
|
| 18 |
+ "github.com/docker/docker/volume/drivers" |
|
| 19 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 20 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 21 |
+ "github.com/sirupsen/logrus" |
|
| 22 |
+) |
|
| 23 |
+ |
|
| 24 |
+const ( |
|
| 25 |
+ volumeDataDir = "volumes" |
|
| 26 |
+) |
|
| 27 |
+ |
|
| 28 |
+type volumeWrapper struct {
|
|
| 29 |
+ volume.Volume |
|
| 30 |
+ labels map[string]string |
|
| 31 |
+ scope string |
|
| 32 |
+ options map[string]string |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func (v volumeWrapper) Options() map[string]string {
|
|
| 36 |
+ if v.options == nil {
|
|
| 37 |
+ return nil |
|
| 38 |
+ } |
|
| 39 |
+ options := make(map[string]string, len(v.options)) |
|
| 40 |
+ for key, value := range v.options {
|
|
| 41 |
+ options[key] = value |
|
| 42 |
+ } |
|
| 43 |
+ return options |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+func (v volumeWrapper) Labels() map[string]string {
|
|
| 47 |
+ if v.labels == nil {
|
|
| 48 |
+ return nil |
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ labels := make(map[string]string, len(v.labels)) |
|
| 52 |
+ for key, value := range v.labels {
|
|
| 53 |
+ labels[key] = value |
|
| 54 |
+ } |
|
| 55 |
+ return labels |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func (v volumeWrapper) Scope() string {
|
|
| 59 |
+ return v.scope |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+func (v volumeWrapper) CachedPath() string {
|
|
| 63 |
+ if vv, ok := v.Volume.(interface {
|
|
| 64 |
+ CachedPath() string |
|
| 65 |
+ }); ok {
|
|
| 66 |
+ return vv.CachedPath() |
|
| 67 |
+ } |
|
| 68 |
+ return v.Volume.Path() |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+// NewStore creates a new volume store at the given path |
|
| 72 |
+func NewStore(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
|
|
| 73 |
+ vs := &VolumeStore{
|
|
| 74 |
+ locks: &locker.Locker{},
|
|
| 75 |
+ names: make(map[string]volume.Volume), |
|
| 76 |
+ refs: make(map[string]map[string]struct{}),
|
|
| 77 |
+ labels: make(map[string]map[string]string), |
|
| 78 |
+ options: make(map[string]map[string]string), |
|
| 79 |
+ drivers: drivers, |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ if rootPath != "" {
|
|
| 83 |
+ // initialize metadata store |
|
| 84 |
+ volPath := filepath.Join(rootPath, volumeDataDir) |
|
| 85 |
+ if err := os.MkdirAll(volPath, 0750); err != nil {
|
|
| 86 |
+ return nil, err |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ var err error |
|
| 90 |
+ vs.db, err = bolt.Open(filepath.Join(volPath, "metadata.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ return nil, errors.Wrap(err, "error while opening volume store metadata database") |
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ // initialize volumes bucket |
|
| 96 |
+ if err := vs.db.Update(func(tx *bolt.Tx) error {
|
|
| 97 |
+ if _, err := tx.CreateBucketIfNotExists(volumeBucketName); err != nil {
|
|
| 98 |
+ return errors.Wrap(err, "error while setting up volume store metadata database") |
|
| 99 |
+ } |
|
| 100 |
+ return nil |
|
| 101 |
+ }); err != nil {
|
|
| 102 |
+ return nil, err |
|
| 103 |
+ } |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ vs.restore() |
|
| 107 |
+ |
|
| 108 |
+ return vs, nil |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) {
|
|
| 112 |
+ s.globalLock.RLock() |
|
| 113 |
+ v, exists := s.names[name] |
|
| 114 |
+ s.globalLock.RUnlock() |
|
| 115 |
+ return v, exists |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+func (s *VolumeStore) setNamed(v volume.Volume, ref string) {
|
|
| 119 |
+ name := v.Name() |
|
| 120 |
+ |
|
| 121 |
+ s.globalLock.Lock() |
|
| 122 |
+ s.names[name] = v |
|
| 123 |
+ if len(ref) > 0 {
|
|
| 124 |
+ if s.refs[name] == nil {
|
|
| 125 |
+ s.refs[name] = make(map[string]struct{})
|
|
| 126 |
+ } |
|
| 127 |
+ s.refs[name][ref] = struct{}{}
|
|
| 128 |
+ } |
|
| 129 |
+ s.globalLock.Unlock() |
|
| 130 |
+} |
|
| 131 |
+ |
|
| 132 |
+// hasRef returns true if the given name has at least one ref. |
|
| 133 |
+// Callers of this function are expected to hold the name lock. |
|
| 134 |
+func (s *VolumeStore) hasRef(name string) bool {
|
|
| 135 |
+ s.globalLock.RLock() |
|
| 136 |
+ l := len(s.refs[name]) |
|
| 137 |
+ s.globalLock.RUnlock() |
|
| 138 |
+ return l > 0 |
|
| 139 |
+} |
|
| 140 |
+ |
|
| 141 |
+// getRefs gets the list of refs for a given name |
|
| 142 |
+// Callers of this function are expected to hold the name lock. |
|
| 143 |
+func (s *VolumeStore) getRefs(name string) []string {
|
|
| 144 |
+ s.globalLock.RLock() |
|
| 145 |
+ defer s.globalLock.RUnlock() |
|
| 146 |
+ |
|
| 147 |
+ refs := make([]string, 0, len(s.refs[name])) |
|
| 148 |
+ for r := range s.refs[name] {
|
|
| 149 |
+ refs = append(refs, r) |
|
| 150 |
+ } |
|
| 151 |
+ |
|
| 152 |
+ return refs |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+// purge allows the cleanup of internal data on docker in case |
|
| 156 |
+// the internal data is out of sync with volumes driver plugins. |
|
| 157 |
+func (s *VolumeStore) purge(ctx context.Context, name string) error {
|
|
| 158 |
+ s.globalLock.Lock() |
|
| 159 |
+ defer s.globalLock.Unlock() |
|
| 160 |
+ |
|
| 161 |
+ select {
|
|
| 162 |
+ case <-ctx.Done(): |
|
| 163 |
+ return ctx.Err() |
|
| 164 |
+ default: |
|
| 165 |
+ } |
|
| 166 |
+ |
|
| 167 |
+ v, exists := s.names[name] |
|
| 168 |
+ if exists {
|
|
| 169 |
+ driverName := v.DriverName() |
|
| 170 |
+ if _, err := s.drivers.ReleaseDriver(driverName); err != nil {
|
|
| 171 |
+ logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
|
|
| 172 |
+ } |
|
| 173 |
+ } |
|
| 174 |
+ if err := s.removeMeta(name); err != nil {
|
|
| 175 |
+ logrus.Errorf("Error removing volume metadata for volume %q: %v", name, err)
|
|
| 176 |
+ } |
|
| 177 |
+ delete(s.names, name) |
|
| 178 |
+ delete(s.refs, name) |
|
| 179 |
+ delete(s.labels, name) |
|
| 180 |
+ delete(s.options, name) |
|
| 181 |
+ return nil |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts |
|
| 185 |
+type VolumeStore struct {
|
|
| 186 |
+ // locks ensures that only one action is being performed on a particular volume at a time without locking the entire store |
|
| 187 |
+ // since actions on volumes can be quite slow, this ensures the store is free to handle requests for other volumes. |
|
| 188 |
+ locks *locker.Locker |
|
| 189 |
+ drivers *drivers.Store |
|
| 190 |
+ // globalLock is used to protect access to mutable structures used by the store object |
|
| 191 |
+ globalLock sync.RWMutex |
|
| 192 |
+ // names stores the volume name -> volume relationship. |
|
| 193 |
+ // This is used for making lookups faster so we don't have to probe all drivers |
|
| 194 |
+ names map[string]volume.Volume |
|
| 195 |
+ // refs stores the volume name and the list of things referencing it |
|
| 196 |
+ refs map[string]map[string]struct{}
|
|
| 197 |
+ // labels stores volume labels for each volume |
|
| 198 |
+ labels map[string]map[string]string |
|
| 199 |
+ // options stores volume options for each volume |
|
| 200 |
+ options map[string]map[string]string |
|
| 201 |
+ db *bolt.DB |
|
| 202 |
+} |
|
| 203 |
+ |
|
| 204 |
+func filterByDriver(names []string) filterFunc {
|
|
| 205 |
+ return func(v volume.Volume) bool {
|
|
| 206 |
+ for _, name := range names {
|
|
| 207 |
+ if name == v.DriverName() {
|
|
| 208 |
+ return true |
|
| 209 |
+ } |
|
| 210 |
+ } |
|
| 211 |
+ return false |
|
| 212 |
+ } |
|
| 213 |
+} |
|
| 214 |
+ |
|
| 215 |
+func (s *VolumeStore) byReferenced(referenced bool) filterFunc {
|
|
| 216 |
+ return func(v volume.Volume) bool {
|
|
| 217 |
+ return s.hasRef(v.Name()) == referenced |
|
| 218 |
+ } |
|
| 219 |
+} |
|
| 220 |
+ |
|
| 221 |
+func (s *VolumeStore) filter(ctx context.Context, vols *[]volume.Volume, by By) (warnings []string, err error) {
|
|
| 222 |
+ // note that this specifically does not support the `FromList` By type. |
|
| 223 |
+ switch f := by.(type) {
|
|
| 224 |
+ case nil: |
|
| 225 |
+ if *vols == nil {
|
|
| 226 |
+ var ls []volume.Volume |
|
| 227 |
+ ls, warnings, err = s.list(ctx) |
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ return warnings, err |
|
| 230 |
+ } |
|
| 231 |
+ *vols = ls |
|
| 232 |
+ } |
|
| 233 |
+ case byDriver: |
|
| 234 |
+ if *vols != nil {
|
|
| 235 |
+ filter(vols, filterByDriver([]string(f))) |
|
| 236 |
+ return nil, nil |
|
| 237 |
+ } |
|
| 238 |
+ var ls []volume.Volume |
|
| 239 |
+ ls, warnings, err = s.list(ctx, []string(f)...) |
|
| 240 |
+ if err != nil {
|
|
| 241 |
+ return nil, err |
|
| 242 |
+ } |
|
| 243 |
+ *vols = ls |
|
| 244 |
+ case ByReferenced: |
|
| 245 |
+ // TODO(@cpuguy83): It would be nice to optimize this by looking at the list |
|
| 246 |
+ // of referenced volumes, however the locking strategy makes this difficult |
|
| 247 |
+ // without either providing inconsistent data or deadlocks. |
|
| 248 |
+ if *vols == nil {
|
|
| 249 |
+ var ls []volume.Volume |
|
| 250 |
+ ls, warnings, err = s.list(ctx) |
|
| 251 |
+ if err != nil {
|
|
| 252 |
+ return nil, err |
|
| 253 |
+ } |
|
| 254 |
+ *vols = ls |
|
| 255 |
+ } |
|
| 256 |
+ filter(vols, s.byReferenced(bool(f))) |
|
| 257 |
+ case andCombinator: |
|
| 258 |
+ for _, by := range f {
|
|
| 259 |
+ w, err := s.filter(ctx, vols, by) |
|
| 260 |
+ if err != nil {
|
|
| 261 |
+ return warnings, err |
|
| 262 |
+ } |
|
| 263 |
+ warnings = append(warnings, w...) |
|
| 264 |
+ } |
|
| 265 |
+ case orCombinator: |
|
| 266 |
+ for _, by := range f {
|
|
| 267 |
+ switch by.(type) {
|
|
| 268 |
+ case byDriver: |
|
| 269 |
+ var ls []volume.Volume |
|
| 270 |
+ w, err := s.filter(ctx, &ls, by) |
|
| 271 |
+ if err != nil {
|
|
| 272 |
+ return warnings, err |
|
| 273 |
+ } |
|
| 274 |
+ warnings = append(warnings, w...) |
|
| 275 |
+ default: |
|
| 276 |
+ ls, w, err := s.list(ctx) |
|
| 277 |
+ if err != nil {
|
|
| 278 |
+ return warnings, err |
|
| 279 |
+ } |
|
| 280 |
+ warnings = append(warnings, w...) |
|
| 281 |
+ w, err = s.filter(ctx, &ls, by) |
|
| 282 |
+ if err != nil {
|
|
| 283 |
+ return warnings, err |
|
| 284 |
+ } |
|
| 285 |
+ warnings = append(warnings, w...) |
|
| 286 |
+ *vols = append(*vols, ls...) |
|
| 287 |
+ } |
|
| 288 |
+ } |
|
| 289 |
+ unique(vols) |
|
| 290 |
+ case CustomFilter: |
|
| 291 |
+ if *vols == nil {
|
|
| 292 |
+ var ls []volume.Volume |
|
| 293 |
+ ls, warnings, err = s.list(ctx) |
|
| 294 |
+ if err != nil {
|
|
| 295 |
+ return nil, err |
|
| 296 |
+ } |
|
| 297 |
+ *vols = ls |
|
| 298 |
+ } |
|
| 299 |
+ filter(vols, filterFunc(f)) |
|
| 300 |
+ default: |
|
| 301 |
+ return nil, errdefs.InvalidParameter(errors.Errorf("unsupported filter: %T", f))
|
|
| 302 |
+ } |
|
| 303 |
+ return warnings, nil |
|
| 304 |
+} |
|
| 305 |
+ |
|
| 306 |
+func unique(ls *[]volume.Volume) {
|
|
| 307 |
+ names := make(map[string]bool, len(*ls)) |
|
| 308 |
+ filter(ls, func(v volume.Volume) bool {
|
|
| 309 |
+ if names[v.Name()] {
|
|
| 310 |
+ return false |
|
| 311 |
+ } |
|
| 312 |
+ names[v.Name()] = true |
|
| 313 |
+ return true |
|
| 314 |
+ }) |
|
| 315 |
+} |
|
| 316 |
+ |
|
| 317 |
+// Find lists volumes filtered by the past in filter. |
|
| 318 |
+// If a driver returns a volume that has name which conflicts with another volume from a different driver, |
|
| 319 |
+// the first volume is chosen and the conflicting volume is dropped. |
|
| 320 |
+func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) {
|
|
| 321 |
+ logrus.WithField("ByType", fmt.Sprintf("%T", by)).WithField("ByValue", fmt.Sprintf("%+v", by)).Debug("VolumeStore.Find")
|
|
| 322 |
+ switch f := by.(type) {
|
|
| 323 |
+ case nil, orCombinator, andCombinator, byDriver, ByReferenced, CustomFilter: |
|
| 324 |
+ warnings, err = s.filter(ctx, &vols, by) |
|
| 325 |
+ case fromList: |
|
| 326 |
+ warnings, err = s.filter(ctx, f.ls, f.by) |
|
| 327 |
+ default: |
|
| 328 |
+ // Really shouldn't be possible, but makes sure that any new By's are added to this check. |
|
| 329 |
+ err = errdefs.InvalidParameter(errors.Errorf("unsupported filter type: %T", f))
|
|
| 330 |
+ } |
|
| 331 |
+ if err != nil {
|
|
| 332 |
+ return nil, nil, &OpErr{Err: err, Op: "list"}
|
|
| 333 |
+ } |
|
| 334 |
+ |
|
| 335 |
+ var out []volume.Volume |
|
| 336 |
+ |
|
| 337 |
+ for _, v := range vols {
|
|
| 338 |
+ name := normalizeVolumeName(v.Name()) |
|
| 339 |
+ |
|
| 340 |
+ s.locks.Lock(name) |
|
| 341 |
+ storedV, exists := s.getNamed(name) |
|
| 342 |
+ // Note: it's not safe to populate the cache here because the volume may have been |
|
| 343 |
+ // deleted before we acquire a lock on its name |
|
| 344 |
+ if exists && storedV.DriverName() != v.DriverName() {
|
|
| 345 |
+ logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), storedV.DriverName(), v.DriverName())
|
|
| 346 |
+ s.locks.Unlock(v.Name()) |
|
| 347 |
+ continue |
|
| 348 |
+ } |
|
| 349 |
+ |
|
| 350 |
+ out = append(out, v) |
|
| 351 |
+ s.locks.Unlock(v.Name()) |
|
| 352 |
+ } |
|
| 353 |
+ return out, warnings, nil |
|
| 354 |
+} |
|
| 355 |
+ |
|
| 356 |
+type filterFunc func(volume.Volume) bool |
|
| 357 |
+ |
|
| 358 |
+func filter(vols *[]volume.Volume, fn filterFunc) {
|
|
| 359 |
+ var evict []int |
|
| 360 |
+ for i, v := range *vols {
|
|
| 361 |
+ if !fn(v) {
|
|
| 362 |
+ evict = append(evict, i) |
|
| 363 |
+ } |
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ for n, i := range evict {
|
|
| 367 |
+ copy((*vols)[i-n:], (*vols)[i-n+1:]) |
|
| 368 |
+ (*vols)[len(*vols)-1] = nil |
|
| 369 |
+ *vols = (*vols)[:len(*vols)-1] |
|
| 370 |
+ } |
|
| 371 |
+} |
|
| 372 |
+ |
|
| 373 |
+// list goes through each volume driver and asks for its list of volumes. |
|
| 374 |
+// TODO(@cpuguy83): plumb context through |
|
| 375 |
+func (s *VolumeStore) list(ctx context.Context, driverNames ...string) ([]volume.Volume, []string, error) {
|
|
| 376 |
+ var ( |
|
| 377 |
+ ls = []volume.Volume{} // do not return a nil value as this affects filtering
|
|
| 378 |
+ warnings []string |
|
| 379 |
+ ) |
|
| 380 |
+ |
|
| 381 |
+ var dls []volume.Driver |
|
| 382 |
+ |
|
| 383 |
+ all, err := s.drivers.GetAllDrivers() |
|
| 384 |
+ if err != nil {
|
|
| 385 |
+ return nil, nil, err |
|
| 386 |
+ } |
|
| 387 |
+ if len(driverNames) == 0 {
|
|
| 388 |
+ dls = all |
|
| 389 |
+ } else {
|
|
| 390 |
+ idx := make(map[string]bool, len(driverNames)) |
|
| 391 |
+ for _, name := range driverNames {
|
|
| 392 |
+ idx[name] = true |
|
| 393 |
+ } |
|
| 394 |
+ for _, d := range all {
|
|
| 395 |
+ if idx[d.Name()] {
|
|
| 396 |
+ dls = append(dls, d) |
|
| 397 |
+ } |
|
| 398 |
+ } |
|
| 399 |
+ } |
|
| 400 |
+ |
|
| 401 |
+ type vols struct {
|
|
| 402 |
+ vols []volume.Volume |
|
| 403 |
+ err error |
|
| 404 |
+ driverName string |
|
| 405 |
+ } |
|
| 406 |
+ chVols := make(chan vols, len(dls)) |
|
| 407 |
+ |
|
| 408 |
+ for _, vd := range dls {
|
|
| 409 |
+ go func(d volume.Driver) {
|
|
| 410 |
+ vs, err := d.List() |
|
| 411 |
+ if err != nil {
|
|
| 412 |
+ chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
|
|
| 413 |
+ return |
|
| 414 |
+ } |
|
| 415 |
+ for i, v := range vs {
|
|
| 416 |
+ s.globalLock.RLock() |
|
| 417 |
+ vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope(), s.options[v.Name()]}
|
|
| 418 |
+ s.globalLock.RUnlock() |
|
| 419 |
+ } |
|
| 420 |
+ |
|
| 421 |
+ chVols <- vols{vols: vs}
|
|
| 422 |
+ }(vd) |
|
| 423 |
+ } |
|
| 424 |
+ |
|
| 425 |
+ badDrivers := make(map[string]struct{})
|
|
| 426 |
+ for i := 0; i < len(dls); i++ {
|
|
| 427 |
+ vs := <-chVols |
|
| 428 |
+ |
|
| 429 |
+ if vs.err != nil {
|
|
| 430 |
+ warnings = append(warnings, vs.err.Error()) |
|
| 431 |
+ badDrivers[vs.driverName] = struct{}{}
|
|
| 432 |
+ } |
|
| 433 |
+ ls = append(ls, vs.vols...) |
|
| 434 |
+ } |
|
| 435 |
+ |
|
| 436 |
+ if len(badDrivers) > 0 {
|
|
| 437 |
+ s.globalLock.RLock() |
|
| 438 |
+ for _, v := range s.names {
|
|
| 439 |
+ if _, exists := badDrivers[v.DriverName()]; exists {
|
|
| 440 |
+ ls = append(ls, v) |
|
| 441 |
+ } |
|
| 442 |
+ } |
|
| 443 |
+ s.globalLock.RUnlock() |
|
| 444 |
+ } |
|
| 445 |
+ return ls, warnings, nil |
|
| 446 |
+} |
|
| 447 |
+ |
|
| 448 |
+// Create creates a volume with the given name and driver |
|
| 449 |
+// If the volume needs to be created with a reference to prevent race conditions |
|
| 450 |
+// with volume cleanup, make sure to use the `CreateWithReference` option. |
|
| 451 |
+func (s *VolumeStore) Create(ctx context.Context, name, driverName string, createOpts ...opts.CreateOption) (volume.Volume, error) {
|
|
| 452 |
+ var cfg opts.CreateConfig |
|
| 453 |
+ for _, o := range createOpts {
|
|
| 454 |
+ o(&cfg) |
|
| 455 |
+ } |
|
| 456 |
+ |
|
| 457 |
+ name = normalizeVolumeName(name) |
|
| 458 |
+ s.locks.Lock(name) |
|
| 459 |
+ defer s.locks.Unlock(name) |
|
| 460 |
+ |
|
| 461 |
+ select {
|
|
| 462 |
+ case <-ctx.Done(): |
|
| 463 |
+ return nil, ctx.Err() |
|
| 464 |
+ default: |
|
| 465 |
+ } |
|
| 466 |
+ |
|
| 467 |
+ v, err := s.create(ctx, name, driverName, cfg.Options, cfg.Labels) |
|
| 468 |
+ if err != nil {
|
|
| 469 |
+ if _, ok := err.(*OpErr); ok {
|
|
| 470 |
+ return nil, err |
|
| 471 |
+ } |
|
| 472 |
+ return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
|
| 473 |
+ } |
|
| 474 |
+ |
|
| 475 |
+ s.setNamed(v, cfg.Reference) |
|
| 476 |
+ return v, nil |
|
| 477 |
+} |
|
| 478 |
+ |
|
| 479 |
+// checkConflict checks the local cache for name collisions with the passed in name, |
|
| 480 |
+// for existing volumes with the same name but in a different driver. |
|
| 481 |
+// This is used by `Create` as a best effort to prevent name collisions for volumes. |
|
| 482 |
+// If a matching volume is found that is not a conflict that is returned so the caller |
|
| 483 |
+// does not need to perform an additional lookup. |
|
| 484 |
+// When no matching volume is found, both returns will be nil |
|
| 485 |
+// |
|
| 486 |
+// Note: This does not probe all the drivers for name collisions because v1 plugins |
|
| 487 |
+// are very slow, particularly if the plugin is down, and cause other issues, |
|
| 488 |
+// particularly around locking the store. |
|
| 489 |
+// TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially |
|
| 490 |
+// use a connect timeout for this kind of check to ensure we aren't blocking for a |
|
| 491 |
+// long time. |
|
| 492 |
+func (s *VolumeStore) checkConflict(ctx context.Context, name, driverName string) (volume.Volume, error) {
|
|
| 493 |
+ // check the local cache |
|
| 494 |
+ v, _ := s.getNamed(name) |
|
| 495 |
+ if v == nil {
|
|
| 496 |
+ return nil, nil |
|
| 497 |
+ } |
|
| 498 |
+ |
|
| 499 |
+ vDriverName := v.DriverName() |
|
| 500 |
+ var conflict bool |
|
| 501 |
+ if driverName != "" {
|
|
| 502 |
+ // Retrieve canonical driver name to avoid inconsistencies (for example |
|
| 503 |
+ // "plugin" vs. "plugin:latest") |
|
| 504 |
+ vd, err := s.drivers.GetDriver(driverName) |
|
| 505 |
+ if err != nil {
|
|
| 506 |
+ return nil, err |
|
| 507 |
+ } |
|
| 508 |
+ |
|
| 509 |
+ if vDriverName != vd.Name() {
|
|
| 510 |
+ conflict = true |
|
| 511 |
+ } |
|
| 512 |
+ } |
|
| 513 |
+ |
|
| 514 |
+ // let's check if the found volume ref |
|
| 515 |
+ // is stale by checking with the driver if it still exists |
|
| 516 |
+ exists, err := volumeExists(ctx, s.drivers, v) |
|
| 517 |
+ if err != nil {
|
|
| 518 |
+ 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) |
|
| 519 |
+ } |
|
| 520 |
+ |
|
| 521 |
+ if exists {
|
|
| 522 |
+ if conflict {
|
|
| 523 |
+ return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name) |
|
| 524 |
+ } |
|
| 525 |
+ return v, nil |
|
| 526 |
+ } |
|
| 527 |
+ |
|
| 528 |
+ if s.hasRef(v.Name()) {
|
|
| 529 |
+ // Containers are referencing this volume but it doesn't seem to exist anywhere. |
|
| 530 |
+ // Return a conflict error here, the user can fix this with `docker volume rm -f` |
|
| 531 |
+ return nil, errors.Wrapf(errNameConflict, "found references to volume '%s' in driver '%s' but the volume was not found in the driver -- you may need to remove containers referencing this volume or force remove the volume to re-create it", name, vDriverName) |
|
| 532 |
+ } |
|
| 533 |
+ |
|
| 534 |
+ // doesn't exist, so purge it from the cache |
|
| 535 |
+ s.purge(ctx, name) |
|
| 536 |
+ return nil, nil |
|
| 537 |
+} |
|
| 538 |
+ |
|
| 539 |
+// volumeExists returns if the volume is still present in the driver. |
|
| 540 |
+// An error is returned if there was an issue communicating with the driver. |
|
| 541 |
+func volumeExists(ctx context.Context, store *drivers.Store, v volume.Volume) (bool, error) {
|
|
| 542 |
+ exists, err := lookupVolume(ctx, store, v.DriverName(), v.Name()) |
|
| 543 |
+ if err != nil {
|
|
| 544 |
+ return false, err |
|
| 545 |
+ } |
|
| 546 |
+ return exists != nil, nil |
|
| 547 |
+} |
|
| 548 |
+ |
|
| 549 |
+// create asks the given driver to create a volume with the name/opts. |
|
| 550 |
+// If a volume with the name is already known, it will ask the stored driver for the volume. |
|
| 551 |
+// If the passed in driver name does not match the driver name which is stored |
|
| 552 |
+// for the given volume name, an error is returned after checking if the reference is stale. |
|
| 553 |
+// If the reference is stale, it will be purged and this create can continue. |
|
| 554 |
+// It is expected that callers of this function hold any necessary locks. |
|
| 555 |
+func (s *VolumeStore) create(ctx context.Context, name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 556 |
+ // Validate the name in a platform-specific manner |
|
| 557 |
+ |
|
| 558 |
+ // volume name validation is specific to the host os and not on container image |
|
| 559 |
+ // windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS |
|
| 560 |
+ parser := volumemounts.NewParser(runtime.GOOS) |
|
| 561 |
+ err := parser.ValidateVolumeName(name) |
|
| 562 |
+ if err != nil {
|
|
| 563 |
+ return nil, err |
|
| 564 |
+ } |
|
| 565 |
+ |
|
| 566 |
+ v, err := s.checkConflict(ctx, name, driverName) |
|
| 567 |
+ if err != nil {
|
|
| 568 |
+ return nil, err |
|
| 569 |
+ } |
|
| 570 |
+ |
|
| 571 |
+ if v != nil {
|
|
| 572 |
+ // there is an existing volume, if we already have this stored locally, return it. |
|
| 573 |
+ // TODO: there could be some inconsistent details such as labels here |
|
| 574 |
+ if vv, _ := s.getNamed(v.Name()); vv != nil {
|
|
| 575 |
+ return vv, nil |
|
| 576 |
+ } |
|
| 577 |
+ } |
|
| 578 |
+ |
|
| 579 |
+ // Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name |
|
| 580 |
+ if driverName == "" {
|
|
| 581 |
+ v, _ = s.getVolume(ctx, name, "") |
|
| 582 |
+ if v != nil {
|
|
| 583 |
+ return v, nil |
|
| 584 |
+ } |
|
| 585 |
+ } |
|
| 586 |
+ |
|
| 587 |
+ if driverName == "" {
|
|
| 588 |
+ driverName = volume.DefaultDriverName |
|
| 589 |
+ } |
|
| 590 |
+ vd, err := s.drivers.CreateDriver(driverName) |
|
| 591 |
+ if err != nil {
|
|
| 592 |
+ return nil, &OpErr{Op: "create", Name: name, Err: err}
|
|
| 593 |
+ } |
|
| 594 |
+ |
|
| 595 |
+ logrus.Debugf("Registering new volume reference: driver %q, name %q", vd.Name(), name)
|
|
| 596 |
+ if v, _ = vd.Get(name); v == nil {
|
|
| 597 |
+ v, err = vd.Create(name, opts) |
|
| 598 |
+ if err != nil {
|
|
| 599 |
+ if _, err := s.drivers.ReleaseDriver(driverName); err != nil {
|
|
| 600 |
+ logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
|
|
| 601 |
+ } |
|
| 602 |
+ return nil, err |
|
| 603 |
+ } |
|
| 604 |
+ } |
|
| 605 |
+ |
|
| 606 |
+ s.globalLock.Lock() |
|
| 607 |
+ s.labels[name] = labels |
|
| 608 |
+ s.options[name] = opts |
|
| 609 |
+ s.refs[name] = make(map[string]struct{})
|
|
| 610 |
+ s.globalLock.Unlock() |
|
| 611 |
+ |
|
| 612 |
+ metadata := volumeMetadata{
|
|
| 613 |
+ Name: name, |
|
| 614 |
+ Driver: vd.Name(), |
|
| 615 |
+ Labels: labels, |
|
| 616 |
+ Options: opts, |
|
| 617 |
+ } |
|
| 618 |
+ |
|
| 619 |
+ if err := s.setMeta(name, metadata); err != nil {
|
|
| 620 |
+ return nil, err |
|
| 621 |
+ } |
|
| 622 |
+ return volumeWrapper{v, labels, vd.Scope(), opts}, nil
|
|
| 623 |
+} |
|
| 624 |
+ |
|
| 625 |
+// Get looks if a volume with the given name exists and returns it if so |
|
| 626 |
+func (s *VolumeStore) Get(ctx context.Context, name string, getOptions ...opts.GetOption) (volume.Volume, error) {
|
|
| 627 |
+ var cfg opts.GetConfig |
|
| 628 |
+ for _, o := range getOptions {
|
|
| 629 |
+ o(&cfg) |
|
| 630 |
+ } |
|
| 631 |
+ name = normalizeVolumeName(name) |
|
| 632 |
+ s.locks.Lock(name) |
|
| 633 |
+ defer s.locks.Unlock(name) |
|
| 634 |
+ |
|
| 635 |
+ v, err := s.getVolume(ctx, name, cfg.Driver) |
|
| 636 |
+ if err != nil {
|
|
| 637 |
+ return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 638 |
+ } |
|
| 639 |
+ if cfg.Driver != "" && v.DriverName() != cfg.Driver {
|
|
| 640 |
+ return nil, &OpErr{Name: name, Op: "get", Err: errdefs.Conflict(errors.New("found volume driver does not match passed in driver"))}
|
|
| 641 |
+ } |
|
| 642 |
+ s.setNamed(v, cfg.Reference) |
|
| 643 |
+ return v, nil |
|
| 644 |
+} |
|
| 645 |
+ |
|
| 646 |
+// getVolume requests the volume, if the driver info is stored it just accesses that driver, |
|
| 647 |
+// if the driver is unknown it probes all drivers until it finds the first volume with that name. |
|
| 648 |
+// it is expected that callers of this function hold any necessary locks |
|
| 649 |
+func (s *VolumeStore) getVolume(ctx context.Context, name, driverName string) (volume.Volume, error) {
|
|
| 650 |
+ var meta volumeMetadata |
|
| 651 |
+ meta, err := s.getMeta(name) |
|
| 652 |
+ if err != nil {
|
|
| 653 |
+ return nil, err |
|
| 654 |
+ } |
|
| 655 |
+ |
|
| 656 |
+ if driverName != "" {
|
|
| 657 |
+ if meta.Driver == "" {
|
|
| 658 |
+ meta.Driver = driverName |
|
| 659 |
+ } |
|
| 660 |
+ if driverName != meta.Driver {
|
|
| 661 |
+ return nil, errdefs.Conflict(errors.New("provided volume driver does not match stored driver"))
|
|
| 662 |
+ } |
|
| 663 |
+ } |
|
| 664 |
+ |
|
| 665 |
+ if driverName == "" {
|
|
| 666 |
+ driverName = meta.Driver |
|
| 667 |
+ } |
|
| 668 |
+ if driverName == "" {
|
|
| 669 |
+ s.globalLock.RLock() |
|
| 670 |
+ select {
|
|
| 671 |
+ case <-ctx.Done(): |
|
| 672 |
+ s.globalLock.RUnlock() |
|
| 673 |
+ return nil, ctx.Err() |
|
| 674 |
+ default: |
|
| 675 |
+ } |
|
| 676 |
+ v, exists := s.names[name] |
|
| 677 |
+ s.globalLock.RUnlock() |
|
| 678 |
+ if exists {
|
|
| 679 |
+ meta.Driver = v.DriverName() |
|
| 680 |
+ if err := s.setMeta(name, meta); err != nil {
|
|
| 681 |
+ return nil, err |
|
| 682 |
+ } |
|
| 683 |
+ } |
|
| 684 |
+ } |
|
| 685 |
+ |
|
| 686 |
+ if meta.Driver != "" {
|
|
| 687 |
+ vol, err := lookupVolume(ctx, s.drivers, meta.Driver, name) |
|
| 688 |
+ if err != nil {
|
|
| 689 |
+ return nil, err |
|
| 690 |
+ } |
|
| 691 |
+ if vol == nil {
|
|
| 692 |
+ s.purge(ctx, name) |
|
| 693 |
+ return nil, errNoSuchVolume |
|
| 694 |
+ } |
|
| 695 |
+ |
|
| 696 |
+ var scope string |
|
| 697 |
+ vd, err := s.drivers.GetDriver(meta.Driver) |
|
| 698 |
+ if err == nil {
|
|
| 699 |
+ scope = vd.Scope() |
|
| 700 |
+ } |
|
| 701 |
+ return volumeWrapper{vol, meta.Labels, scope, meta.Options}, nil
|
|
| 702 |
+ } |
|
| 703 |
+ |
|
| 704 |
+ logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
|
| 705 |
+ drivers, err := s.drivers.GetAllDrivers() |
|
| 706 |
+ if err != nil {
|
|
| 707 |
+ return nil, err |
|
| 708 |
+ } |
|
| 709 |
+ |
|
| 710 |
+ for _, d := range drivers {
|
|
| 711 |
+ select {
|
|
| 712 |
+ case <-ctx.Done(): |
|
| 713 |
+ return nil, ctx.Err() |
|
| 714 |
+ default: |
|
| 715 |
+ } |
|
| 716 |
+ v, err := d.Get(name) |
|
| 717 |
+ if err != nil || v == nil {
|
|
| 718 |
+ continue |
|
| 719 |
+ } |
|
| 720 |
+ meta.Driver = v.DriverName() |
|
| 721 |
+ if err := s.setMeta(name, meta); err != nil {
|
|
| 722 |
+ return nil, err |
|
| 723 |
+ } |
|
| 724 |
+ return volumeWrapper{v, meta.Labels, d.Scope(), meta.Options}, nil
|
|
| 725 |
+ } |
|
| 726 |
+ return nil, errNoSuchVolume |
|
| 727 |
+} |
|
| 728 |
+ |
|
| 729 |
+// lookupVolume gets the specified volume from the specified driver. |
|
| 730 |
+// This will only return errors related to communications with the driver. |
|
| 731 |
+// If the driver returns an error that is not communication related the |
|
| 732 |
+// error is logged but not returned. |
|
| 733 |
+// If the volume is not found it will return `nil, nil`` |
|
| 734 |
+// TODO(@cpuguy83): plumb through the context to lower level components |
|
| 735 |
+func lookupVolume(ctx context.Context, store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
|
|
| 736 |
+ if driverName == "" {
|
|
| 737 |
+ driverName = volume.DefaultDriverName |
|
| 738 |
+ } |
|
| 739 |
+ vd, err := store.GetDriver(driverName) |
|
| 740 |
+ if err != nil {
|
|
| 741 |
+ return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName) |
|
| 742 |
+ } |
|
| 743 |
+ v, err := vd.Get(volumeName) |
|
| 744 |
+ if err != nil {
|
|
| 745 |
+ err = errors.Cause(err) |
|
| 746 |
+ if _, ok := err.(net.Error); ok {
|
|
| 747 |
+ if v != nil {
|
|
| 748 |
+ volumeName = v.Name() |
|
| 749 |
+ driverName = v.DriverName() |
|
| 750 |
+ } |
|
| 751 |
+ return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName) |
|
| 752 |
+ } |
|
| 753 |
+ |
|
| 754 |
+ // At this point, the error could be anything from the driver, such as "no such volume" |
|
| 755 |
+ // Let's not check an error here, and instead check if the driver returned a volume |
|
| 756 |
+ logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Debug("Error while looking up volume")
|
|
| 757 |
+ } |
|
| 758 |
+ return v, nil |
|
| 759 |
+} |
|
| 760 |
+ |
|
| 761 |
+// Remove removes the requested volume. A volume is not removed if it has any refs |
|
| 762 |
+func (s *VolumeStore) Remove(ctx context.Context, v volume.Volume, rmOpts ...opts.RemoveOption) error {
|
|
| 763 |
+ var cfg opts.RemoveConfig |
|
| 764 |
+ for _, o := range rmOpts {
|
|
| 765 |
+ o(&cfg) |
|
| 766 |
+ } |
|
| 767 |
+ |
|
| 768 |
+ name := v.Name() |
|
| 769 |
+ s.locks.Lock(name) |
|
| 770 |
+ defer s.locks.Unlock(name) |
|
| 771 |
+ |
|
| 772 |
+ select {
|
|
| 773 |
+ case <-ctx.Done(): |
|
| 774 |
+ return ctx.Err() |
|
| 775 |
+ default: |
|
| 776 |
+ } |
|
| 777 |
+ |
|
| 778 |
+ if s.hasRef(name) {
|
|
| 779 |
+ return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove", Refs: s.getRefs(name)}
|
|
| 780 |
+ } |
|
| 781 |
+ |
|
| 782 |
+ v, err := s.getVolume(ctx, name, v.DriverName()) |
|
| 783 |
+ if err != nil {
|
|
| 784 |
+ return err |
|
| 785 |
+ } |
|
| 786 |
+ |
|
| 787 |
+ vd, err := s.drivers.GetDriver(v.DriverName()) |
|
| 788 |
+ if err != nil {
|
|
| 789 |
+ return &OpErr{Err: err, Name: v.DriverName(), Op: "remove"}
|
|
| 790 |
+ } |
|
| 791 |
+ |
|
| 792 |
+ logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
|
| 793 |
+ vol := unwrapVolume(v) |
|
| 794 |
+ |
|
| 795 |
+ err = vd.Remove(vol) |
|
| 796 |
+ if err != nil {
|
|
| 797 |
+ err = &OpErr{Err: err, Name: name, Op: "remove"}
|
|
| 798 |
+ } |
|
| 799 |
+ |
|
| 800 |
+ if err == nil || cfg.PurgeOnError {
|
|
| 801 |
+ if e := s.purge(ctx, name); e != nil && err == nil {
|
|
| 802 |
+ err = e |
|
| 803 |
+ } |
|
| 804 |
+ } |
|
| 805 |
+ return err |
|
| 806 |
+} |
|
| 807 |
+ |
|
| 808 |
+// Release releases the specified reference to the volume |
|
| 809 |
+func (s *VolumeStore) Release(ctx context.Context, name string, ref string) error {
|
|
| 810 |
+ s.locks.Lock(name) |
|
| 811 |
+ defer s.locks.Unlock(name) |
|
| 812 |
+ select {
|
|
| 813 |
+ case <-ctx.Done(): |
|
| 814 |
+ return ctx.Err() |
|
| 815 |
+ default: |
|
| 816 |
+ } |
|
| 817 |
+ |
|
| 818 |
+ s.globalLock.Lock() |
|
| 819 |
+ defer s.globalLock.Unlock() |
|
| 820 |
+ |
|
| 821 |
+ select {
|
|
| 822 |
+ case <-ctx.Done(): |
|
| 823 |
+ return ctx.Err() |
|
| 824 |
+ default: |
|
| 825 |
+ } |
|
| 826 |
+ |
|
| 827 |
+ if s.refs[name] != nil {
|
|
| 828 |
+ delete(s.refs[name], ref) |
|
| 829 |
+ } |
|
| 830 |
+ return nil |
|
| 831 |
+} |
|
| 832 |
+ |
|
| 833 |
+// CountReferences gives a count of all references for a given volume. |
|
| 834 |
+func (s *VolumeStore) CountReferences(v volume.Volume) int {
|
|
| 835 |
+ name := normalizeVolumeName(v.Name()) |
|
| 836 |
+ |
|
| 837 |
+ s.locks.Lock(name) |
|
| 838 |
+ defer s.locks.Unlock(name) |
|
| 839 |
+ s.globalLock.Lock() |
|
| 840 |
+ defer s.globalLock.Unlock() |
|
| 841 |
+ |
|
| 842 |
+ return len(s.refs[name]) |
|
| 843 |
+} |
|
| 844 |
+ |
|
| 845 |
+func unwrapVolume(v volume.Volume) volume.Volume {
|
|
| 846 |
+ if vol, ok := v.(volumeWrapper); ok {
|
|
| 847 |
+ return vol.Volume |
|
| 848 |
+ } |
|
| 849 |
+ |
|
| 850 |
+ return v |
|
| 851 |
+} |
|
| 852 |
+ |
|
| 853 |
+// Shutdown releases all resources used by the volume store |
|
| 854 |
+// It does not make any changes to volumes, drivers, etc. |
|
| 855 |
+func (s *VolumeStore) Shutdown() error {
|
|
| 856 |
+ return s.db.Close() |
|
| 857 |
+} |
| 0 | 858 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,421 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "errors" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "net" |
|
| 8 |
+ "os" |
|
| 9 |
+ "strings" |
|
| 10 |
+ "testing" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/volume" |
|
| 13 |
+ volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 14 |
+ "github.com/docker/docker/volume/service/opts" |
|
| 15 |
+ volumetestutils "github.com/docker/docker/volume/testutils" |
|
| 16 |
+ "github.com/google/go-cmp/cmp" |
|
| 17 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 18 |
+ is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+func TestCreate(t *testing.T) {
|
|
| 22 |
+ t.Parallel() |
|
| 23 |
+ |
|
| 24 |
+ s, cleanup := setupTest(t) |
|
| 25 |
+ defer cleanup() |
|
| 26 |
+ s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 27 |
+ |
|
| 28 |
+ ctx := context.Background() |
|
| 29 |
+ v, err := s.Create(ctx, "fake1", "fake") |
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ t.Fatal(err) |
|
| 32 |
+ } |
|
| 33 |
+ if v.Name() != "fake1" {
|
|
| 34 |
+ t.Fatalf("Expected fake1 volume, got %v", v)
|
|
| 35 |
+ } |
|
| 36 |
+ if l, _, _ := s.Find(ctx, nil); len(l) != 1 {
|
|
| 37 |
+ t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ if _, err := s.Create(ctx, "none", "none"); err == nil {
|
|
| 41 |
+ t.Fatalf("Expected unknown driver error, got nil")
|
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ _, err = s.Create(ctx, "fakeerror", "fake", opts.WithCreateOptions(map[string]string{"error": "create error"}))
|
|
| 45 |
+ expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
|
|
| 46 |
+ if err != nil && err.Error() != expected.Error() {
|
|
| 47 |
+ t.Fatalf("Expected create fakeError: create error, got %v", err)
|
|
| 48 |
+ } |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+func TestRemove(t *testing.T) {
|
|
| 52 |
+ t.Parallel() |
|
| 53 |
+ |
|
| 54 |
+ s, cleanup := setupTest(t) |
|
| 55 |
+ defer cleanup() |
|
| 56 |
+ |
|
| 57 |
+ s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 58 |
+ s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
|
| 59 |
+ |
|
| 60 |
+ ctx := context.Background() |
|
| 61 |
+ |
|
| 62 |
+ // doing string compare here since this error comes directly from the driver |
|
| 63 |
+ expected := "no such volume" |
|
| 64 |
+ var v volume.Volume = volumetestutils.NoopVolume{}
|
|
| 65 |
+ if err := s.Remove(ctx, v); err == nil || !strings.Contains(err.Error(), expected) {
|
|
| 66 |
+ t.Fatalf("Expected error %q, got %v", expected, err)
|
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("fake"))
|
|
| 70 |
+ if err != nil {
|
|
| 71 |
+ t.Fatal(err) |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ if err := s.Remove(ctx, v); !IsInUse(err) {
|
|
| 75 |
+ t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
|
| 76 |
+ } |
|
| 77 |
+ s.Release(ctx, v.Name(), "fake") |
|
| 78 |
+ if err := s.Remove(ctx, v); err != nil {
|
|
| 79 |
+ t.Fatal(err) |
|
| 80 |
+ } |
|
| 81 |
+ if l, _, _ := s.Find(ctx, nil); len(l) != 0 {
|
|
| 82 |
+ t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
|
|
| 83 |
+ } |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func TestList(t *testing.T) {
|
|
| 87 |
+ t.Parallel() |
|
| 88 |
+ |
|
| 89 |
+ dir, err := ioutil.TempDir("", "test-list")
|
|
| 90 |
+ assert.NilError(t, err) |
|
| 91 |
+ defer os.RemoveAll(dir) |
|
| 92 |
+ |
|
| 93 |
+ drivers := volumedrivers.NewStore(nil) |
|
| 94 |
+ drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 95 |
+ drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
|
|
| 96 |
+ |
|
| 97 |
+ s, err := NewStore(dir, drivers) |
|
| 98 |
+ assert.NilError(t, err) |
|
| 99 |
+ |
|
| 100 |
+ ctx := context.Background() |
|
| 101 |
+ if _, err := s.Create(ctx, "test", "fake"); err != nil {
|
|
| 102 |
+ t.Fatal(err) |
|
| 103 |
+ } |
|
| 104 |
+ if _, err := s.Create(ctx, "test2", "fake2"); err != nil {
|
|
| 105 |
+ t.Fatal(err) |
|
| 106 |
+ } |
|
| 107 |
+ |
|
| 108 |
+ ls, _, err := s.Find(ctx, nil) |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ t.Fatal(err) |
|
| 111 |
+ } |
|
| 112 |
+ if len(ls) != 2 {
|
|
| 113 |
+ t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
|
| 114 |
+ } |
|
| 115 |
+ if err := s.Shutdown(); err != nil {
|
|
| 116 |
+ t.Fatal(err) |
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ // and again with a new store |
|
| 120 |
+ s, err = NewStore(dir, drivers) |
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ t.Fatal(err) |
|
| 123 |
+ } |
|
| 124 |
+ ls, _, err = s.Find(ctx, nil) |
|
| 125 |
+ if err != nil {
|
|
| 126 |
+ t.Fatal(err) |
|
| 127 |
+ } |
|
| 128 |
+ if len(ls) != 2 {
|
|
| 129 |
+ t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
|
| 130 |
+ } |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+func TestFindByDriver(t *testing.T) {
|
|
| 134 |
+ t.Parallel() |
|
| 135 |
+ s, cleanup := setupTest(t) |
|
| 136 |
+ defer cleanup() |
|
| 137 |
+ |
|
| 138 |
+ assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake"))
|
|
| 139 |
+ assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop"))
|
|
| 140 |
+ |
|
| 141 |
+ ctx := context.Background() |
|
| 142 |
+ _, err := s.Create(ctx, "fake1", "fake") |
|
| 143 |
+ assert.NilError(t, err) |
|
| 144 |
+ |
|
| 145 |
+ _, err = s.Create(ctx, "fake2", "fake") |
|
| 146 |
+ assert.NilError(t, err) |
|
| 147 |
+ |
|
| 148 |
+ _, err = s.Create(ctx, "fake3", "noop") |
|
| 149 |
+ assert.NilError(t, err) |
|
| 150 |
+ |
|
| 151 |
+ l, _, err := s.Find(ctx, ByDriver("fake"))
|
|
| 152 |
+ assert.NilError(t, err) |
|
| 153 |
+ assert.Equal(t, len(l), 2) |
|
| 154 |
+ |
|
| 155 |
+ l, _, err = s.Find(ctx, ByDriver("noop"))
|
|
| 156 |
+ assert.NilError(t, err) |
|
| 157 |
+ assert.Equal(t, len(l), 1) |
|
| 158 |
+ |
|
| 159 |
+ l, _, err = s.Find(ctx, ByDriver("nosuchdriver"))
|
|
| 160 |
+ assert.NilError(t, err) |
|
| 161 |
+ assert.Equal(t, len(l), 0) |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 164 |
+func TestFindByReferenced(t *testing.T) {
|
|
| 165 |
+ t.Parallel() |
|
| 166 |
+ s, cleanup := setupTest(t) |
|
| 167 |
+ defer cleanup() |
|
| 168 |
+ |
|
| 169 |
+ s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 170 |
+ s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
|
| 171 |
+ |
|
| 172 |
+ ctx := context.Background() |
|
| 173 |
+ if _, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference")); err != nil {
|
|
| 174 |
+ t.Fatal(err) |
|
| 175 |
+ } |
|
| 176 |
+ if _, err := s.Create(ctx, "fake2", "fake"); err != nil {
|
|
| 177 |
+ t.Fatal(err) |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ dangling, _, err := s.Find(ctx, ByReferenced(false)) |
|
| 181 |
+ assert.Assert(t, err) |
|
| 182 |
+ assert.Assert(t, len(dangling) == 1) |
|
| 183 |
+ assert.Check(t, dangling[0].Name() == "fake2") |
|
| 184 |
+ |
|
| 185 |
+ used, _, err := s.Find(ctx, ByReferenced(true)) |
|
| 186 |
+ assert.Assert(t, err) |
|
| 187 |
+ assert.Assert(t, len(used) == 1) |
|
| 188 |
+ assert.Check(t, used[0].Name() == "fake1") |
|
| 189 |
+} |
|
| 190 |
+ |
|
| 191 |
+func TestDerefMultipleOfSameRef(t *testing.T) {
|
|
| 192 |
+ t.Parallel() |
|
| 193 |
+ s, cleanup := setupTest(t) |
|
| 194 |
+ defer cleanup() |
|
| 195 |
+ s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 196 |
+ |
|
| 197 |
+ ctx := context.Background() |
|
| 198 |
+ v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference"))
|
|
| 199 |
+ if err != nil {
|
|
| 200 |
+ t.Fatal(err) |
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 203 |
+ if _, err := s.Get(ctx, "fake1", opts.WithGetDriver("fake"), opts.WithGetReference("volReference")); err != nil {
|
|
| 204 |
+ t.Fatal(err) |
|
| 205 |
+ } |
|
| 206 |
+ |
|
| 207 |
+ s.Release(ctx, v.Name(), "volReference") |
|
| 208 |
+ if err := s.Remove(ctx, v); err != nil {
|
|
| 209 |
+ t.Fatal(err) |
|
| 210 |
+ } |
|
| 211 |
+} |
|
| 212 |
+ |
|
| 213 |
+func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
|
|
| 214 |
+ t.Parallel() |
|
| 215 |
+ s, cleanup := setupTest(t) |
|
| 216 |
+ defer cleanup() |
|
| 217 |
+ |
|
| 218 |
+ vd := volumetestutils.NewFakeDriver("fake")
|
|
| 219 |
+ s.drivers.Register(vd, "fake") |
|
| 220 |
+ |
|
| 221 |
+ // Create a volume in the driver directly |
|
| 222 |
+ if _, err := vd.Create("foo", nil); err != nil {
|
|
| 223 |
+ t.Fatal(err) |
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ ctx := context.Background() |
|
| 227 |
+ v, err := s.Create(ctx, "foo", "fake", opts.WithCreateLabels(map[string]string{"hello": "world"}))
|
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ t.Fatal(err) |
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 232 |
+ switch dv := v.(type) {
|
|
| 233 |
+ case volume.DetailedVolume: |
|
| 234 |
+ if dv.Labels()["hello"] != "world" {
|
|
| 235 |
+ t.Fatalf("labels don't match")
|
|
| 236 |
+ } |
|
| 237 |
+ default: |
|
| 238 |
+ t.Fatalf("got unexpected type: %T", v)
|
|
| 239 |
+ } |
|
| 240 |
+} |
|
| 241 |
+ |
|
| 242 |
+func TestDefererencePluginOnCreateError(t *testing.T) {
|
|
| 243 |
+ t.Parallel() |
|
| 244 |
+ |
|
| 245 |
+ var ( |
|
| 246 |
+ l net.Listener |
|
| 247 |
+ err error |
|
| 248 |
+ ) |
|
| 249 |
+ |
|
| 250 |
+ for i := 32768; l == nil && i < 40000; i++ {
|
|
| 251 |
+ l, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", i))
|
|
| 252 |
+ } |
|
| 253 |
+ if l == nil {
|
|
| 254 |
+ t.Fatalf("could not create listener: %v", err)
|
|
| 255 |
+ } |
|
| 256 |
+ defer l.Close() |
|
| 257 |
+ |
|
| 258 |
+ s, cleanup := setupTest(t) |
|
| 259 |
+ defer cleanup() |
|
| 260 |
+ |
|
| 261 |
+ d := volumetestutils.NewFakeDriver("TestDefererencePluginOnCreateError")
|
|
| 262 |
+ p, err := volumetestutils.MakeFakePlugin(d, l) |
|
| 263 |
+ if err != nil {
|
|
| 264 |
+ t.Fatal(err) |
|
| 265 |
+ } |
|
| 266 |
+ |
|
| 267 |
+ pg := volumetestutils.NewFakePluginGetter(p) |
|
| 268 |
+ s.drivers = volumedrivers.NewStore(pg) |
|
| 269 |
+ |
|
| 270 |
+ ctx := context.Background() |
|
| 271 |
+ // create a good volume so we have a plugin reference |
|
| 272 |
+ _, err = s.Create(ctx, "fake1", d.Name()) |
|
| 273 |
+ if err != nil {
|
|
| 274 |
+ t.Fatal(err) |
|
| 275 |
+ } |
|
| 276 |
+ |
|
| 277 |
+ // Now create another one expecting an error |
|
| 278 |
+ _, err = s.Create(ctx, "fake2", d.Name(), opts.WithCreateOptions(map[string]string{"error": "some error"}))
|
|
| 279 |
+ if err == nil || !strings.Contains(err.Error(), "some error") {
|
|
| 280 |
+ t.Fatalf("expected an error on create: %v", err)
|
|
| 281 |
+ } |
|
| 282 |
+ |
|
| 283 |
+ // There should be only 1 plugin reference |
|
| 284 |
+ if refs := volumetestutils.FakeRefs(p); refs != 1 {
|
|
| 285 |
+ t.Fatalf("expected 1 plugin reference, got: %d", refs)
|
|
| 286 |
+ } |
|
| 287 |
+} |
|
| 288 |
+ |
|
| 289 |
+func TestRefDerefRemove(t *testing.T) {
|
|
| 290 |
+ t.Parallel() |
|
| 291 |
+ |
|
| 292 |
+ driverName := "test-ref-deref-remove" |
|
| 293 |
+ s, cleanup := setupTest(t) |
|
| 294 |
+ defer cleanup() |
|
| 295 |
+ s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 296 |
+ |
|
| 297 |
+ ctx := context.Background() |
|
| 298 |
+ v, err := s.Create(ctx, "test", driverName, opts.WithCreateReference("test-ref"))
|
|
| 299 |
+ assert.NilError(t, err) |
|
| 300 |
+ |
|
| 301 |
+ err = s.Remove(ctx, v) |
|
| 302 |
+ assert.Assert(t, is.ErrorContains(err, "")) |
|
| 303 |
+ assert.Equal(t, errVolumeInUse, err.(*OpErr).Err) |
|
| 304 |
+ |
|
| 305 |
+ s.Release(ctx, v.Name(), "test-ref") |
|
| 306 |
+ err = s.Remove(ctx, v) |
|
| 307 |
+ assert.NilError(t, err) |
|
| 308 |
+} |
|
| 309 |
+ |
|
| 310 |
+func TestGet(t *testing.T) {
|
|
| 311 |
+ t.Parallel() |
|
| 312 |
+ |
|
| 313 |
+ driverName := "test-get" |
|
| 314 |
+ s, cleanup := setupTest(t) |
|
| 315 |
+ defer cleanup() |
|
| 316 |
+ s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 317 |
+ |
|
| 318 |
+ ctx := context.Background() |
|
| 319 |
+ _, err := s.Get(ctx, "not-exist") |
|
| 320 |
+ assert.Assert(t, is.ErrorContains(err, "")) |
|
| 321 |
+ assert.Equal(t, errNoSuchVolume, err.(*OpErr).Err) |
|
| 322 |
+ |
|
| 323 |
+ v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
|
|
| 324 |
+ assert.NilError(t, err) |
|
| 325 |
+ |
|
| 326 |
+ v2, err := s.Get(ctx, "test") |
|
| 327 |
+ assert.NilError(t, err) |
|
| 328 |
+ assert.DeepEqual(t, v1, v2, cmpVolume) |
|
| 329 |
+ |
|
| 330 |
+ dv := v2.(volume.DetailedVolume) |
|
| 331 |
+ assert.Equal(t, "1", dv.Labels()["a"]) |
|
| 332 |
+ |
|
| 333 |
+ err = s.Remove(ctx, v1) |
|
| 334 |
+ assert.NilError(t, err) |
|
| 335 |
+} |
|
| 336 |
+ |
|
| 337 |
+func TestGetWithReference(t *testing.T) {
|
|
| 338 |
+ t.Parallel() |
|
| 339 |
+ |
|
| 340 |
+ driverName := "test-get-with-ref" |
|
| 341 |
+ s, cleanup := setupTest(t) |
|
| 342 |
+ defer cleanup() |
|
| 343 |
+ s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 344 |
+ |
|
| 345 |
+ ctx := context.Background() |
|
| 346 |
+ _, err := s.Get(ctx, "not-exist", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
|
|
| 347 |
+ assert.Assert(t, is.ErrorContains(err, "")) |
|
| 348 |
+ |
|
| 349 |
+ v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
|
|
| 350 |
+ assert.NilError(t, err) |
|
| 351 |
+ |
|
| 352 |
+ v2, err := s.Get(ctx, "test", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
|
|
| 353 |
+ assert.NilError(t, err) |
|
| 354 |
+ assert.DeepEqual(t, v1, v2, cmpVolume) |
|
| 355 |
+ |
|
| 356 |
+ err = s.Remove(ctx, v2) |
|
| 357 |
+ assert.Assert(t, is.ErrorContains(err, "")) |
|
| 358 |
+ assert.Equal(t, errVolumeInUse, err.(*OpErr).Err) |
|
| 359 |
+ |
|
| 360 |
+ s.Release(ctx, v2.Name(), "test-ref") |
|
| 361 |
+ err = s.Remove(ctx, v2) |
|
| 362 |
+ assert.NilError(t, err) |
|
| 363 |
+} |
|
| 364 |
+ |
|
| 365 |
+var cmpVolume = cmp.AllowUnexported(volumetestutils.FakeVolume{}, volumeWrapper{})
|
|
| 366 |
+ |
|
| 367 |
+func setupTest(t *testing.T) (*VolumeStore, func()) {
|
|
| 368 |
+ t.Helper() |
|
| 369 |
+ |
|
| 370 |
+ dirName := strings.Replace(t.Name(), string(os.PathSeparator), "_", -1) |
|
| 371 |
+ dir, err := ioutil.TempDir("", dirName)
|
|
| 372 |
+ assert.NilError(t, err) |
|
| 373 |
+ |
|
| 374 |
+ cleanup := func() {
|
|
| 375 |
+ t.Helper() |
|
| 376 |
+ err := os.RemoveAll(dir) |
|
| 377 |
+ assert.Check(t, err) |
|
| 378 |
+ } |
|
| 379 |
+ |
|
| 380 |
+ s, err := NewStore(dir, volumedrivers.NewStore(nil)) |
|
| 381 |
+ assert.Check(t, err) |
|
| 382 |
+ return s, func() {
|
|
| 383 |
+ s.Shutdown() |
|
| 384 |
+ cleanup() |
|
| 385 |
+ } |
|
| 386 |
+} |
|
| 387 |
+ |
|
| 388 |
+func TestFilterFunc(t *testing.T) {
|
|
| 389 |
+ testDriver := volumetestutils.NewFakeDriver("test")
|
|
| 390 |
+ testVolume, err := testDriver.Create("test", nil)
|
|
| 391 |
+ assert.NilError(t, err) |
|
| 392 |
+ testVolume2, err := testDriver.Create("test2", nil)
|
|
| 393 |
+ assert.NilError(t, err) |
|
| 394 |
+ testVolume3, err := testDriver.Create("test3", nil)
|
|
| 395 |
+ assert.NilError(t, err) |
|
| 396 |
+ |
|
| 397 |
+ for _, test := range []struct {
|
|
| 398 |
+ vols []volume.Volume |
|
| 399 |
+ fn filterFunc |
|
| 400 |
+ desc string |
|
| 401 |
+ expect []volume.Volume |
|
| 402 |
+ }{
|
|
| 403 |
+ {desc: "test nil list", vols: nil, expect: nil, fn: func(volume.Volume) bool { return true }},
|
|
| 404 |
+ {desc: "test empty list", vols: []volume.Volume{}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return true }},
|
|
| 405 |
+ {desc: "test filter non-empty to empty", vols: []volume.Volume{testVolume}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return false }},
|
|
| 406 |
+ {desc: "test nothing to fitler non-empty list", vols: []volume.Volume{testVolume}, expect: []volume.Volume{testVolume}, fn: func(volume.Volume) bool { return true }},
|
|
| 407 |
+ {desc: "test filter some", vols: []volume.Volume{testVolume, testVolume2}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() == testVolume.Name() }},
|
|
| 408 |
+ {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() }},
|
|
| 409 |
+ {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() }},
|
|
| 410 |
+ {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() }},
|
|
| 411 |
+ } {
|
|
| 412 |
+ t.Run(test.desc, func(t *testing.T) {
|
|
| 413 |
+ test := test |
|
| 414 |
+ t.Parallel() |
|
| 415 |
+ |
|
| 416 |
+ filter(&test.vols, test.fn) |
|
| 417 |
+ assert.DeepEqual(t, test.vols, test.expect, cmpVolume) |
|
| 418 |
+ }) |
|
| 419 |
+ } |
|
| 420 |
+} |
| 0 | 421 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+// +build linux freebsd darwin |
|
| 1 |
+ |
|
| 2 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 3 |
+ |
|
| 4 |
+// normalizeVolumeName is a platform specific function to normalize the name |
|
| 5 |
+// of a volume. This is a no-op on Unix-like platforms |
|
| 6 |
+func normalizeVolumeName(name string) string {
|
|
| 7 |
+ return name |
|
| 8 |
+} |
| 0 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+package service // import "github.com/docker/docker/volume/service" |
|
| 1 |
+ |
|
| 2 |
+import "strings" |
|
| 3 |
+ |
|
| 4 |
+// normalizeVolumeName is a platform specific function to normalize the name |
|
| 5 |
+// of a volume. On Windows, as NTFS is case insensitive, under |
|
| 6 |
+// c:\ProgramData\Docker\Volumes\, the folders John and john would be synonymous. |
|
| 7 |
+// Hence we can't allow the volume "John" and "john" to be created as separate |
|
| 8 |
+// volumes. |
|
| 9 |
+func normalizeVolumeName(name string) string {
|
|
| 10 |
+ return strings.ToLower(name) |
|
| 11 |
+} |
| 0 | 12 |
deleted file mode 100644 |
| ... | ... |
@@ -1,95 +0,0 @@ |
| 1 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/json" |
|
| 5 |
- |
|
| 6 |
- "github.com/boltdb/bolt" |
|
| 7 |
- "github.com/docker/docker/errdefs" |
|
| 8 |
- "github.com/pkg/errors" |
|
| 9 |
- "github.com/sirupsen/logrus" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-var volumeBucketName = []byte("volumes")
|
|
| 13 |
- |
|
| 14 |
-type volumeMetadata struct {
|
|
| 15 |
- Name string |
|
| 16 |
- Driver string |
|
| 17 |
- Labels map[string]string |
|
| 18 |
- Options map[string]string |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-func (s *VolumeStore) setMeta(name string, meta volumeMetadata) error {
|
|
| 22 |
- return s.db.Update(func(tx *bolt.Tx) error {
|
|
| 23 |
- return setMeta(tx, name, meta) |
|
| 24 |
- }) |
|
| 25 |
-} |
|
| 26 |
- |
|
| 27 |
-func setMeta(tx *bolt.Tx, name string, meta volumeMetadata) error {
|
|
| 28 |
- metaJSON, err := json.Marshal(meta) |
|
| 29 |
- if err != nil {
|
|
| 30 |
- return err |
|
| 31 |
- } |
|
| 32 |
- b, err := tx.CreateBucketIfNotExists(volumeBucketName) |
|
| 33 |
- if err != nil {
|
|
| 34 |
- return errors.Wrap(err, "error creating volume bucket") |
|
| 35 |
- } |
|
| 36 |
- return errors.Wrap(b.Put([]byte(name), metaJSON), "error setting volume metadata") |
|
| 37 |
-} |
|
| 38 |
- |
|
| 39 |
-func (s *VolumeStore) getMeta(name string) (volumeMetadata, error) {
|
|
| 40 |
- var meta volumeMetadata |
|
| 41 |
- err := s.db.View(func(tx *bolt.Tx) error {
|
|
| 42 |
- return getMeta(tx, name, &meta) |
|
| 43 |
- }) |
|
| 44 |
- return meta, err |
|
| 45 |
-} |
|
| 46 |
- |
|
| 47 |
-func getMeta(tx *bolt.Tx, name string, meta *volumeMetadata) error {
|
|
| 48 |
- b := tx.Bucket(volumeBucketName) |
|
| 49 |
- if b == nil {
|
|
| 50 |
- return errdefs.NotFound(errors.New("volume bucket does not exist"))
|
|
| 51 |
- } |
|
| 52 |
- val := b.Get([]byte(name)) |
|
| 53 |
- if len(val) == 0 {
|
|
| 54 |
- return nil |
|
| 55 |
- } |
|
| 56 |
- if err := json.Unmarshal(val, meta); err != nil {
|
|
| 57 |
- return errors.Wrap(err, "error unmarshaling volume metadata") |
|
| 58 |
- } |
|
| 59 |
- return nil |
|
| 60 |
-} |
|
| 61 |
- |
|
| 62 |
-func (s *VolumeStore) removeMeta(name string) error {
|
|
| 63 |
- return s.db.Update(func(tx *bolt.Tx) error {
|
|
| 64 |
- return removeMeta(tx, name) |
|
| 65 |
- }) |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-func removeMeta(tx *bolt.Tx, name string) error {
|
|
| 69 |
- b := tx.Bucket(volumeBucketName) |
|
| 70 |
- return errors.Wrap(b.Delete([]byte(name)), "error removing volume metadata") |
|
| 71 |
-} |
|
| 72 |
- |
|
| 73 |
-// listMeta is used during restore to get the list of volume metadata |
|
| 74 |
-// from the on-disk database. |
|
| 75 |
-// Any errors that occur are only logged. |
|
| 76 |
-func listMeta(tx *bolt.Tx) []volumeMetadata {
|
|
| 77 |
- var ls []volumeMetadata |
|
| 78 |
- b := tx.Bucket(volumeBucketName) |
|
| 79 |
- b.ForEach(func(k, v []byte) error {
|
|
| 80 |
- if len(v) == 0 {
|
|
| 81 |
- // don't try to unmarshal an empty value |
|
| 82 |
- return nil |
|
| 83 |
- } |
|
| 84 |
- |
|
| 85 |
- var m volumeMetadata |
|
| 86 |
- if err := json.Unmarshal(v, &m); err != nil {
|
|
| 87 |
- // Just log the error |
|
| 88 |
- logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(k), err)
|
|
| 89 |
- return nil |
|
| 90 |
- } |
|
| 91 |
- ls = append(ls, m) |
|
| 92 |
- return nil |
|
| 93 |
- }) |
|
| 94 |
- return ls |
|
| 95 |
-} |
| 96 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,52 +0,0 @@ |
| 1 |
-package store |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io/ioutil" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "testing" |
|
| 8 |
- "time" |
|
| 9 |
- |
|
| 10 |
- "github.com/boltdb/bolt" |
|
| 11 |
- "github.com/gotestyourself/gotestyourself/assert" |
|
| 12 |
- is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-func TestSetGetMeta(t *testing.T) {
|
|
| 16 |
- t.Parallel() |
|
| 17 |
- |
|
| 18 |
- dir, err := ioutil.TempDir("", "test-set-get")
|
|
| 19 |
- assert.NilError(t, err) |
|
| 20 |
- defer os.RemoveAll(dir) |
|
| 21 |
- |
|
| 22 |
- db, err := bolt.Open(filepath.Join(dir, "db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
| 23 |
- assert.NilError(t, err) |
|
| 24 |
- |
|
| 25 |
- store := &VolumeStore{db: db}
|
|
| 26 |
- |
|
| 27 |
- _, err = store.getMeta("test")
|
|
| 28 |
- assert.Assert(t, is.ErrorContains(err, "")) |
|
| 29 |
- |
|
| 30 |
- err = db.Update(func(tx *bolt.Tx) error {
|
|
| 31 |
- _, err := tx.CreateBucket(volumeBucketName) |
|
| 32 |
- return err |
|
| 33 |
- }) |
|
| 34 |
- assert.NilError(t, err) |
|
| 35 |
- |
|
| 36 |
- meta, err := store.getMeta("test")
|
|
| 37 |
- assert.NilError(t, err) |
|
| 38 |
- assert.DeepEqual(t, volumeMetadata{}, meta)
|
|
| 39 |
- |
|
| 40 |
- testMeta := volumeMetadata{
|
|
| 41 |
- Name: "test", |
|
| 42 |
- Driver: "fake", |
|
| 43 |
- Labels: map[string]string{"a": "1", "b": "2"},
|
|
| 44 |
- Options: map[string]string{"foo": "bar"},
|
|
| 45 |
- } |
|
| 46 |
- err = store.setMeta("test", testMeta)
|
|
| 47 |
- assert.NilError(t, err) |
|
| 48 |
- |
|
| 49 |
- meta, err = store.getMeta("test")
|
|
| 50 |
- assert.NilError(t, err) |
|
| 51 |
- assert.DeepEqual(t, testMeta, meta) |
|
| 52 |
-} |
| 53 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,95 +0,0 @@ |
| 1 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "strings" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-const ( |
|
| 8 |
- // errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container |
|
| 9 |
- errVolumeInUse conflictError = "volume is in use" |
|
| 10 |
- // errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store |
|
| 11 |
- errNoSuchVolume notFoundError = "no such volume" |
|
| 12 |
- // errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver |
|
| 13 |
- errNameConflict conflictError = "volume name must be unique" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-type conflictError string |
|
| 17 |
- |
|
| 18 |
-func (e conflictError) Error() string {
|
|
| 19 |
- return string(e) |
|
| 20 |
-} |
|
| 21 |
-func (conflictError) Conflict() {}
|
|
| 22 |
- |
|
| 23 |
-type notFoundError string |
|
| 24 |
- |
|
| 25 |
-func (e notFoundError) Error() string {
|
|
| 26 |
- return string(e) |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-func (notFoundError) NotFound() {}
|
|
| 30 |
- |
|
| 31 |
-// OpErr is the error type returned by functions in the store package. It describes |
|
| 32 |
-// the operation, volume name, and error. |
|
| 33 |
-type OpErr struct {
|
|
| 34 |
- // Err is the error that occurred during the operation. |
|
| 35 |
- Err error |
|
| 36 |
- // Op is the operation which caused the error, such as "create", or "list". |
|
| 37 |
- Op string |
|
| 38 |
- // Name is the name of the resource being requested for this op, typically the volume name or the driver name. |
|
| 39 |
- Name string |
|
| 40 |
- // Refs is the list of references associated with the resource. |
|
| 41 |
- Refs []string |
|
| 42 |
-} |
|
| 43 |
- |
|
| 44 |
-// Error satisfies the built-in error interface type. |
|
| 45 |
-func (e *OpErr) Error() string {
|
|
| 46 |
- if e == nil {
|
|
| 47 |
- return "<nil>" |
|
| 48 |
- } |
|
| 49 |
- s := e.Op |
|
| 50 |
- if e.Name != "" {
|
|
| 51 |
- s = s + " " + e.Name |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- s = s + ": " + e.Err.Error() |
|
| 55 |
- if len(e.Refs) > 0 {
|
|
| 56 |
- s = s + " - " + "[" + strings.Join(e.Refs, ", ") + "]" |
|
| 57 |
- } |
|
| 58 |
- return s |
|
| 59 |
-} |
|
| 60 |
- |
|
| 61 |
-// Cause returns the error the caused this error |
|
| 62 |
-func (e *OpErr) Cause() error {
|
|
| 63 |
- return e.Err |
|
| 64 |
-} |
|
| 65 |
- |
|
| 66 |
-// IsInUse returns a boolean indicating whether the error indicates that a |
|
| 67 |
-// volume is in use |
|
| 68 |
-func IsInUse(err error) bool {
|
|
| 69 |
- return isErr(err, errVolumeInUse) |
|
| 70 |
-} |
|
| 71 |
- |
|
| 72 |
-// IsNotExist returns a boolean indicating whether the error indicates that the volume does not exist |
|
| 73 |
-func IsNotExist(err error) bool {
|
|
| 74 |
- return isErr(err, errNoSuchVolume) |
|
| 75 |
-} |
|
| 76 |
- |
|
| 77 |
-// IsNameConflict returns a boolean indicating whether the error indicates that a |
|
| 78 |
-// volume name is already taken |
|
| 79 |
-func IsNameConflict(err error) bool {
|
|
| 80 |
- return isErr(err, errNameConflict) |
|
| 81 |
-} |
|
| 82 |
- |
|
| 83 |
-type causal interface {
|
|
| 84 |
- Cause() error |
|
| 85 |
-} |
|
| 86 |
- |
|
| 87 |
-func isErr(err error, expected error) bool {
|
|
| 88 |
- switch pe := err.(type) {
|
|
| 89 |
- case nil: |
|
| 90 |
- return false |
|
| 91 |
- case causal: |
|
| 92 |
- return isErr(pe.Cause(), expected) |
|
| 93 |
- } |
|
| 94 |
- return err == expected |
|
| 95 |
-} |
| 96 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,82 +0,0 @@ |
| 1 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "sync" |
|
| 5 |
- |
|
| 6 |
- "github.com/boltdb/bolt" |
|
| 7 |
- "github.com/docker/docker/volume" |
|
| 8 |
- "github.com/sirupsen/logrus" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-// restore is called when a new volume store is created. |
|
| 12 |
-// It's primary purpose is to ensure that all drivers' refcounts are set based |
|
| 13 |
-// on known volumes after a restart. |
|
| 14 |
-// This only attempts to track volumes that are actually stored in the on-disk db. |
|
| 15 |
-// It does not probe the available drivers to find anything that may have been added |
|
| 16 |
-// out of band. |
|
| 17 |
-func (s *VolumeStore) restore() {
|
|
| 18 |
- var ls []volumeMetadata |
|
| 19 |
- s.db.View(func(tx *bolt.Tx) error {
|
|
| 20 |
- ls = listMeta(tx) |
|
| 21 |
- return nil |
|
| 22 |
- }) |
|
| 23 |
- |
|
| 24 |
- chRemove := make(chan *volumeMetadata, len(ls)) |
|
| 25 |
- var wg sync.WaitGroup |
|
| 26 |
- for _, meta := range ls {
|
|
| 27 |
- wg.Add(1) |
|
| 28 |
- // this is potentially a very slow operation, so do it in a goroutine |
|
| 29 |
- go func(meta volumeMetadata) {
|
|
| 30 |
- defer wg.Done() |
|
| 31 |
- |
|
| 32 |
- var v volume.Volume |
|
| 33 |
- var err error |
|
| 34 |
- if meta.Driver != "" {
|
|
| 35 |
- v, err = lookupVolume(s.drivers, meta.Driver, meta.Name) |
|
| 36 |
- if err != nil && err != errNoSuchVolume {
|
|
| 37 |
- logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
|
|
| 38 |
- return |
|
| 39 |
- } |
|
| 40 |
- if v == nil {
|
|
| 41 |
- // doesn't exist in the driver, remove it from the db |
|
| 42 |
- chRemove <- &meta |
|
| 43 |
- return |
|
| 44 |
- } |
|
| 45 |
- } else {
|
|
| 46 |
- v, err = s.getVolume(meta.Name) |
|
| 47 |
- if err != nil {
|
|
| 48 |
- if err == errNoSuchVolume {
|
|
| 49 |
- chRemove <- &meta |
|
| 50 |
- } |
|
| 51 |
- return |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- meta.Driver = v.DriverName() |
|
| 55 |
- if err := s.setMeta(v.Name(), meta); err != nil {
|
|
| 56 |
- logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", v.Name()).Warn("Error updating volume metadata on restore")
|
|
| 57 |
- } |
|
| 58 |
- } |
|
| 59 |
- |
|
| 60 |
- // increment driver refcount |
|
| 61 |
- s.drivers.CreateDriver(meta.Driver) |
|
| 62 |
- |
|
| 63 |
- // cache the volume |
|
| 64 |
- s.globalLock.Lock() |
|
| 65 |
- s.options[v.Name()] = meta.Options |
|
| 66 |
- s.labels[v.Name()] = meta.Labels |
|
| 67 |
- s.names[v.Name()] = v |
|
| 68 |
- s.globalLock.Unlock() |
|
| 69 |
- }(meta) |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- wg.Wait() |
|
| 73 |
- close(chRemove) |
|
| 74 |
- s.db.Update(func(tx *bolt.Tx) error {
|
|
| 75 |
- for meta := range chRemove {
|
|
| 76 |
- if err := removeMeta(tx, meta.Name); err != nil {
|
|
| 77 |
- logrus.WithField("volume", meta.Name).Warnf("Error removing stale entry from volume db: %v", err)
|
|
| 78 |
- } |
|
| 79 |
- } |
|
| 80 |
- return nil |
|
| 81 |
- }) |
|
| 82 |
-} |
| 83 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,55 +0,0 @@ |
| 1 |
-package store |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io/ioutil" |
|
| 5 |
- "os" |
|
| 6 |
- "testing" |
|
| 7 |
- |
|
| 8 |
- "github.com/docker/docker/volume" |
|
| 9 |
- volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 10 |
- volumetestutils "github.com/docker/docker/volume/testutils" |
|
| 11 |
- "github.com/gotestyourself/gotestyourself/assert" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func TestRestore(t *testing.T) {
|
|
| 15 |
- t.Parallel() |
|
| 16 |
- |
|
| 17 |
- dir, err := ioutil.TempDir("", "test-restore")
|
|
| 18 |
- assert.NilError(t, err) |
|
| 19 |
- defer os.RemoveAll(dir) |
|
| 20 |
- |
|
| 21 |
- drivers := volumedrivers.NewStore(nil) |
|
| 22 |
- driverName := "test-restore" |
|
| 23 |
- drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 24 |
- |
|
| 25 |
- s, err := New(dir, drivers) |
|
| 26 |
- assert.NilError(t, err) |
|
| 27 |
- defer s.Shutdown() |
|
| 28 |
- |
|
| 29 |
- _, err = s.Create("test1", driverName, nil, nil)
|
|
| 30 |
- assert.NilError(t, err) |
|
| 31 |
- |
|
| 32 |
- testLabels := map[string]string{"a": "1"}
|
|
| 33 |
- testOpts := map[string]string{"foo": "bar"}
|
|
| 34 |
- _, err = s.Create("test2", driverName, testOpts, testLabels)
|
|
| 35 |
- assert.NilError(t, err) |
|
| 36 |
- |
|
| 37 |
- s.Shutdown() |
|
| 38 |
- |
|
| 39 |
- s, err = New(dir, drivers) |
|
| 40 |
- assert.NilError(t, err) |
|
| 41 |
- |
|
| 42 |
- v, err := s.Get("test1")
|
|
| 43 |
- assert.NilError(t, err) |
|
| 44 |
- |
|
| 45 |
- dv := v.(volume.DetailedVolume) |
|
| 46 |
- var nilMap map[string]string |
|
| 47 |
- assert.DeepEqual(t, nilMap, dv.Options()) |
|
| 48 |
- assert.DeepEqual(t, nilMap, dv.Labels()) |
|
| 49 |
- |
|
| 50 |
- v, err = s.Get("test2")
|
|
| 51 |
- assert.NilError(t, err) |
|
| 52 |
- dv = v.(volume.DetailedVolume) |
|
| 53 |
- assert.DeepEqual(t, testOpts, dv.Options()) |
|
| 54 |
- assert.DeepEqual(t, testLabels, dv.Labels()) |
|
| 55 |
-} |
| 56 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,707 +0,0 @@ |
| 1 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "net" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "runtime" |
|
| 8 |
- "sync" |
|
| 9 |
- "time" |
|
| 10 |
- |
|
| 11 |
- "github.com/pkg/errors" |
|
| 12 |
- |
|
| 13 |
- "github.com/boltdb/bolt" |
|
| 14 |
- "github.com/docker/docker/pkg/locker" |
|
| 15 |
- "github.com/docker/docker/volume" |
|
| 16 |
- "github.com/docker/docker/volume/drivers" |
|
| 17 |
- volumemounts "github.com/docker/docker/volume/mounts" |
|
| 18 |
- "github.com/sirupsen/logrus" |
|
| 19 |
-) |
|
| 20 |
- |
|
| 21 |
-const ( |
|
| 22 |
- volumeDataDir = "volumes" |
|
| 23 |
-) |
|
| 24 |
- |
|
| 25 |
-type volumeWrapper struct {
|
|
| 26 |
- volume.Volume |
|
| 27 |
- labels map[string]string |
|
| 28 |
- scope string |
|
| 29 |
- options map[string]string |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-func (v volumeWrapper) Options() map[string]string {
|
|
| 33 |
- if v.options == nil {
|
|
| 34 |
- return nil |
|
| 35 |
- } |
|
| 36 |
- options := make(map[string]string, len(v.options)) |
|
| 37 |
- for key, value := range v.options {
|
|
| 38 |
- options[key] = value |
|
| 39 |
- } |
|
| 40 |
- return options |
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 |
-func (v volumeWrapper) Labels() map[string]string {
|
|
| 44 |
- if v.labels == nil {
|
|
| 45 |
- return nil |
|
| 46 |
- } |
|
| 47 |
- |
|
| 48 |
- labels := make(map[string]string, len(v.labels)) |
|
| 49 |
- for key, value := range v.labels {
|
|
| 50 |
- labels[key] = value |
|
| 51 |
- } |
|
| 52 |
- return labels |
|
| 53 |
-} |
|
| 54 |
- |
|
| 55 |
-func (v volumeWrapper) Scope() string {
|
|
| 56 |
- return v.scope |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-func (v volumeWrapper) CachedPath() string {
|
|
| 60 |
- if vv, ok := v.Volume.(interface {
|
|
| 61 |
- CachedPath() string |
|
| 62 |
- }); ok {
|
|
| 63 |
- return vv.CachedPath() |
|
| 64 |
- } |
|
| 65 |
- return v.Volume.Path() |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-// New initializes a VolumeStore to keep |
|
| 69 |
-// reference counting of volumes in the system. |
|
| 70 |
-func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
|
|
| 71 |
- vs := &VolumeStore{
|
|
| 72 |
- locks: &locker.Locker{},
|
|
| 73 |
- names: make(map[string]volume.Volume), |
|
| 74 |
- refs: make(map[string]map[string]struct{}),
|
|
| 75 |
- labels: make(map[string]map[string]string), |
|
| 76 |
- options: make(map[string]map[string]string), |
|
| 77 |
- drivers: drivers, |
|
| 78 |
- } |
|
| 79 |
- |
|
| 80 |
- if rootPath != "" {
|
|
| 81 |
- // initialize metadata store |
|
| 82 |
- volPath := filepath.Join(rootPath, volumeDataDir) |
|
| 83 |
- if err := os.MkdirAll(volPath, 0750); err != nil {
|
|
| 84 |
- return nil, err |
|
| 85 |
- } |
|
| 86 |
- |
|
| 87 |
- dbPath := filepath.Join(volPath, "metadata.db") |
|
| 88 |
- |
|
| 89 |
- var err error |
|
| 90 |
- vs.db, err = bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
| 91 |
- if err != nil {
|
|
| 92 |
- return nil, errors.Wrap(err, "error while opening volume store metadata database") |
|
| 93 |
- } |
|
| 94 |
- |
|
| 95 |
- // initialize volumes bucket |
|
| 96 |
- if err := vs.db.Update(func(tx *bolt.Tx) error {
|
|
| 97 |
- if _, err := tx.CreateBucketIfNotExists(volumeBucketName); err != nil {
|
|
| 98 |
- return errors.Wrap(err, "error while setting up volume store metadata database") |
|
| 99 |
- } |
|
| 100 |
- return nil |
|
| 101 |
- }); err != nil {
|
|
| 102 |
- return nil, err |
|
| 103 |
- } |
|
| 104 |
- } |
|
| 105 |
- |
|
| 106 |
- vs.restore() |
|
| 107 |
- |
|
| 108 |
- return vs, nil |
|
| 109 |
-} |
|
| 110 |
- |
|
| 111 |
-func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) {
|
|
| 112 |
- s.globalLock.RLock() |
|
| 113 |
- v, exists := s.names[name] |
|
| 114 |
- s.globalLock.RUnlock() |
|
| 115 |
- return v, exists |
|
| 116 |
-} |
|
| 117 |
- |
|
| 118 |
-func (s *VolumeStore) setNamed(v volume.Volume, ref string) {
|
|
| 119 |
- name := v.Name() |
|
| 120 |
- |
|
| 121 |
- s.globalLock.Lock() |
|
| 122 |
- s.names[name] = v |
|
| 123 |
- if len(ref) > 0 {
|
|
| 124 |
- if s.refs[name] == nil {
|
|
| 125 |
- s.refs[name] = make(map[string]struct{})
|
|
| 126 |
- } |
|
| 127 |
- s.refs[name][ref] = struct{}{}
|
|
| 128 |
- } |
|
| 129 |
- s.globalLock.Unlock() |
|
| 130 |
-} |
|
| 131 |
- |
|
| 132 |
-// hasRef returns true if the given name has at least one ref. |
|
| 133 |
-// Callers of this function are expected to hold the name lock. |
|
| 134 |
-func (s *VolumeStore) hasRef(name string) bool {
|
|
| 135 |
- s.globalLock.RLock() |
|
| 136 |
- l := len(s.refs[name]) |
|
| 137 |
- s.globalLock.RUnlock() |
|
| 138 |
- return l > 0 |
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-// getRefs gets the list of refs for a given name |
|
| 142 |
-// Callers of this function are expected to hold the name lock. |
|
| 143 |
-func (s *VolumeStore) getRefs(name string) []string {
|
|
| 144 |
- s.globalLock.RLock() |
|
| 145 |
- defer s.globalLock.RUnlock() |
|
| 146 |
- |
|
| 147 |
- refs := make([]string, 0, len(s.refs[name])) |
|
| 148 |
- for r := range s.refs[name] {
|
|
| 149 |
- refs = append(refs, r) |
|
| 150 |
- } |
|
| 151 |
- |
|
| 152 |
- return refs |
|
| 153 |
-} |
|
| 154 |
- |
|
| 155 |
-// Purge allows the cleanup of internal data on docker in case |
|
| 156 |
-// the internal data is out of sync with volumes driver plugins. |
|
| 157 |
-func (s *VolumeStore) Purge(name string) {
|
|
| 158 |
- s.globalLock.Lock() |
|
| 159 |
- v, exists := s.names[name] |
|
| 160 |
- if exists {
|
|
| 161 |
- driverName := v.DriverName() |
|
| 162 |
- if _, err := s.drivers.ReleaseDriver(driverName); err != nil {
|
|
| 163 |
- logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
|
|
| 164 |
- } |
|
| 165 |
- } |
|
| 166 |
- if err := s.removeMeta(name); err != nil {
|
|
| 167 |
- logrus.Errorf("Error removing volume metadata for volume %q: %v", name, err)
|
|
| 168 |
- } |
|
| 169 |
- delete(s.names, name) |
|
| 170 |
- delete(s.refs, name) |
|
| 171 |
- delete(s.labels, name) |
|
| 172 |
- delete(s.options, name) |
|
| 173 |
- s.globalLock.Unlock() |
|
| 174 |
-} |
|
| 175 |
- |
|
| 176 |
-// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts |
|
| 177 |
-type VolumeStore struct {
|
|
| 178 |
- // locks ensures that only one action is being performed on a particular volume at a time without locking the entire store |
|
| 179 |
- // since actions on volumes can be quite slow, this ensures the store is free to handle requests for other volumes. |
|
| 180 |
- locks *locker.Locker |
|
| 181 |
- drivers *drivers.Store |
|
| 182 |
- // globalLock is used to protect access to mutable structures used by the store object |
|
| 183 |
- globalLock sync.RWMutex |
|
| 184 |
- // names stores the volume name -> volume relationship. |
|
| 185 |
- // This is used for making lookups faster so we don't have to probe all drivers |
|
| 186 |
- names map[string]volume.Volume |
|
| 187 |
- // refs stores the volume name and the list of things referencing it |
|
| 188 |
- refs map[string]map[string]struct{}
|
|
| 189 |
- // labels stores volume labels for each volume |
|
| 190 |
- labels map[string]map[string]string |
|
| 191 |
- // options stores volume options for each volume |
|
| 192 |
- options map[string]map[string]string |
|
| 193 |
- db *bolt.DB |
|
| 194 |
-} |
|
| 195 |
- |
|
| 196 |
-// List proxies to all registered volume drivers to get the full list of volumes |
|
| 197 |
-// If a driver returns a volume that has name which conflicts with another volume from a different driver, |
|
| 198 |
-// the first volume is chosen and the conflicting volume is dropped. |
|
| 199 |
-func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
|
|
| 200 |
- vols, warnings, err := s.list() |
|
| 201 |
- if err != nil {
|
|
| 202 |
- return nil, nil, &OpErr{Err: err, Op: "list"}
|
|
| 203 |
- } |
|
| 204 |
- var out []volume.Volume |
|
| 205 |
- |
|
| 206 |
- for _, v := range vols {
|
|
| 207 |
- name := normalizeVolumeName(v.Name()) |
|
| 208 |
- |
|
| 209 |
- s.locks.Lock(name) |
|
| 210 |
- storedV, exists := s.getNamed(name) |
|
| 211 |
- // Note: it's not safe to populate the cache here because the volume may have been |
|
| 212 |
- // deleted before we acquire a lock on its name |
|
| 213 |
- if exists && storedV.DriverName() != v.DriverName() {
|
|
| 214 |
- logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), storedV.DriverName(), v.DriverName())
|
|
| 215 |
- s.locks.Unlock(v.Name()) |
|
| 216 |
- continue |
|
| 217 |
- } |
|
| 218 |
- |
|
| 219 |
- out = append(out, v) |
|
| 220 |
- s.locks.Unlock(v.Name()) |
|
| 221 |
- } |
|
| 222 |
- return out, warnings, nil |
|
| 223 |
-} |
|
| 224 |
- |
|
| 225 |
-// list goes through each volume driver and asks for its list of volumes. |
|
| 226 |
-func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
|
|
| 227 |
- var ( |
|
| 228 |
- ls []volume.Volume |
|
| 229 |
- warnings []string |
|
| 230 |
- ) |
|
| 231 |
- |
|
| 232 |
- drivers, err := s.drivers.GetAllDrivers() |
|
| 233 |
- if err != nil {
|
|
| 234 |
- return nil, nil, err |
|
| 235 |
- } |
|
| 236 |
- |
|
| 237 |
- type vols struct {
|
|
| 238 |
- vols []volume.Volume |
|
| 239 |
- err error |
|
| 240 |
- driverName string |
|
| 241 |
- } |
|
| 242 |
- chVols := make(chan vols, len(drivers)) |
|
| 243 |
- |
|
| 244 |
- for _, vd := range drivers {
|
|
| 245 |
- go func(d volume.Driver) {
|
|
| 246 |
- vs, err := d.List() |
|
| 247 |
- if err != nil {
|
|
| 248 |
- chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
|
|
| 249 |
- return |
|
| 250 |
- } |
|
| 251 |
- for i, v := range vs {
|
|
| 252 |
- s.globalLock.RLock() |
|
| 253 |
- vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope(), s.options[v.Name()]}
|
|
| 254 |
- s.globalLock.RUnlock() |
|
| 255 |
- } |
|
| 256 |
- |
|
| 257 |
- chVols <- vols{vols: vs}
|
|
| 258 |
- }(vd) |
|
| 259 |
- } |
|
| 260 |
- |
|
| 261 |
- badDrivers := make(map[string]struct{})
|
|
| 262 |
- for i := 0; i < len(drivers); i++ {
|
|
| 263 |
- vs := <-chVols |
|
| 264 |
- |
|
| 265 |
- if vs.err != nil {
|
|
| 266 |
- warnings = append(warnings, vs.err.Error()) |
|
| 267 |
- badDrivers[vs.driverName] = struct{}{}
|
|
| 268 |
- logrus.Warn(vs.err) |
|
| 269 |
- } |
|
| 270 |
- ls = append(ls, vs.vols...) |
|
| 271 |
- } |
|
| 272 |
- |
|
| 273 |
- if len(badDrivers) > 0 {
|
|
| 274 |
- s.globalLock.RLock() |
|
| 275 |
- for _, v := range s.names {
|
|
| 276 |
- if _, exists := badDrivers[v.DriverName()]; exists {
|
|
| 277 |
- ls = append(ls, v) |
|
| 278 |
- } |
|
| 279 |
- } |
|
| 280 |
- s.globalLock.RUnlock() |
|
| 281 |
- } |
|
| 282 |
- return ls, warnings, nil |
|
| 283 |
-} |
|
| 284 |
- |
|
| 285 |
-// CreateWithRef creates a volume with the given name and driver and stores the ref |
|
| 286 |
-// This ensures there's no race between creating a volume and then storing a reference. |
|
| 287 |
-func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 288 |
- name = normalizeVolumeName(name) |
|
| 289 |
- s.locks.Lock(name) |
|
| 290 |
- defer s.locks.Unlock(name) |
|
| 291 |
- |
|
| 292 |
- v, err := s.create(name, driverName, opts, labels) |
|
| 293 |
- if err != nil {
|
|
| 294 |
- if _, ok := err.(*OpErr); ok {
|
|
| 295 |
- return nil, err |
|
| 296 |
- } |
|
| 297 |
- return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
|
| 298 |
- } |
|
| 299 |
- |
|
| 300 |
- s.setNamed(v, ref) |
|
| 301 |
- return v, nil |
|
| 302 |
-} |
|
| 303 |
- |
|
| 304 |
-// Create creates a volume with the given name and driver. |
|
| 305 |
-// This is just like CreateWithRef() except we don't store a reference while holding the lock. |
|
| 306 |
-func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 307 |
- return s.CreateWithRef(name, driverName, "", opts, labels) |
|
| 308 |
-} |
|
| 309 |
- |
|
| 310 |
-// checkConflict checks the local cache for name collisions with the passed in name, |
|
| 311 |
-// for existing volumes with the same name but in a different driver. |
|
| 312 |
-// This is used by `Create` as a best effort to prevent name collisions for volumes. |
|
| 313 |
-// If a matching volume is found that is not a conflict that is returned so the caller |
|
| 314 |
-// does not need to perform an additional lookup. |
|
| 315 |
-// When no matching volume is found, both returns will be nil |
|
| 316 |
-// |
|
| 317 |
-// Note: This does not probe all the drivers for name collisions because v1 plugins |
|
| 318 |
-// are very slow, particularly if the plugin is down, and cause other issues, |
|
| 319 |
-// particularly around locking the store. |
|
| 320 |
-// TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially |
|
| 321 |
-// use a connect timeout for this kind of check to ensure we aren't blocking for a |
|
| 322 |
-// long time. |
|
| 323 |
-func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, error) {
|
|
| 324 |
- // check the local cache |
|
| 325 |
- v, _ := s.getNamed(name) |
|
| 326 |
- if v == nil {
|
|
| 327 |
- return nil, nil |
|
| 328 |
- } |
|
| 329 |
- |
|
| 330 |
- vDriverName := v.DriverName() |
|
| 331 |
- var conflict bool |
|
| 332 |
- if driverName != "" {
|
|
| 333 |
- // Retrieve canonical driver name to avoid inconsistencies (for example |
|
| 334 |
- // "plugin" vs. "plugin:latest") |
|
| 335 |
- vd, err := s.drivers.GetDriver(driverName) |
|
| 336 |
- if err != nil {
|
|
| 337 |
- return nil, err |
|
| 338 |
- } |
|
| 339 |
- |
|
| 340 |
- if vDriverName != vd.Name() {
|
|
| 341 |
- conflict = true |
|
| 342 |
- } |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- // let's check if the found volume ref |
|
| 346 |
- // is stale by checking with the driver if it still exists |
|
| 347 |
- exists, err := volumeExists(s.drivers, v) |
|
| 348 |
- if err != nil {
|
|
| 349 |
- 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) |
|
| 350 |
- } |
|
| 351 |
- |
|
| 352 |
- if exists {
|
|
| 353 |
- if conflict {
|
|
| 354 |
- return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name) |
|
| 355 |
- } |
|
| 356 |
- return v, nil |
|
| 357 |
- } |
|
| 358 |
- |
|
| 359 |
- if s.hasRef(v.Name()) {
|
|
| 360 |
- // Containers are referencing this volume but it doesn't seem to exist anywhere. |
|
| 361 |
- // Return a conflict error here, the user can fix this with `docker volume rm -f` |
|
| 362 |
- return nil, errors.Wrapf(errNameConflict, "found references to volume '%s' in driver '%s' but the volume was not found in the driver -- you may need to remove containers referencing this volume or force remove the volume to re-create it", name, vDriverName) |
|
| 363 |
- } |
|
| 364 |
- |
|
| 365 |
- // doesn't exist, so purge it from the cache |
|
| 366 |
- s.Purge(name) |
|
| 367 |
- return nil, nil |
|
| 368 |
-} |
|
| 369 |
- |
|
| 370 |
-// volumeExists returns if the volume is still present in the driver. |
|
| 371 |
-// An error is returned if there was an issue communicating with the driver. |
|
| 372 |
-func volumeExists(store *drivers.Store, v volume.Volume) (bool, error) {
|
|
| 373 |
- exists, err := lookupVolume(store, v.DriverName(), v.Name()) |
|
| 374 |
- if err != nil {
|
|
| 375 |
- return false, err |
|
| 376 |
- } |
|
| 377 |
- return exists != nil, nil |
|
| 378 |
-} |
|
| 379 |
- |
|
| 380 |
-// create asks the given driver to create a volume with the name/opts. |
|
| 381 |
-// If a volume with the name is already known, it will ask the stored driver for the volume. |
|
| 382 |
-// If the passed in driver name does not match the driver name which is stored |
|
| 383 |
-// for the given volume name, an error is returned after checking if the reference is stale. |
|
| 384 |
-// If the reference is stale, it will be purged and this create can continue. |
|
| 385 |
-// It is expected that callers of this function hold any necessary locks. |
|
| 386 |
-func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 387 |
- // Validate the name in a platform-specific manner |
|
| 388 |
- |
|
| 389 |
- // volume name validation is specific to the host os and not on container image |
|
| 390 |
- // windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS |
|
| 391 |
- parser := volumemounts.NewParser(runtime.GOOS) |
|
| 392 |
- err := parser.ValidateVolumeName(name) |
|
| 393 |
- if err != nil {
|
|
| 394 |
- return nil, err |
|
| 395 |
- } |
|
| 396 |
- |
|
| 397 |
- v, err := s.checkConflict(name, driverName) |
|
| 398 |
- if err != nil {
|
|
| 399 |
- return nil, err |
|
| 400 |
- } |
|
| 401 |
- |
|
| 402 |
- if v != nil {
|
|
| 403 |
- // there is an existing volume, if we already have this stored locally, return it. |
|
| 404 |
- // TODO: there could be some inconsistent details such as labels here |
|
| 405 |
- if vv, _ := s.getNamed(v.Name()); vv != nil {
|
|
| 406 |
- return vv, nil |
|
| 407 |
- } |
|
| 408 |
- } |
|
| 409 |
- |
|
| 410 |
- // Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name |
|
| 411 |
- if driverName == "" {
|
|
| 412 |
- v, _ = s.getVolume(name) |
|
| 413 |
- if v != nil {
|
|
| 414 |
- return v, nil |
|
| 415 |
- } |
|
| 416 |
- } |
|
| 417 |
- |
|
| 418 |
- if driverName == "" {
|
|
| 419 |
- driverName = volume.DefaultDriverName |
|
| 420 |
- } |
|
| 421 |
- vd, err := s.drivers.CreateDriver(driverName) |
|
| 422 |
- if err != nil {
|
|
| 423 |
- return nil, &OpErr{Op: "create", Name: name, Err: err}
|
|
| 424 |
- } |
|
| 425 |
- |
|
| 426 |
- logrus.Debugf("Registering new volume reference: driver %q, name %q", vd.Name(), name)
|
|
| 427 |
- if v, _ = vd.Get(name); v == nil {
|
|
| 428 |
- v, err = vd.Create(name, opts) |
|
| 429 |
- if err != nil {
|
|
| 430 |
- if _, err := s.drivers.ReleaseDriver(driverName); err != nil {
|
|
| 431 |
- logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
|
|
| 432 |
- } |
|
| 433 |
- return nil, err |
|
| 434 |
- } |
|
| 435 |
- } |
|
| 436 |
- |
|
| 437 |
- s.globalLock.Lock() |
|
| 438 |
- s.labels[name] = labels |
|
| 439 |
- s.options[name] = opts |
|
| 440 |
- s.refs[name] = make(map[string]struct{})
|
|
| 441 |
- s.globalLock.Unlock() |
|
| 442 |
- |
|
| 443 |
- metadata := volumeMetadata{
|
|
| 444 |
- Name: name, |
|
| 445 |
- Driver: vd.Name(), |
|
| 446 |
- Labels: labels, |
|
| 447 |
- Options: opts, |
|
| 448 |
- } |
|
| 449 |
- |
|
| 450 |
- if err := s.setMeta(name, metadata); err != nil {
|
|
| 451 |
- return nil, err |
|
| 452 |
- } |
|
| 453 |
- return volumeWrapper{v, labels, vd.Scope(), opts}, nil
|
|
| 454 |
-} |
|
| 455 |
- |
|
| 456 |
-// GetWithRef gets a volume with the given name from the passed in driver and stores the ref |
|
| 457 |
-// This is just like Get(), but we store the reference while holding the lock. |
|
| 458 |
-// This makes sure there are no races between checking for the existence of a volume and adding a reference for it |
|
| 459 |
-func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, error) {
|
|
| 460 |
- name = normalizeVolumeName(name) |
|
| 461 |
- s.locks.Lock(name) |
|
| 462 |
- defer s.locks.Unlock(name) |
|
| 463 |
- |
|
| 464 |
- if driverName == "" {
|
|
| 465 |
- driverName = volume.DefaultDriverName |
|
| 466 |
- } |
|
| 467 |
- vd, err := s.drivers.GetDriver(driverName) |
|
| 468 |
- if err != nil {
|
|
| 469 |
- return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 470 |
- } |
|
| 471 |
- |
|
| 472 |
- v, err := vd.Get(name) |
|
| 473 |
- if err != nil {
|
|
| 474 |
- return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 475 |
- } |
|
| 476 |
- |
|
| 477 |
- s.setNamed(v, ref) |
|
| 478 |
- |
|
| 479 |
- s.globalLock.RLock() |
|
| 480 |
- defer s.globalLock.RUnlock() |
|
| 481 |
- return volumeWrapper{v, s.labels[name], vd.Scope(), s.options[name]}, nil
|
|
| 482 |
-} |
|
| 483 |
- |
|
| 484 |
-// Get looks if a volume with the given name exists and returns it if so |
|
| 485 |
-func (s *VolumeStore) Get(name string) (volume.Volume, error) {
|
|
| 486 |
- name = normalizeVolumeName(name) |
|
| 487 |
- s.locks.Lock(name) |
|
| 488 |
- defer s.locks.Unlock(name) |
|
| 489 |
- |
|
| 490 |
- v, err := s.getVolume(name) |
|
| 491 |
- if err != nil {
|
|
| 492 |
- return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 493 |
- } |
|
| 494 |
- s.setNamed(v, "") |
|
| 495 |
- return v, nil |
|
| 496 |
-} |
|
| 497 |
- |
|
| 498 |
-// getVolume requests the volume, if the driver info is stored it just accesses that driver, |
|
| 499 |
-// if the driver is unknown it probes all drivers until it finds the first volume with that name. |
|
| 500 |
-// it is expected that callers of this function hold any necessary locks |
|
| 501 |
-func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
|
| 502 |
- var meta volumeMetadata |
|
| 503 |
- meta, err := s.getMeta(name) |
|
| 504 |
- if err != nil {
|
|
| 505 |
- return nil, err |
|
| 506 |
- } |
|
| 507 |
- |
|
| 508 |
- driverName := meta.Driver |
|
| 509 |
- if driverName == "" {
|
|
| 510 |
- s.globalLock.RLock() |
|
| 511 |
- v, exists := s.names[name] |
|
| 512 |
- s.globalLock.RUnlock() |
|
| 513 |
- if exists {
|
|
| 514 |
- meta.Driver = v.DriverName() |
|
| 515 |
- if err := s.setMeta(name, meta); err != nil {
|
|
| 516 |
- return nil, err |
|
| 517 |
- } |
|
| 518 |
- } |
|
| 519 |
- } |
|
| 520 |
- |
|
| 521 |
- if meta.Driver != "" {
|
|
| 522 |
- vol, err := lookupVolume(s.drivers, meta.Driver, name) |
|
| 523 |
- if err != nil {
|
|
| 524 |
- return nil, err |
|
| 525 |
- } |
|
| 526 |
- if vol == nil {
|
|
| 527 |
- s.Purge(name) |
|
| 528 |
- return nil, errNoSuchVolume |
|
| 529 |
- } |
|
| 530 |
- |
|
| 531 |
- var scope string |
|
| 532 |
- vd, err := s.drivers.GetDriver(meta.Driver) |
|
| 533 |
- if err == nil {
|
|
| 534 |
- scope = vd.Scope() |
|
| 535 |
- } |
|
| 536 |
- return volumeWrapper{vol, meta.Labels, scope, meta.Options}, nil
|
|
| 537 |
- } |
|
| 538 |
- |
|
| 539 |
- logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
|
| 540 |
- drivers, err := s.drivers.GetAllDrivers() |
|
| 541 |
- if err != nil {
|
|
| 542 |
- return nil, err |
|
| 543 |
- } |
|
| 544 |
- |
|
| 545 |
- for _, d := range drivers {
|
|
| 546 |
- v, err := d.Get(name) |
|
| 547 |
- if err != nil || v == nil {
|
|
| 548 |
- continue |
|
| 549 |
- } |
|
| 550 |
- meta.Driver = v.DriverName() |
|
| 551 |
- if err := s.setMeta(name, meta); err != nil {
|
|
| 552 |
- return nil, err |
|
| 553 |
- } |
|
| 554 |
- return volumeWrapper{v, meta.Labels, d.Scope(), meta.Options}, nil
|
|
| 555 |
- } |
|
| 556 |
- return nil, errNoSuchVolume |
|
| 557 |
-} |
|
| 558 |
- |
|
| 559 |
-// lookupVolume gets the specified volume from the specified driver. |
|
| 560 |
-// This will only return errors related to communications with the driver. |
|
| 561 |
-// If the driver returns an error that is not communication related the |
|
| 562 |
-// error is logged but not returned. |
|
| 563 |
-// If the volume is not found it will return `nil, nil`` |
|
| 564 |
-func lookupVolume(store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
|
|
| 565 |
- if driverName == "" {
|
|
| 566 |
- driverName = volume.DefaultDriverName |
|
| 567 |
- } |
|
| 568 |
- vd, err := store.GetDriver(driverName) |
|
| 569 |
- if err != nil {
|
|
| 570 |
- return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName) |
|
| 571 |
- } |
|
| 572 |
- v, err := vd.Get(volumeName) |
|
| 573 |
- if err != nil {
|
|
| 574 |
- err = errors.Cause(err) |
|
| 575 |
- if _, ok := err.(net.Error); ok {
|
|
| 576 |
- if v != nil {
|
|
| 577 |
- volumeName = v.Name() |
|
| 578 |
- driverName = v.DriverName() |
|
| 579 |
- } |
|
| 580 |
- return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName) |
|
| 581 |
- } |
|
| 582 |
- |
|
| 583 |
- // At this point, the error could be anything from the driver, such as "no such volume" |
|
| 584 |
- // Let's not check an error here, and instead check if the driver returned a volume |
|
| 585 |
- logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Warnf("Error while looking up volume")
|
|
| 586 |
- } |
|
| 587 |
- return v, nil |
|
| 588 |
-} |
|
| 589 |
- |
|
| 590 |
-// Remove removes the requested volume. A volume is not removed if it has any refs |
|
| 591 |
-func (s *VolumeStore) Remove(v volume.Volume) error {
|
|
| 592 |
- name := normalizeVolumeName(v.Name()) |
|
| 593 |
- s.locks.Lock(name) |
|
| 594 |
- defer s.locks.Unlock(name) |
|
| 595 |
- |
|
| 596 |
- if s.hasRef(name) {
|
|
| 597 |
- return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: s.getRefs(name)}
|
|
| 598 |
- } |
|
| 599 |
- |
|
| 600 |
- vd, err := s.drivers.GetDriver(v.DriverName()) |
|
| 601 |
- if err != nil {
|
|
| 602 |
- return &OpErr{Err: err, Name: v.DriverName(), Op: "remove"}
|
|
| 603 |
- } |
|
| 604 |
- |
|
| 605 |
- logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
|
| 606 |
- vol := unwrapVolume(v) |
|
| 607 |
- if err := vd.Remove(vol); err != nil {
|
|
| 608 |
- return &OpErr{Err: err, Name: name, Op: "remove"}
|
|
| 609 |
- } |
|
| 610 |
- |
|
| 611 |
- s.Purge(name) |
|
| 612 |
- return nil |
|
| 613 |
-} |
|
| 614 |
- |
|
| 615 |
-// Dereference removes the specified reference to the volume |
|
| 616 |
-func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
|
|
| 617 |
- name := v.Name() |
|
| 618 |
- |
|
| 619 |
- s.locks.Lock(name) |
|
| 620 |
- defer s.locks.Unlock(name) |
|
| 621 |
- |
|
| 622 |
- s.globalLock.Lock() |
|
| 623 |
- defer s.globalLock.Unlock() |
|
| 624 |
- |
|
| 625 |
- if s.refs[name] != nil {
|
|
| 626 |
- delete(s.refs[name], ref) |
|
| 627 |
- } |
|
| 628 |
-} |
|
| 629 |
- |
|
| 630 |
-// Refs gets the current list of refs for the given volume |
|
| 631 |
-func (s *VolumeStore) Refs(v volume.Volume) []string {
|
|
| 632 |
- name := v.Name() |
|
| 633 |
- |
|
| 634 |
- s.locks.Lock(name) |
|
| 635 |
- defer s.locks.Unlock(name) |
|
| 636 |
- |
|
| 637 |
- return s.getRefs(name) |
|
| 638 |
-} |
|
| 639 |
- |
|
| 640 |
-// FilterByDriver returns the available volumes filtered by driver name |
|
| 641 |
-func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
|
|
| 642 |
- vd, err := s.drivers.GetDriver(name) |
|
| 643 |
- if err != nil {
|
|
| 644 |
- return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
|
| 645 |
- } |
|
| 646 |
- ls, err := vd.List() |
|
| 647 |
- if err != nil {
|
|
| 648 |
- return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
|
| 649 |
- } |
|
| 650 |
- for i, v := range ls {
|
|
| 651 |
- options := map[string]string{}
|
|
| 652 |
- s.globalLock.RLock() |
|
| 653 |
- for key, value := range s.options[v.Name()] {
|
|
| 654 |
- options[key] = value |
|
| 655 |
- } |
|
| 656 |
- ls[i] = volumeWrapper{v, s.labels[v.Name()], vd.Scope(), options}
|
|
| 657 |
- s.globalLock.RUnlock() |
|
| 658 |
- } |
|
| 659 |
- return ls, nil |
|
| 660 |
-} |
|
| 661 |
- |
|
| 662 |
-// FilterByUsed returns the available volumes filtered by if they are in use or not. |
|
| 663 |
-// `used=true` returns only volumes that are being used, while `used=false` returns |
|
| 664 |
-// only volumes that are not being used. |
|
| 665 |
-func (s *VolumeStore) FilterByUsed(vols []volume.Volume, used bool) []volume.Volume {
|
|
| 666 |
- return s.filter(vols, func(v volume.Volume) bool {
|
|
| 667 |
- s.locks.Lock(v.Name()) |
|
| 668 |
- hasRef := s.hasRef(v.Name()) |
|
| 669 |
- s.locks.Unlock(v.Name()) |
|
| 670 |
- return used == hasRef |
|
| 671 |
- }) |
|
| 672 |
-} |
|
| 673 |
- |
|
| 674 |
-// filterFunc defines a function to allow filter volumes in the store |
|
| 675 |
-type filterFunc func(vol volume.Volume) bool |
|
| 676 |
- |
|
| 677 |
-// filter returns the available volumes filtered by a filterFunc function |
|
| 678 |
-func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume {
|
|
| 679 |
- var ls []volume.Volume |
|
| 680 |
- for _, v := range vols {
|
|
| 681 |
- if f(v) {
|
|
| 682 |
- ls = append(ls, v) |
|
| 683 |
- } |
|
| 684 |
- } |
|
| 685 |
- return ls |
|
| 686 |
-} |
|
| 687 |
- |
|
| 688 |
-func unwrapVolume(v volume.Volume) volume.Volume {
|
|
| 689 |
- if vol, ok := v.(volumeWrapper); ok {
|
|
| 690 |
- return vol.Volume |
|
| 691 |
- } |
|
| 692 |
- |
|
| 693 |
- return v |
|
| 694 |
-} |
|
| 695 |
- |
|
| 696 |
-// Shutdown releases all resources used by the volume store |
|
| 697 |
-// It does not make any changes to volumes, drivers, etc. |
|
| 698 |
-func (s *VolumeStore) Shutdown() error {
|
|
| 699 |
- return s.db.Close() |
|
| 700 |
-} |
|
| 701 |
- |
|
| 702 |
-// GetDriverList gets the list of volume drivers from the configured volume driver |
|
| 703 |
-// store. |
|
| 704 |
-// TODO(@cpuguy83): This should be factored out into a separate service. |
|
| 705 |
-func (s *VolumeStore) GetDriverList() []string {
|
|
| 706 |
- return s.drivers.GetDriverList() |
|
| 707 |
-} |
| 708 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,379 +0,0 @@ |
| 1 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "fmt" |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "net" |
|
| 8 |
- "os" |
|
| 9 |
- "strings" |
|
| 10 |
- "testing" |
|
| 11 |
- |
|
| 12 |
- "github.com/docker/docker/volume" |
|
| 13 |
- volumedrivers "github.com/docker/docker/volume/drivers" |
|
| 14 |
- volumetestutils "github.com/docker/docker/volume/testutils" |
|
| 15 |
- "github.com/google/go-cmp/cmp" |
|
| 16 |
- "github.com/gotestyourself/gotestyourself/assert" |
|
| 17 |
- is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 18 |
-) |
|
| 19 |
- |
|
| 20 |
-func TestCreate(t *testing.T) {
|
|
| 21 |
- t.Parallel() |
|
| 22 |
- |
|
| 23 |
- s, cleanup := setupTest(t) |
|
| 24 |
- defer cleanup() |
|
| 25 |
- s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 26 |
- |
|
| 27 |
- v, err := s.Create("fake1", "fake", nil, nil)
|
|
| 28 |
- if err != nil {
|
|
| 29 |
- t.Fatal(err) |
|
| 30 |
- } |
|
| 31 |
- if v.Name() != "fake1" {
|
|
| 32 |
- t.Fatalf("Expected fake1 volume, got %v", v)
|
|
| 33 |
- } |
|
| 34 |
- if l, _, _ := s.List(); len(l) != 1 {
|
|
| 35 |
- t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
|
| 36 |
- } |
|
| 37 |
- |
|
| 38 |
- if _, err := s.Create("none", "none", nil, nil); err == nil {
|
|
| 39 |
- t.Fatalf("Expected unknown driver error, got nil")
|
|
| 40 |
- } |
|
| 41 |
- |
|
| 42 |
- _, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"}, nil)
|
|
| 43 |
- expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
|
|
| 44 |
- if err != nil && err.Error() != expected.Error() {
|
|
| 45 |
- t.Fatalf("Expected create fakeError: create error, got %v", err)
|
|
| 46 |
- } |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-func TestRemove(t *testing.T) {
|
|
| 50 |
- t.Parallel() |
|
| 51 |
- |
|
| 52 |
- s, cleanup := setupTest(t) |
|
| 53 |
- defer cleanup() |
|
| 54 |
- |
|
| 55 |
- s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 56 |
- s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
|
| 57 |
- |
|
| 58 |
- // doing string compare here since this error comes directly from the driver |
|
| 59 |
- expected := "no such volume" |
|
| 60 |
- if err := s.Remove(volumetestutils.NoopVolume{}); err == nil || !strings.Contains(err.Error(), expected) {
|
|
| 61 |
- t.Fatalf("Expected error %q, got %v", expected, err)
|
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- v, err := s.CreateWithRef("fake1", "fake", "fake", nil, nil)
|
|
| 65 |
- if err != nil {
|
|
| 66 |
- t.Fatal(err) |
|
| 67 |
- } |
|
| 68 |
- |
|
| 69 |
- if err := s.Remove(v); !IsInUse(err) {
|
|
| 70 |
- t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
|
| 71 |
- } |
|
| 72 |
- s.Dereference(v, "fake") |
|
| 73 |
- if err := s.Remove(v); err != nil {
|
|
| 74 |
- t.Fatal(err) |
|
| 75 |
- } |
|
| 76 |
- if l, _, _ := s.List(); len(l) != 0 {
|
|
| 77 |
- t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
|
|
| 78 |
- } |
|
| 79 |
-} |
|
| 80 |
- |
|
| 81 |
-func TestList(t *testing.T) {
|
|
| 82 |
- t.Parallel() |
|
| 83 |
- |
|
| 84 |
- dir, err := ioutil.TempDir("", "test-list")
|
|
| 85 |
- assert.NilError(t, err) |
|
| 86 |
- defer os.RemoveAll(dir) |
|
| 87 |
- |
|
| 88 |
- drivers := volumedrivers.NewStore(nil) |
|
| 89 |
- drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 90 |
- drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
|
|
| 91 |
- |
|
| 92 |
- s, err := New(dir, drivers) |
|
| 93 |
- assert.NilError(t, err) |
|
| 94 |
- |
|
| 95 |
- if _, err := s.Create("test", "fake", nil, nil); err != nil {
|
|
| 96 |
- t.Fatal(err) |
|
| 97 |
- } |
|
| 98 |
- if _, err := s.Create("test2", "fake2", nil, nil); err != nil {
|
|
| 99 |
- t.Fatal(err) |
|
| 100 |
- } |
|
| 101 |
- |
|
| 102 |
- ls, _, err := s.List() |
|
| 103 |
- if err != nil {
|
|
| 104 |
- t.Fatal(err) |
|
| 105 |
- } |
|
| 106 |
- if len(ls) != 2 {
|
|
| 107 |
- t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
|
| 108 |
- } |
|
| 109 |
- if err := s.Shutdown(); err != nil {
|
|
| 110 |
- t.Fatal(err) |
|
| 111 |
- } |
|
| 112 |
- |
|
| 113 |
- // and again with a new store |
|
| 114 |
- s, err = New(dir, drivers) |
|
| 115 |
- if err != nil {
|
|
| 116 |
- t.Fatal(err) |
|
| 117 |
- } |
|
| 118 |
- ls, _, err = s.List() |
|
| 119 |
- if err != nil {
|
|
| 120 |
- t.Fatal(err) |
|
| 121 |
- } |
|
| 122 |
- if len(ls) != 2 {
|
|
| 123 |
- t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
|
| 124 |
- } |
|
| 125 |
-} |
|
| 126 |
- |
|
| 127 |
-func TestFilterByDriver(t *testing.T) {
|
|
| 128 |
- t.Parallel() |
|
| 129 |
- s, cleanup := setupTest(t) |
|
| 130 |
- defer cleanup() |
|
| 131 |
- |
|
| 132 |
- s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 133 |
- s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
|
| 134 |
- |
|
| 135 |
- if _, err := s.Create("fake1", "fake", nil, nil); err != nil {
|
|
| 136 |
- t.Fatal(err) |
|
| 137 |
- } |
|
| 138 |
- if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
|
|
| 139 |
- t.Fatal(err) |
|
| 140 |
- } |
|
| 141 |
- if _, err := s.Create("fake3", "noop", nil, nil); err != nil {
|
|
| 142 |
- t.Fatal(err) |
|
| 143 |
- } |
|
| 144 |
- |
|
| 145 |
- if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
|
|
| 146 |
- t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
|
|
| 147 |
- } |
|
| 148 |
- |
|
| 149 |
- if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
|
|
| 150 |
- t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
|
| 151 |
- } |
|
| 152 |
-} |
|
| 153 |
- |
|
| 154 |
-func TestFilterByUsed(t *testing.T) {
|
|
| 155 |
- t.Parallel() |
|
| 156 |
- s, cleanup := setupTest(t) |
|
| 157 |
- defer cleanup() |
|
| 158 |
- |
|
| 159 |
- s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 160 |
- s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
|
| 161 |
- |
|
| 162 |
- if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil); err != nil {
|
|
| 163 |
- t.Fatal(err) |
|
| 164 |
- } |
|
| 165 |
- if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
|
|
| 166 |
- t.Fatal(err) |
|
| 167 |
- } |
|
| 168 |
- |
|
| 169 |
- vols, _, err := s.List() |
|
| 170 |
- if err != nil {
|
|
| 171 |
- t.Fatal(err) |
|
| 172 |
- } |
|
| 173 |
- |
|
| 174 |
- dangling := s.FilterByUsed(vols, false) |
|
| 175 |
- if len(dangling) != 1 {
|
|
| 176 |
- t.Fatalf("expected 1 dangling volume, got %v", len(dangling))
|
|
| 177 |
- } |
|
| 178 |
- if dangling[0].Name() != "fake2" {
|
|
| 179 |
- t.Fatalf("expected dangling volume fake2, got %s", dangling[0].Name())
|
|
| 180 |
- } |
|
| 181 |
- |
|
| 182 |
- used := s.FilterByUsed(vols, true) |
|
| 183 |
- if len(used) != 1 {
|
|
| 184 |
- t.Fatalf("expected 1 used volume, got %v", len(used))
|
|
| 185 |
- } |
|
| 186 |
- if used[0].Name() != "fake1" {
|
|
| 187 |
- t.Fatalf("expected used volume fake1, got %s", used[0].Name())
|
|
| 188 |
- } |
|
| 189 |
-} |
|
| 190 |
- |
|
| 191 |
-func TestDerefMultipleOfSameRef(t *testing.T) {
|
|
| 192 |
- t.Parallel() |
|
| 193 |
- s, cleanup := setupTest(t) |
|
| 194 |
- defer cleanup() |
|
| 195 |
- s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 196 |
- |
|
| 197 |
- v, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil)
|
|
| 198 |
- if err != nil {
|
|
| 199 |
- t.Fatal(err) |
|
| 200 |
- } |
|
| 201 |
- |
|
| 202 |
- if _, err := s.GetWithRef("fake1", "fake", "volReference"); err != nil {
|
|
| 203 |
- t.Fatal(err) |
|
| 204 |
- } |
|
| 205 |
- |
|
| 206 |
- s.Dereference(v, "volReference") |
|
| 207 |
- if err := s.Remove(v); err != nil {
|
|
| 208 |
- t.Fatal(err) |
|
| 209 |
- } |
|
| 210 |
-} |
|
| 211 |
- |
|
| 212 |
-func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
|
|
| 213 |
- t.Parallel() |
|
| 214 |
- s, cleanup := setupTest(t) |
|
| 215 |
- defer cleanup() |
|
| 216 |
- |
|
| 217 |
- vd := volumetestutils.NewFakeDriver("fake")
|
|
| 218 |
- s.drivers.Register(vd, "fake") |
|
| 219 |
- |
|
| 220 |
- // Create a volume in the driver directly |
|
| 221 |
- if _, err := vd.Create("foo", nil); err != nil {
|
|
| 222 |
- t.Fatal(err) |
|
| 223 |
- } |
|
| 224 |
- |
|
| 225 |
- v, err := s.Create("foo", "fake", nil, map[string]string{"hello": "world"})
|
|
| 226 |
- if err != nil {
|
|
| 227 |
- t.Fatal(err) |
|
| 228 |
- } |
|
| 229 |
- |
|
| 230 |
- switch dv := v.(type) {
|
|
| 231 |
- case volume.DetailedVolume: |
|
| 232 |
- if dv.Labels()["hello"] != "world" {
|
|
| 233 |
- t.Fatalf("labels don't match")
|
|
| 234 |
- } |
|
| 235 |
- default: |
|
| 236 |
- t.Fatalf("got unexpected type: %T", v)
|
|
| 237 |
- } |
|
| 238 |
-} |
|
| 239 |
- |
|
| 240 |
-func TestDefererencePluginOnCreateError(t *testing.T) {
|
|
| 241 |
- t.Parallel() |
|
| 242 |
- |
|
| 243 |
- var ( |
|
| 244 |
- l net.Listener |
|
| 245 |
- err error |
|
| 246 |
- ) |
|
| 247 |
- |
|
| 248 |
- for i := 32768; l == nil && i < 40000; i++ {
|
|
| 249 |
- l, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", i))
|
|
| 250 |
- } |
|
| 251 |
- if l == nil {
|
|
| 252 |
- t.Fatalf("could not create listener: %v", err)
|
|
| 253 |
- } |
|
| 254 |
- defer l.Close() |
|
| 255 |
- |
|
| 256 |
- s, cleanup := setupTest(t) |
|
| 257 |
- defer cleanup() |
|
| 258 |
- |
|
| 259 |
- d := volumetestutils.NewFakeDriver("TestDefererencePluginOnCreateError")
|
|
| 260 |
- p, err := volumetestutils.MakeFakePlugin(d, l) |
|
| 261 |
- if err != nil {
|
|
| 262 |
- t.Fatal(err) |
|
| 263 |
- } |
|
| 264 |
- |
|
| 265 |
- pg := volumetestutils.NewFakePluginGetter(p) |
|
| 266 |
- s.drivers = volumedrivers.NewStore(pg) |
|
| 267 |
- |
|
| 268 |
- // create a good volume so we have a plugin reference |
|
| 269 |
- _, err = s.Create("fake1", d.Name(), nil, nil)
|
|
| 270 |
- if err != nil {
|
|
| 271 |
- t.Fatal(err) |
|
| 272 |
- } |
|
| 273 |
- |
|
| 274 |
- // Now create another one expecting an error |
|
| 275 |
- _, err = s.Create("fake2", d.Name(), map[string]string{"error": "some error"}, nil)
|
|
| 276 |
- if err == nil || !strings.Contains(err.Error(), "some error") {
|
|
| 277 |
- t.Fatalf("expected an error on create: %v", err)
|
|
| 278 |
- } |
|
| 279 |
- |
|
| 280 |
- // There should be only 1 plugin reference |
|
| 281 |
- if refs := volumetestutils.FakeRefs(p); refs != 1 {
|
|
| 282 |
- t.Fatalf("expected 1 plugin reference, got: %d", refs)
|
|
| 283 |
- } |
|
| 284 |
-} |
|
| 285 |
- |
|
| 286 |
-func TestRefDerefRemove(t *testing.T) {
|
|
| 287 |
- t.Parallel() |
|
| 288 |
- |
|
| 289 |
- driverName := "test-ref-deref-remove" |
|
| 290 |
- s, cleanup := setupTest(t) |
|
| 291 |
- defer cleanup() |
|
| 292 |
- s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 293 |
- |
|
| 294 |
- v, err := s.CreateWithRef("test", driverName, "test-ref", nil, nil)
|
|
| 295 |
- assert.NilError(t, err) |
|
| 296 |
- |
|
| 297 |
- err = s.Remove(v) |
|
| 298 |
- assert.Assert(t, is.ErrorContains(err, "")) |
|
| 299 |
- assert.Equal(t, errVolumeInUse, err.(*OpErr).Err) |
|
| 300 |
- |
|
| 301 |
- s.Dereference(v, "test-ref") |
|
| 302 |
- err = s.Remove(v) |
|
| 303 |
- assert.NilError(t, err) |
|
| 304 |
-} |
|
| 305 |
- |
|
| 306 |
-func TestGet(t *testing.T) {
|
|
| 307 |
- t.Parallel() |
|
| 308 |
- |
|
| 309 |
- driverName := "test-get" |
|
| 310 |
- s, cleanup := setupTest(t) |
|
| 311 |
- defer cleanup() |
|
| 312 |
- s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 313 |
- |
|
| 314 |
- _, err := s.Get("not-exist")
|
|
| 315 |
- assert.Assert(t, is.ErrorContains(err, "")) |
|
| 316 |
- assert.Equal(t, errNoSuchVolume, err.(*OpErr).Err) |
|
| 317 |
- |
|
| 318 |
- v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"})
|
|
| 319 |
- assert.NilError(t, err) |
|
| 320 |
- |
|
| 321 |
- v2, err := s.Get("test")
|
|
| 322 |
- assert.NilError(t, err) |
|
| 323 |
- assert.DeepEqual(t, v1, v2, cmpVolume) |
|
| 324 |
- |
|
| 325 |
- dv := v2.(volume.DetailedVolume) |
|
| 326 |
- assert.Equal(t, "1", dv.Labels()["a"]) |
|
| 327 |
- |
|
| 328 |
- err = s.Remove(v1) |
|
| 329 |
- assert.NilError(t, err) |
|
| 330 |
-} |
|
| 331 |
- |
|
| 332 |
-func TestGetWithRef(t *testing.T) {
|
|
| 333 |
- t.Parallel() |
|
| 334 |
- |
|
| 335 |
- driverName := "test-get-with-ref" |
|
| 336 |
- s, cleanup := setupTest(t) |
|
| 337 |
- defer cleanup() |
|
| 338 |
- s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName) |
|
| 339 |
- |
|
| 340 |
- _, err := s.GetWithRef("not-exist", driverName, "test-ref")
|
|
| 341 |
- assert.Assert(t, is.ErrorContains(err, "")) |
|
| 342 |
- |
|
| 343 |
- v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"})
|
|
| 344 |
- assert.NilError(t, err) |
|
| 345 |
- |
|
| 346 |
- v2, err := s.GetWithRef("test", driverName, "test-ref")
|
|
| 347 |
- assert.NilError(t, err) |
|
| 348 |
- assert.DeepEqual(t, v1, v2, cmpVolume) |
|
| 349 |
- |
|
| 350 |
- err = s.Remove(v2) |
|
| 351 |
- assert.Assert(t, is.ErrorContains(err, "")) |
|
| 352 |
- assert.Equal(t, errVolumeInUse, err.(*OpErr).Err) |
|
| 353 |
- |
|
| 354 |
- s.Dereference(v2, "test-ref") |
|
| 355 |
- err = s.Remove(v2) |
|
| 356 |
- assert.NilError(t, err) |
|
| 357 |
-} |
|
| 358 |
- |
|
| 359 |
-var cmpVolume = cmp.AllowUnexported(volumetestutils.FakeVolume{}, volumeWrapper{})
|
|
| 360 |
- |
|
| 361 |
-func setupTest(t *testing.T) (*VolumeStore, func()) {
|
|
| 362 |
- t.Helper() |
|
| 363 |
- |
|
| 364 |
- dirName := strings.Replace(t.Name(), string(os.PathSeparator), "_", -1) |
|
| 365 |
- dir, err := ioutil.TempDir("", dirName)
|
|
| 366 |
- assert.NilError(t, err) |
|
| 367 |
- |
|
| 368 |
- cleanup := func() {
|
|
| 369 |
- err := os.RemoveAll(dir) |
|
| 370 |
- assert.Check(t, err) |
|
| 371 |
- } |
|
| 372 |
- |
|
| 373 |
- s, err := New(dir, volumedrivers.NewStore(nil)) |
|
| 374 |
- assert.Check(t, err) |
|
| 375 |
- return s, func() {
|
|
| 376 |
- s.Shutdown() |
|
| 377 |
- cleanup() |
|
| 378 |
- } |
|
| 379 |
-} |
| 380 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,9 +0,0 @@ |
| 1 |
-// +build linux freebsd |
|
| 2 |
- |
|
| 3 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 4 |
- |
|
| 5 |
-// normalizeVolumeName is a platform specific function to normalize the name |
|
| 6 |
-// of a volume. This is a no-op on Unix-like platforms |
|
| 7 |
-func normalizeVolumeName(name string) string {
|
|
| 8 |
- return name |
|
| 9 |
-} |
| 10 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,12 +0,0 @@ |
| 1 |
-package store // import "github.com/docker/docker/volume/store" |
|
| 2 |
- |
|
| 3 |
-import "strings" |
|
| 4 |
- |
|
| 5 |
-// normalizeVolumeName is a platform specific function to normalize the name |
|
| 6 |
-// of a volume. On Windows, as NTFS is case insensitive, under |
|
| 7 |
-// c:\ProgramData\Docker\Volumes\, the folders John and john would be synonymous. |
|
| 8 |
-// Hence we can't allow the volume "John" and "john" to be created as separate |
|
| 9 |
-// volumes. |
|
| 10 |
-func normalizeVolumeName(name string) string {
|
|
| 11 |
- return strings.ToLower(name) |
|
| 12 |
-} |
| ... | ... |
@@ -64,7 +64,9 @@ func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
|
| 64 | 64 |
func (FakeVolume) Unmount(_ string) error { return nil }
|
| 65 | 65 |
|
| 66 | 66 |
// Status provides low-level details about the volume |
| 67 |
-func (FakeVolume) Status() map[string]interface{} { return nil }
|
|
| 67 |
+func (FakeVolume) Status() map[string]interface{} {
|
|
| 68 |
+ return map[string]interface{}{"datakey": "datavalue"}
|
|
| 69 |
+} |
|
| 68 | 70 |
|
| 69 | 71 |
// CreatedAt provides the time the volume (directory) was created at |
| 70 | 72 |
func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
|