mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Extract volume interaction to a volumes service
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>
This commit is contained in:
		
							parent
							
								
									9c2c887b12
								
							
						
					
					
						commit
						e4b6adc88e
					
				
					 50 changed files with 1533 additions and 639 deletions
				
			
		| 
						 | 
				
			
			@ -3,6 +3,7 @@ package volume // import "github.com/docker/docker/api/server/router/volume"
 | 
			
		|||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	// TODO return types need to be refactored into pkg
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,9 +12,9 @@ import (
 | 
			
		|||
// Backend is the methods that need to be implemented to provide
 | 
			
		||||
// volume specific functionality
 | 
			
		||||
type Backend interface {
 | 
			
		||||
	Volumes(filter string) ([]*types.Volume, []string, error)
 | 
			
		||||
	VolumeInspect(name string) (*types.Volume, error)
 | 
			
		||||
	VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error)
 | 
			
		||||
	VolumeRm(name string, force bool) error
 | 
			
		||||
	VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error)
 | 
			
		||||
	List(ctx context.Context, filter filters.Args) ([]*types.Volume, []string, error)
 | 
			
		||||
	Get(ctx context.Context, name string, opts ...opts.GetOption) (*types.Volume, error)
 | 
			
		||||
	Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error)
 | 
			
		||||
	Remove(ctx context.Context, name string, opts ...opts.RemoveOption) error
 | 
			
		||||
	Prune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ package volume // import "github.com/docker/docker/api/server/router/volume"
 | 
			
		|||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +10,8 @@ import (
 | 
			
		|||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	volumetypes "github.com/docker/docker/api/types/volume"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumes, warnings, err := v.backend.Volumes(r.Form.Get("filters"))
 | 
			
		||||
	filters, err := filters.FromJSON(r.Form.Get("filters"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errdefs.InvalidParameter(errors.Wrap(err, "error reading volume filters"))
 | 
			
		||||
	}
 | 
			
		||||
	volumes, warnings, err := v.backend.List(ctx, filters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +35,7 @@ func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWrite
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volume, err := v.backend.VolumeInspect(vars["name"])
 | 
			
		||||
	volume, err := v.backend.Get(ctx, vars["name"], opts.WithGetResolveStatus)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +59,7 @@ func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWri
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volume, err := v.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels)
 | 
			
		||||
	volume, err := v.backend.Create(ctx, req.Name, req.Driver, opts.WithCreateOptions(req.DriverOpts), opts.WithCreateLabels(req.Labels))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +71,7 @@ func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter,
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	force := httputils.BoolValue(r, "force")
 | 
			
		||||
	if err := v.backend.VolumeRm(vars["name"], force); err != nil {
 | 
			
		||||
	if err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	w.WriteHeader(http.StatusNoContent)
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +88,7 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pruneReport, err := v.backend.VolumesPrune(ctx, pruneFilters)
 | 
			
		||||
	pruneReport, err := v.backend.Prune(ctx, pruneFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -453,7 +453,7 @@ func initRouter(opts routerOptions) {
 | 
			
		|||
		container.NewRouter(opts.daemon, decoder),
 | 
			
		||||
		image.NewRouter(opts.daemon.ImageService()),
 | 
			
		||||
		systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildCache),
 | 
			
		||||
		volume.NewRouter(opts.daemon),
 | 
			
		||||
		volume.NewRouter(opts.daemon.VolumesService()),
 | 
			
		||||
		build.NewRouter(opts.buildBackend, opts.daemon),
 | 
			
		||||
		sessionrouter.NewRouter(opts.sessionManager),
 | 
			
		||||
		swarmrouter.NewRouter(opts.cluster),
 | 
			
		||||
| 
						 | 
				
			
			@ -595,6 +595,7 @@ func createAndStartCluster(cli *DaemonCli, d *daemon.Daemon) (*cluster.Cluster,
 | 
			
		|||
		Root:                   cli.Config.Root,
 | 
			
		||||
		Name:                   name,
 | 
			
		||||
		Backend:                d,
 | 
			
		||||
		VolumeBackend:          d.VolumesService(),
 | 
			
		||||
		ImageBackend:           d.ImageService(),
 | 
			
		||||
		PluginBackend:          d.PluginManager(),
 | 
			
		||||
		NetworkSubnetsProvider: d,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,7 +127,7 @@ func (container *Container) CopyImagePathContent(v volume.Volume, destination st
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = ioutil.ReadDir(rootfs); err != nil {
 | 
			
		||||
	if _, err := os.Stat(rootfs); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,6 +85,7 @@ type Config struct {
 | 
			
		|||
	Backend                executorpkg.Backend
 | 
			
		||||
	ImageBackend           executorpkg.ImageBackend
 | 
			
		||||
	PluginBackend          plugin.Backend
 | 
			
		||||
	VolumeBackend          executorpkg.VolumeBackend
 | 
			
		||||
	NetworkSubnetsProvider NetworkSubnetsProvider
 | 
			
		||||
 | 
			
		||||
	// DefaultAdvertiseAddr is the default host/IP or network interface to use
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ import (
 | 
			
		|||
	clustertypes "github.com/docker/docker/daemon/cluster/provider"
 | 
			
		||||
	networkSettings "github.com/docker/docker/daemon/network"
 | 
			
		||||
	"github.com/docker/docker/plugin"
 | 
			
		||||
	volumeopts "github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	"github.com/docker/libnetwork/cluster"
 | 
			
		||||
	networktypes "github.com/docker/libnetwork/types"
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +48,6 @@ type Backend interface {
 | 
			
		|||
	SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error
 | 
			
		||||
	SetContainerConfigReferences(name string, refs []*swarmtypes.ConfigReference) error
 | 
			
		||||
	SystemInfo() (*types.Info, error)
 | 
			
		||||
	VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error)
 | 
			
		||||
	Containers(config *types.ContainerListOptions) ([]*types.Container, error)
 | 
			
		||||
	SetNetworkBootstrapKeys([]*networktypes.EncryptionKey) error
 | 
			
		||||
	DaemonJoinsCluster(provider cluster.Provider)
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +62,11 @@ type Backend interface {
 | 
			
		|||
	GetAttachmentStore() *networkSettings.AttachmentStore
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumeBackend is used by an executor to perform volume operations
 | 
			
		||||
type VolumeBackend interface {
 | 
			
		||||
	Create(ctx context.Context, name, driverName string, opts ...volumeopts.CreateOption) (*types.Volume, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ImageBackend is used by an executor to perform image operations
 | 
			
		||||
type ImageBackend interface {
 | 
			
		||||
	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/daemon"
 | 
			
		||||
	"github.com/docker/docker/daemon/cluster/convert"
 | 
			
		||||
	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 | 
			
		||||
	volumeopts "github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	"github.com/docker/swarmkit/agent/exec"
 | 
			
		||||
	"github.com/docker/swarmkit/api"
 | 
			
		||||
| 
						 | 
				
			
			@ -36,23 +37,25 @@ import (
 | 
			
		|||
// are mostly naked calls to the client API, seeded with information from
 | 
			
		||||
// containerConfig.
 | 
			
		||||
type containerAdapter struct {
 | 
			
		||||
	backend      executorpkg.Backend
 | 
			
		||||
	imageBackend executorpkg.ImageBackend
 | 
			
		||||
	container    *containerConfig
 | 
			
		||||
	dependencies exec.DependencyGetter
 | 
			
		||||
	backend       executorpkg.Backend
 | 
			
		||||
	imageBackend  executorpkg.ImageBackend
 | 
			
		||||
	volumeBackend executorpkg.VolumeBackend
 | 
			
		||||
	container     *containerConfig
 | 
			
		||||
	dependencies  exec.DependencyGetter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newContainerAdapter(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*containerAdapter, error) {
 | 
			
		||||
func newContainerAdapter(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*containerAdapter, error) {
 | 
			
		||||
	ctnr, err := newContainerConfig(task, node)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &containerAdapter{
 | 
			
		||||
		container:    ctnr,
 | 
			
		||||
		backend:      b,
 | 
			
		||||
		imageBackend: i,
 | 
			
		||||
		dependencies: dependencies,
 | 
			
		||||
		container:     ctnr,
 | 
			
		||||
		backend:       b,
 | 
			
		||||
		imageBackend:  i,
 | 
			
		||||
		volumeBackend: v,
 | 
			
		||||
		dependencies:  dependencies,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -388,7 +391,10 @@ func (c *containerAdapter) createVolumes(ctx context.Context) error {
 | 
			
		|||
		req := c.container.volumeCreateRequest(&mount)
 | 
			
		||||
 | 
			
		||||
		// Check if this volume exists on the engine
 | 
			
		||||
		if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil {
 | 
			
		||||
		if _, err := c.volumeBackend.Create(ctx, req.Name, req.Driver,
 | 
			
		||||
			volumeopts.WithCreateOptions(req.DriverOpts),
 | 
			
		||||
			volumeopts.WithCreateLabels(req.Labels),
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			// TODO(amitshukla): Today, volume create through the engine api does not return an error
 | 
			
		||||
			// when the named volume with the same parameters already exists.
 | 
			
		||||
			// It returns an error if the driver name is different - that is a valid error
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,8 +21,8 @@ type networkAttacherController struct {
 | 
			
		|||
	closed  chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newNetworkAttacherController(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*networkAttacherController, error) {
 | 
			
		||||
	adapter, err := newContainerAdapter(b, i, task, node, dependencies)
 | 
			
		||||
func newNetworkAttacherController(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*networkAttacherController, error) {
 | 
			
		||||
	adapter, err := newContainerAdapter(b, i, v, task, node, dependencies)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,8 +40,8 @@ type controller struct {
 | 
			
		|||
var _ exec.Controller = &controller{}
 | 
			
		||||
 | 
			
		||||
// NewController returns a docker exec runner for the provided task.
 | 
			
		||||
func newController(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*controller, error) {
 | 
			
		||||
	adapter, err := newContainerAdapter(b, i, task, node, dependencies)
 | 
			
		||||
func newController(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*controller, error) {
 | 
			
		||||
	adapter, err := newContainerAdapter(b, i, v, task, node, dependencies)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,17 +28,19 @@ type executor struct {
 | 
			
		|||
	backend       executorpkg.Backend
 | 
			
		||||
	imageBackend  executorpkg.ImageBackend
 | 
			
		||||
	pluginBackend plugin.Backend
 | 
			
		||||
	volumeBackend executorpkg.VolumeBackend
 | 
			
		||||
	dependencies  exec.DependencyManager
 | 
			
		||||
	mutex         sync.Mutex // This mutex protects the following node field
 | 
			
		||||
	node          *api.NodeDescription
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewExecutor returns an executor from the docker client.
 | 
			
		||||
func NewExecutor(b executorpkg.Backend, p plugin.Backend, i executorpkg.ImageBackend) exec.Executor {
 | 
			
		||||
func NewExecutor(b executorpkg.Backend, p plugin.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend) exec.Executor {
 | 
			
		||||
	return &executor{
 | 
			
		||||
		backend:       b,
 | 
			
		||||
		pluginBackend: p,
 | 
			
		||||
		imageBackend:  i,
 | 
			
		||||
		volumeBackend: v,
 | 
			
		||||
		dependencies:  agent.NewDependencyManager(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +213,7 @@ func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
 | 
			
		|||
	e.mutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	if t.Spec.GetAttachment() != nil {
 | 
			
		||||
		return newNetworkAttacherController(e.backend, e.imageBackend, t, nodeDescription, dependencyGetter)
 | 
			
		||||
		return newNetworkAttacherController(e.backend, e.imageBackend, e.volumeBackend, t, nodeDescription, dependencyGetter)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var ctlr exec.Controller
 | 
			
		||||
| 
						 | 
				
			
			@ -240,7 +242,7 @@ func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
 | 
			
		|||
			return ctlr, fmt.Errorf("unsupported runtime type: %q", runtimeKind)
 | 
			
		||||
		}
 | 
			
		||||
	case *api.TaskSpec_Container:
 | 
			
		||||
		c, err := newController(e.backend, e.imageBackend, t, nodeDescription, dependencyGetter)
 | 
			
		||||
		c, err := newController(e.backend, e.imageBackend, e.volumeBackend, t, nodeDescription, dependencyGetter)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ctlr, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,7 @@ func TestHealthStates(t *testing.T) {
 | 
			
		|||
		EventsService: e,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	controller, err := newController(daemon, nil, task, nil, nil)
 | 
			
		||||
	controller, err := newController(daemon, nil, nil, task, nil, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("create controller fail %v", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
func newTestControllerWithMount(m api.Mount) (*controller, error) {
 | 
			
		||||
	return newController(&daemon.Daemon{}, nil, &api.Task{
 | 
			
		||||
	return newController(&daemon.Daemon{}, nil, nil, &api.Task{
 | 
			
		||||
		ID:        stringid.GenerateRandomID(),
 | 
			
		||||
		ServiceID: stringid.GenerateRandomID(),
 | 
			
		||||
		Spec: api.TaskSpec{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -123,7 +123,9 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
 | 
			
		|||
		Executor: container.NewExecutor(
 | 
			
		||||
			n.cluster.config.Backend,
 | 
			
		||||
			n.cluster.config.PluginBackend,
 | 
			
		||||
			n.cluster.config.ImageBackend),
 | 
			
		||||
			n.cluster.config.ImageBackend,
 | 
			
		||||
			n.cluster.config.VolumeBackend,
 | 
			
		||||
		),
 | 
			
		||||
		HeartbeatTick: n.cluster.config.RaftHeartbeatTick,
 | 
			
		||||
		// Recommended value in etcd/raft is 10 x (HeartbeatTick).
 | 
			
		||||
		// Lower values were seen to have caused instability because of
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,8 +7,6 @@ import (
 | 
			
		|||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	containertypes "github.com/docker/docker/api/types/container"
 | 
			
		||||
	networktypes "github.com/docker/docker/api/types/network"
 | 
			
		||||
| 
						 | 
				
			
			@ -16,10 +14,10 @@ import (
 | 
			
		|||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/opencontainers/selinux/go-selinux/label"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -255,24 +253,6 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig)
 | 
			
		|||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumeCreate creates a volume with the specified name, driver, and opts
 | 
			
		||||
// This is called directly from the Engine API
 | 
			
		||||
func (daemon *Daemon) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) {
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		name = stringid.GenerateNonCryptoID()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := daemon.volumes.Create(name, driverName, opts, labels)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
 | 
			
		||||
	apiV := volumeToAPIType(v)
 | 
			
		||||
	apiV.Mountpoint = v.Path()
 | 
			
		||||
	return apiV, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error {
 | 
			
		||||
	if img != nil && img.Config != nil {
 | 
			
		||||
		if err := merge(config, img.Config); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
package daemon // import "github.com/docker/docker/daemon"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +12,7 @@ import (
 | 
			
		|||
	mounttypes "github.com/docker/docker/api/types/mount"
 | 
			
		||||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	volumeopts "github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/opencontainers/selinux/go-selinux/label"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -46,16 +48,16 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
 | 
			
		|||
			return fmt.Errorf("cannot mount volume over existing file, file exists %s", path)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil, nil)
 | 
			
		||||
		v, err := daemon.volumes.Create(context.TODO(), name, hostConfig.VolumeDriver, volumeopts.WithCreateReference(container.ID))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := label.Relabel(v.Path(), container.MountLabel, true); err != nil {
 | 
			
		||||
		if err := label.Relabel(v.Mountpoint, container.MountLabel, true); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		container.AddMountPointWithVolume(destination, v, true)
 | 
			
		||||
		container.AddMountPointWithVolume(destination, &volumeWrapper{v: v, s: daemon.volumes}, true)
 | 
			
		||||
	}
 | 
			
		||||
	return daemon.populateVolumes(container)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package daemon // import "github.com/docker/docker/daemon"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +9,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	volumemounts "github.com/docker/docker/volume/mounts"
 | 
			
		||||
	volumeopts "github.com/docker/docker/volume/service/opts"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// createContainerOSSpecificSettings performs host-OS specific container create functionality
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +51,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
 | 
			
		|||
 | 
			
		||||
		// Create the volume in the volume driver. If it doesn't exist,
 | 
			
		||||
		// a new one will be created.
 | 
			
		||||
		v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil, nil)
 | 
			
		||||
		v, err := daemon.volumes.Create(context.TODO(), mp.Name, volumeDriver, volumeopts.WithCreateReference(container.ID))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +87,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
 | 
			
		|||
		//	}
 | 
			
		||||
 | 
			
		||||
		// Add it to container.MountPoints
 | 
			
		||||
		container.AddMountPointWithVolume(mp.Destination, v, mp.RW)
 | 
			
		||||
		container.AddMountPointWithVolume(mp.Destination, &volumeWrapper{v: v, s: daemon.volumes}, mp.RW)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,9 +52,7 @@ import (
 | 
			
		|||
	refstore "github.com/docker/docker/reference"
 | 
			
		||||
	"github.com/docker/docker/registry"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/local"
 | 
			
		||||
	"github.com/docker/docker/volume/store"
 | 
			
		||||
	volumesservice "github.com/docker/docker/volume/service"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	"github.com/docker/libnetwork/cluster"
 | 
			
		||||
	nwconfig "github.com/docker/libnetwork/config"
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +81,7 @@ type Daemon struct {
 | 
			
		|||
	RegistryService   registry.Service
 | 
			
		||||
	EventsService     *events.Events
 | 
			
		||||
	netController     libnetwork.NetworkController
 | 
			
		||||
	volumes           *store.VolumeStore
 | 
			
		||||
	volumes           *volumesservice.VolumesService
 | 
			
		||||
	discoveryWatcher  discovery.Reloader
 | 
			
		||||
	root              string
 | 
			
		||||
	seccompEnabled    bool
 | 
			
		||||
| 
						 | 
				
			
			@ -784,8 +782,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Configure the volumes driver
 | 
			
		||||
	volStore, err := d.configureVolumes(rootIDs)
 | 
			
		||||
	d.volumes, err = volumesservice.NewVolumeService(config.Root, d.PluginStore, rootIDs, d)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -855,7 +852,6 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 | 
			
		|||
	d.statsCollector = d.newStatsCollector(1 * time.Second)
 | 
			
		||||
 | 
			
		||||
	d.EventsService = events.New()
 | 
			
		||||
	d.volumes = volStore
 | 
			
		||||
	d.root = config.Root
 | 
			
		||||
	d.idMappings = idMappings
 | 
			
		||||
	d.seccompEnabled = sysInfo.Seccomp
 | 
			
		||||
| 
						 | 
				
			
			@ -1144,18 +1140,6 @@ func setDefaultMtu(conf *config.Config) {
 | 
			
		|||
	conf.Mtu = config.DefaultNetworkMtu
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (daemon *Daemon) configureVolumes(rootIDs idtools.IDPair) (*store.VolumeStore, error) {
 | 
			
		||||
	volumeDriver, err := local.New(daemon.configStore.Root, rootIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	drivers := volumedrivers.NewStore(daemon.PluginStore)
 | 
			
		||||
	if !drivers.Register(volumeDriver, volumeDriver.Name()) {
 | 
			
		||||
		return nil, errors.New("local volume driver could not be registered")
 | 
			
		||||
	}
 | 
			
		||||
	return store.New(daemon.configStore.Root, drivers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsShuttingDown tells whether the daemon is shutting down or not
 | 
			
		||||
func (daemon *Daemon) IsShuttingDown() bool {
 | 
			
		||||
	return daemon.shutdown
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,9 +13,7 @@ import (
 | 
			
		|||
	_ "github.com/docker/docker/pkg/discovery/memory"
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/truncindex"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/local"
 | 
			
		||||
	"github.com/docker/docker/volume/store"
 | 
			
		||||
	volumesservice "github.com/docker/docker/volume/service"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	"github.com/gotestyourself/gotestyourself/assert"
 | 
			
		||||
| 
						 | 
				
			
			@ -120,18 +118,10 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
 | 
			
		|||
		repository: tmp,
 | 
			
		||||
		root:       tmp,
 | 
			
		||||
	}
 | 
			
		||||
	drivers := volumedrivers.NewStore(nil)
 | 
			
		||||
	daemon.volumes, err = store.New(tmp, drivers)
 | 
			
		||||
	daemon.volumes, err = volumesservice.NewVolumeService(tmp, nil, idtools.IDPair{UID: 0, GID: 0}, daemon)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumesDriver, err := local.New(tmp, idtools.IDPair{UID: 0, GID: 0})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	drivers.Register(volumesDriver, volumesDriver.Name())
 | 
			
		||||
 | 
			
		||||
	return daemon, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,6 @@ import (
 | 
			
		|||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumestore "github.com/docker/docker/volume/store"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -152,35 +150,3 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
 | 
			
		|||
	daemon.LogContainerEvent(container, "destroy")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumeRm removes the volume with the given name.
 | 
			
		||||
// If the volume is referenced by a container it is not removed
 | 
			
		||||
// This is called directly from the Engine API
 | 
			
		||||
func (daemon *Daemon) VolumeRm(name string, force bool) error {
 | 
			
		||||
	v, err := daemon.volumes.Get(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if force && volumestore.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = daemon.volumeRm(v)
 | 
			
		||||
	if err != nil && volumestore.IsInUse(err) {
 | 
			
		||||
		return errdefs.Conflict(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err == nil || force {
 | 
			
		||||
		daemon.volumes.Purge(name)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (daemon *Daemon) volumeRm(v volume.Volume) error {
 | 
			
		||||
	if err := daemon.volumes.Remove(v); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "unable to remove volume")
 | 
			
		||||
	}
 | 
			
		||||
	daemon.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,9 +7,6 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/pkg/directory"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SystemDiskUsage returns information about the daemon data disk usage
 | 
			
		||||
| 
						 | 
				
			
			@ -34,39 +31,11 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
 | 
			
		|||
		return nil, fmt.Errorf("failed to retrieve image list: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumes, err := daemon.volumes.FilterByDriver(volume.DefaultDriverName)
 | 
			
		||||
	localVolumes, err := daemon.volumes.LocalVolumesSize(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var allVolumes []*types.Volume
 | 
			
		||||
	for _, v := range volumes {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			return nil, ctx.Err()
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
		if d, ok := v.(volume.DetailedVolume); ok {
 | 
			
		||||
			if len(d.Options()) > 0 {
 | 
			
		||||
				// skip local volumes with mount options since these could have external
 | 
			
		||||
				// mounted filesystems that will be slow to enumerate.
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		name := v.Name()
 | 
			
		||||
		refs := daemon.volumes.Refs(v)
 | 
			
		||||
 | 
			
		||||
		tv := volumeToAPIType(v)
 | 
			
		||||
		sz, err := directory.Size(ctx, v.Path())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Warnf("failed to determine size of volume %v", name)
 | 
			
		||||
			sz = -1
 | 
			
		||||
		}
 | 
			
		||||
		tv.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(len(refs))}
 | 
			
		||||
		allVolumes = append(allVolumes, tv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	allLayersSize, err := daemon.imageService.LayerDiskUsage(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +44,7 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
 | 
			
		|||
	return &types.DiskUsage{
 | 
			
		||||
		LayersSize: allLayersSize,
 | 
			
		||||
		Containers: allContainers,
 | 
			
		||||
		Volumes:    allVolumes,
 | 
			
		||||
		Volumes:    localVolumes,
 | 
			
		||||
		Images:     allImages,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,10 +18,6 @@ func containerNotFound(id string) error {
 | 
			
		|||
	return objNotFoundError{"container", id}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func volumeNotFound(id string) error {
 | 
			
		||||
	return objNotFoundError{"volume", id}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type objNotFoundError struct {
 | 
			
		||||
	object string
 | 
			
		||||
	id     string
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/layer"
 | 
			
		||||
	"github.com/opencontainers/go-digest"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +27,7 @@ var imagesAcceptedFilters = map[string]bool{
 | 
			
		|||
 | 
			
		||||
// errPruneRunning is returned when a prune request is received while
 | 
			
		||||
// one is in progress
 | 
			
		||||
var errPruneRunning = fmt.Errorf("a prune operation is already running")
 | 
			
		||||
var errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
 | 
			
		||||
 | 
			
		||||
// ImagesPrune removes unused images
 | 
			
		||||
func (i *ImageService) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,6 @@ import (
 | 
			
		|||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/daemon/network"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	volumestore "github.com/docker/docker/volume/store"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -236,22 +235,6 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, err
 | 
			
		|||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumeInspect looks up a volume by name. An error is returned if
 | 
			
		||||
// the volume cannot be found.
 | 
			
		||||
func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
 | 
			
		||||
	v, err := daemon.volumes.Get(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if volumestore.IsNotExist(err) {
 | 
			
		||||
			return nil, volumeNotFound(name)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, errdefs.System(err)
 | 
			
		||||
	}
 | 
			
		||||
	apiV := volumeToAPIType(v)
 | 
			
		||||
	apiV.Mountpoint = v.Path()
 | 
			
		||||
	apiV.Status = v.Status()
 | 
			
		||||
	return apiV, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings {
 | 
			
		||||
	result := &v1p20.NetworkSettings{
 | 
			
		||||
		NetworkSettingsBase: types.NetworkSettingsBase{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,19 +12,11 @@ import (
 | 
			
		|||
	"github.com/docker/docker/daemon/images"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var acceptedVolumeFilterTags = map[string]bool{
 | 
			
		||||
	"dangling": true,
 | 
			
		||||
	"name":     true,
 | 
			
		||||
	"driver":   true,
 | 
			
		||||
	"label":    true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var acceptedPsFilterTags = map[string]bool{
 | 
			
		||||
	"ancestor":  true,
 | 
			
		||||
	"before":    true,
 | 
			
		||||
| 
						 | 
				
			
			@ -605,87 +597,6 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
 | 
			
		|||
	return &c, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Volumes lists known volumes, using the filter to restrict the range
 | 
			
		||||
// of volumes returned.
 | 
			
		||||
func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		volumesOut []*types.Volume
 | 
			
		||||
	)
 | 
			
		||||
	volFilters, err := filters.FromJSON(filter)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumes, warnings, err := daemon.volumes.List()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filterVolumes, err := daemon.filterVolumes(volumes, volFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range filterVolumes {
 | 
			
		||||
		apiV := volumeToAPIType(v)
 | 
			
		||||
		if vv, ok := v.(interface {
 | 
			
		||||
			CachedPath() string
 | 
			
		||||
		}); ok {
 | 
			
		||||
			apiV.Mountpoint = vv.CachedPath()
 | 
			
		||||
		} else {
 | 
			
		||||
			apiV.Mountpoint = v.Path()
 | 
			
		||||
		}
 | 
			
		||||
		volumesOut = append(volumesOut, apiV)
 | 
			
		||||
	}
 | 
			
		||||
	return volumesOut, warnings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// filterVolumes filters volume list according to user specified filter
 | 
			
		||||
// and returns user chosen volumes
 | 
			
		||||
func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ([]volume.Volume, error) {
 | 
			
		||||
	// if filter is empty, return original volume list
 | 
			
		||||
	if filter.Len() == 0 {
 | 
			
		||||
		return vols, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var retVols []volume.Volume
 | 
			
		||||
	for _, vol := range vols {
 | 
			
		||||
		if filter.Contains("name") {
 | 
			
		||||
			if !filter.Match("name", vol.Name()) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if filter.Contains("driver") {
 | 
			
		||||
			if !filter.ExactMatch("driver", vol.DriverName()) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if filter.Contains("label") {
 | 
			
		||||
			v, ok := vol.(volume.DetailedVolume)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if !filter.MatchKVList("label", v.Labels()) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		retVols = append(retVols, vol)
 | 
			
		||||
	}
 | 
			
		||||
	danglingOnly := false
 | 
			
		||||
	if filter.Contains("dangling") {
 | 
			
		||||
		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
 | 
			
		||||
			danglingOnly = true
 | 
			
		||||
		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
 | 
			
		||||
			return nil, invalidFilter{"dangling", filter.Get("dangling")}
 | 
			
		||||
		}
 | 
			
		||||
		retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly)
 | 
			
		||||
	}
 | 
			
		||||
	return retVols, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
 | 
			
		||||
	if !ancestorMap[imageID] {
 | 
			
		||||
		for _, id := range getChildren(imageID) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,13 @@
 | 
			
		|||
package daemon // import "github.com/docker/docker/daemon"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	mounttypes "github.com/docker/docker/api/types/mount"
 | 
			
		||||
	"github.com/docker/docker/container"
 | 
			
		||||
	volumestore "github.com/docker/docker/volume/store"
 | 
			
		||||
	volumesservice "github.com/docker/docker/volume/service"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
 | 
			
		||||
| 
						 | 
				
			
			@ -20,11 +21,12 @@ func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
 | 
			
		|||
 | 
			
		||||
func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool) error {
 | 
			
		||||
	var rmErrors []string
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	for _, m := range container.MountPoints {
 | 
			
		||||
		if m.Type != mounttypes.TypeVolume || m.Volume == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		daemon.volumes.Dereference(m.Volume, container.ID)
 | 
			
		||||
		daemon.volumes.Release(ctx, m.Volume.Name(), container.ID)
 | 
			
		||||
		if !rm {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -35,13 +37,13 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool)
 | 
			
		|||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := daemon.volumes.Remove(m.Volume)
 | 
			
		||||
		err := daemon.volumes.Remove(ctx, m.Volume.Name())
 | 
			
		||||
		// Ignore volume in use errors because having this
 | 
			
		||||
		// volume being referenced by other container is
 | 
			
		||||
		// not an error, but an implementation detail.
 | 
			
		||||
		// This prevents docker from logging "ERROR: Volume in use"
 | 
			
		||||
		// where there is another container using the volume.
 | 
			
		||||
		if err != nil && !volumestore.IsInUse(err) {
 | 
			
		||||
		if err != nil && !volumesservice.IsInUse(err) {
 | 
			
		||||
			rmErrors = append(rmErrors, err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,27 +10,23 @@ import (
 | 
			
		|||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	timetypes "github.com/docker/docker/api/types/time"
 | 
			
		||||
	"github.com/docker/docker/pkg/directory"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// errPruneRunning is returned when a prune request is received while
 | 
			
		||||
	// one is in progress
 | 
			
		||||
	errPruneRunning = fmt.Errorf("a prune operation is already running")
 | 
			
		||||
	errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
 | 
			
		||||
 | 
			
		||||
	containersAcceptedFilters = map[string]bool{
 | 
			
		||||
		"label":  true,
 | 
			
		||||
		"label!": true,
 | 
			
		||||
		"until":  true,
 | 
			
		||||
	}
 | 
			
		||||
	volumesAcceptedFilters = map[string]bool{
 | 
			
		||||
		"label":  true,
 | 
			
		||||
		"label!": true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	networksAcceptedFilters = map[string]bool{
 | 
			
		||||
		"label":  true,
 | 
			
		||||
| 
						 | 
				
			
			@ -92,67 +88,6 @@ func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.
 | 
			
		|||
	return rep, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumesPrune removes unused local volumes
 | 
			
		||||
func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
 | 
			
		||||
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
 | 
			
		||||
		return nil, errPruneRunning
 | 
			
		||||
	}
 | 
			
		||||
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)
 | 
			
		||||
 | 
			
		||||
	// make sure that only accepted filters have been received
 | 
			
		||||
	err := pruneFilters.Validate(volumesAcceptedFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rep := &types.VolumesPruneReport{}
 | 
			
		||||
 | 
			
		||||
	volumes, err := daemon.volumes.FilterByDriver(volume.DefaultDriverName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range volumes {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			logrus.Debugf("VolumesPrune operation cancelled: %#v", *rep)
 | 
			
		||||
			err := ctx.Err()
 | 
			
		||||
			if err == context.Canceled {
 | 
			
		||||
				return rep, nil
 | 
			
		||||
			}
 | 
			
		||||
			return rep, err
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		name := v.Name()
 | 
			
		||||
		refs := daemon.volumes.Refs(v)
 | 
			
		||||
 | 
			
		||||
		if len(refs) == 0 {
 | 
			
		||||
			detailedVolume, ok := v.(volume.DetailedVolume)
 | 
			
		||||
			if ok {
 | 
			
		||||
				if !matchLabels(pruneFilters, detailedVolume.Labels()) {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vSize, err := directory.Size(ctx, v.Path())
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Warnf("could not determine size of volume %s: %v", name, err)
 | 
			
		||||
			}
 | 
			
		||||
			err = daemon.volumeRm(v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Warnf("could not remove volume %s: %v", name, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			rep.SpaceReclaimed += uint64(vSize)
 | 
			
		||||
			rep.VolumesDeleted = append(rep.VolumesDeleted, name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rep, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// localNetworksPrune removes unused local networks
 | 
			
		||||
func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
 | 
			
		||||
	rep := &types.NetworksPruneReport{}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package daemon // import "github.com/docker/docker/daemon"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"reflect"
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +16,8 @@ import (
 | 
			
		|||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumemounts "github.com/docker/docker/volume/mounts"
 | 
			
		||||
	"github.com/docker/docker/volume/service"
 | 
			
		||||
	volumeopts "github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -27,23 +30,6 @@ var (
 | 
			
		|||
 | 
			
		||||
type mounts []container.Mount
 | 
			
		||||
 | 
			
		||||
// volumeToAPIType converts a volume.Volume to the type used by the Engine API
 | 
			
		||||
func volumeToAPIType(v volume.Volume) *types.Volume {
 | 
			
		||||
	createdAt, _ := v.CreatedAt()
 | 
			
		||||
	tv := &types.Volume{
 | 
			
		||||
		Name:      v.Name(),
 | 
			
		||||
		Driver:    v.DriverName(),
 | 
			
		||||
		CreatedAt: createdAt.Format(time.RFC3339),
 | 
			
		||||
	}
 | 
			
		||||
	if v, ok := v.(volume.DetailedVolume); ok {
 | 
			
		||||
		tv.Labels = v.Labels()
 | 
			
		||||
		tv.Options = v.Options()
 | 
			
		||||
		tv.Scope = v.Scope()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len returns the number of mounts. Used in sorting.
 | 
			
		||||
func (m mounts) Len() int {
 | 
			
		||||
	return len(m)
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +64,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
	mountPoints := map[string]*volumemounts.MountPoint{}
 | 
			
		||||
	parser := volumemounts.NewParser(container.OS)
 | 
			
		||||
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// clean up the container mountpoints once return with error
 | 
			
		||||
		if retErr != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +72,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
				if m.Volume == nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				daemon.volumes.Dereference(m.Volume, container.ID)
 | 
			
		||||
				daemon.volumes.Release(ctx, m.Volume.Name(), container.ID)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +81,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
		if v, ok := mountPoints[destination]; ok {
 | 
			
		||||
			logrus.Debugf("Duplicate mount point '%s'", destination)
 | 
			
		||||
			if v.Volume != nil {
 | 
			
		||||
				daemon.volumes.Dereference(v.Volume, container.ID)
 | 
			
		||||
				daemon.volumes.Release(ctx, v.Volume.Name(), container.ID)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -130,11 +117,11 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
			}
 | 
			
		||||
 | 
			
		||||
			if len(cp.Source) == 0 {
 | 
			
		||||
				v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID)
 | 
			
		||||
				v, err := daemon.volumes.Get(ctx, cp.Name, volumeopts.WithGetDriver(cp.Driver), volumeopts.WithGetReference(container.ID))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				cp.Volume = v
 | 
			
		||||
				cp.Volume = &volumeWrapper{v: v, s: daemon.volumes}
 | 
			
		||||
			}
 | 
			
		||||
			dereferenceIfExists(cp.Destination)
 | 
			
		||||
			mountPoints[cp.Destination] = cp
 | 
			
		||||
| 
						 | 
				
			
			@ -163,14 +150,14 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
 | 
			
		||||
		if bind.Type == mounttypes.TypeVolume {
 | 
			
		||||
			// create the volume
 | 
			
		||||
			v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil)
 | 
			
		||||
			v, err := daemon.volumes.Create(ctx, bind.Name, bind.Driver, volumeopts.WithCreateReference(container.ID))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			bind.Volume = v
 | 
			
		||||
			bind.Source = v.Path()
 | 
			
		||||
			bind.Volume = &volumeWrapper{v: v, s: daemon.volumes}
 | 
			
		||||
			bind.Source = v.Mountpoint
 | 
			
		||||
			// bind.Name is an already existing volume, we need to use that here
 | 
			
		||||
			bind.Driver = v.DriverName()
 | 
			
		||||
			bind.Driver = v.Driver
 | 
			
		||||
			if bind.Driver == volume.DefaultDriverName {
 | 
			
		||||
				setBindModeIfNull(bind)
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -199,30 +186,30 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		if mp.Type == mounttypes.TypeVolume {
 | 
			
		||||
			var v volume.Volume
 | 
			
		||||
			var v *types.Volume
 | 
			
		||||
			if cfg.VolumeOptions != nil {
 | 
			
		||||
				var driverOpts map[string]string
 | 
			
		||||
				if cfg.VolumeOptions.DriverConfig != nil {
 | 
			
		||||
					driverOpts = cfg.VolumeOptions.DriverConfig.Options
 | 
			
		||||
				}
 | 
			
		||||
				v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, driverOpts, cfg.VolumeOptions.Labels)
 | 
			
		||||
				v, err = daemon.volumes.Create(ctx,
 | 
			
		||||
					mp.Name,
 | 
			
		||||
					mp.Driver,
 | 
			
		||||
					volumeopts.WithCreateReference(container.ID),
 | 
			
		||||
					volumeopts.WithCreateOptions(driverOpts),
 | 
			
		||||
					volumeopts.WithCreateLabels(cfg.VolumeOptions.Labels),
 | 
			
		||||
				)
 | 
			
		||||
			} else {
 | 
			
		||||
				v, err = daemon.volumes.CreateWithRef(mp.Name, mp.Driver, container.ID, nil, nil)
 | 
			
		||||
				v, err = daemon.volumes.Create(ctx, mp.Name, mp.Driver, volumeopts.WithCreateReference(container.ID))
 | 
			
		||||
			}
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mp.Volume = v
 | 
			
		||||
			mp.Name = v.Name()
 | 
			
		||||
			mp.Driver = v.DriverName()
 | 
			
		||||
			mp.Volume = &volumeWrapper{v: v, s: daemon.volumes}
 | 
			
		||||
			mp.Name = v.Name
 | 
			
		||||
			mp.Driver = v.Driver
 | 
			
		||||
 | 
			
		||||
			// only use the cached path here since getting the path is not necessary right now and calling `Path()` may be slow
 | 
			
		||||
			if cv, ok := v.(interface {
 | 
			
		||||
				CachedPath() string
 | 
			
		||||
			}); ok {
 | 
			
		||||
				mp.Source = cv.CachedPath()
 | 
			
		||||
			}
 | 
			
		||||
			if mp.Driver == volume.DefaultDriverName {
 | 
			
		||||
				setBindModeIfNull(mp)
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +226,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
	for _, m := range mountPoints {
 | 
			
		||||
		if parser.IsBackwardCompatible(m) {
 | 
			
		||||
			if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
 | 
			
		||||
				daemon.volumes.Dereference(mp.Volume, container.ID)
 | 
			
		||||
				daemon.volumes.Release(ctx, mp.Volume.Name(), container.ID)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -254,11 +241,11 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 | 
			
		|||
// This happens after a daemon restart.
 | 
			
		||||
func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
 | 
			
		||||
	if len(m.Driver) > 0 && m.Volume == nil {
 | 
			
		||||
		v, err := daemon.volumes.GetWithRef(m.Name, m.Driver, containerID)
 | 
			
		||||
		v, err := daemon.volumes.Get(context.TODO(), m.Name, volumeopts.WithGetDriver(m.Driver), volumeopts.WithGetReference(containerID))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.Volume = v
 | 
			
		||||
		m.Volume = &volumeWrapper{v: v, s: daemon.volumes}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -385,3 +372,46 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
 | 
			
		|||
		cm.Spec.ReadOnly = !cm.RW
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumesService is used to perform volume operations
 | 
			
		||||
func (daemon *Daemon) VolumesService() *service.VolumesService {
 | 
			
		||||
	return daemon.volumes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type volumeMounter interface {
 | 
			
		||||
	Mount(ctx context.Context, v *types.Volume, ref string) (string, error)
 | 
			
		||||
	Unmount(ctx context.Context, v *types.Volume, ref string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type volumeWrapper struct {
 | 
			
		||||
	v *types.Volume
 | 
			
		||||
	s volumeMounter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) Name() string {
 | 
			
		||||
	return v.v.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) DriverName() string {
 | 
			
		||||
	return v.v.Driver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) Path() string {
 | 
			
		||||
	return v.v.Mountpoint
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) Mount(ref string) (string, error) {
 | 
			
		||||
	return v.s.Mount(context.TODO(), v.v, ref)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) Unmount(ref string) error {
 | 
			
		||||
	return v.s.Unmount(context.TODO(), v.v, ref)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) CreatedAt() (time.Time, error) {
 | 
			
		||||
	return time.Time{}, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *volumeWrapper) Status() map[string]interface{} {
 | 
			
		||||
	return v.v.Status
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,11 +185,12 @@ func (s *DockerSuite) TestVolumeEvents(c *check.C) {
 | 
			
		|||
	c.Assert(len(events), checker.GreaterThan, 4)
 | 
			
		||||
 | 
			
		||||
	volumeEvents := eventActionsByIDAndType(c, events, "test-event-volume-local", "volume")
 | 
			
		||||
	c.Assert(volumeEvents, checker.HasLen, 4)
 | 
			
		||||
	c.Assert(volumeEvents, checker.HasLen, 5)
 | 
			
		||||
	c.Assert(volumeEvents[0], checker.Equals, "create")
 | 
			
		||||
	c.Assert(volumeEvents[1], checker.Equals, "mount")
 | 
			
		||||
	c.Assert(volumeEvents[2], checker.Equals, "unmount")
 | 
			
		||||
	c.Assert(volumeEvents[3], checker.Equals, "destroy")
 | 
			
		||||
	c.Assert(volumeEvents[1], checker.Equals, "create")
 | 
			
		||||
	c.Assert(volumeEvents[2], checker.Equals, "mount")
 | 
			
		||||
	c.Assert(volumeEvents[3], checker.Equals, "unmount")
 | 
			
		||||
	c.Assert(volumeEvents[4], checker.Equals, "destroy")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestNetworkEvents(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -517,22 +517,20 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path`
 | 
			
		||||
//
 | 
			
		||||
// TODO(@cpuguy83): This test is testing internal implementation. In all the cases here, there may not even be a path
 | 
			
		||||
// 	available because the volume is not even mounted. Consider removing this test.
 | 
			
		||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
 | 
			
		||||
	s.d.Start(c)
 | 
			
		||||
	c.Assert(s.ec.paths, checker.Equals, 0)
 | 
			
		||||
 | 
			
		||||
	out, err := s.d.Cmd("volume", "create", "test", "--driver=test-external-volume-driver")
 | 
			
		||||
	c.Assert(err, checker.IsNil, check.Commentf(out))
 | 
			
		||||
	c.Assert(s.ec.paths, checker.Equals, 1)
 | 
			
		||||
	c.Assert(s.ec.paths, checker.Equals, 0)
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("volume", "ls")
 | 
			
		||||
	c.Assert(err, checker.IsNil, check.Commentf(out))
 | 
			
		||||
	c.Assert(s.ec.paths, checker.Equals, 1)
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test")
 | 
			
		||||
	c.Assert(err, checker.IsNil, check.Commentf(out))
 | 
			
		||||
	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
 | 
			
		||||
	c.Assert(s.ec.paths, checker.Equals, 1)
 | 
			
		||||
	c.Assert(s.ec.paths, checker.Equals, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
// +build linux freebsd
 | 
			
		||||
// +build linux freebsd darwin
 | 
			
		||||
 | 
			
		||||
package directory // import "github.com/docker/docker/pkg/directory"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		// `GetCapabilities` is a not a required endpoint.
 | 
			
		||||
		// On error assume it's a local-only driver
 | 
			
		||||
		logrus.Warnf("Volume driver %s returned an error while trying to query its capabilities, using default capabilities: %v", a.name, err)
 | 
			
		||||
		logrus.WithError(err).WithField("driver", a.name).Debug("Volume driver returned an error while trying to query its capabilities, using default capabilities")
 | 
			
		||||
		return volume.Capability{Scope: volume.LocalScope}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +105,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
 | 
			
		|||
 | 
			
		||||
	cap.Scope = strings.ToLower(cap.Scope)
 | 
			
		||||
	if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope {
 | 
			
		||||
		logrus.Warnf("Volume driver %q returned an invalid scope: %q", a.Name(), cap.Scope)
 | 
			
		||||
		logrus.WithField("driver", a.Name()).WithField("scope", a.Scope).Warn("Volume driver returned an invalid scope")
 | 
			
		||||
		cap.Scope = volume.LocalScope
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -167,10 +167,10 @@ func (s *Store) ReleaseDriver(name string) (volume.Driver, error) {
 | 
			
		|||
func (s *Store) GetDriverList() []string {
 | 
			
		||||
	var driverList []string
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	for driverName := range s.extensions {
 | 
			
		||||
		driverList = append(driverList, driverName)
 | 
			
		||||
	}
 | 
			
		||||
	s.mu.Unlock()
 | 
			
		||||
	sort.Strings(driverList)
 | 
			
		||||
	return driverList
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										89
									
								
								volume/service/by.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								volume/service/by.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// By is an interface which is used to implement filtering on volumes.
 | 
			
		||||
type By interface {
 | 
			
		||||
	isBy()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ByDriver is `By` that filters based on the driver names that are passed in
 | 
			
		||||
func ByDriver(drivers ...string) By {
 | 
			
		||||
	return byDriver(drivers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type byDriver []string
 | 
			
		||||
 | 
			
		||||
func (byDriver) isBy() {}
 | 
			
		||||
 | 
			
		||||
// ByReferenced is a `By` that filters based on if the volume has references
 | 
			
		||||
type ByReferenced bool
 | 
			
		||||
 | 
			
		||||
func (ByReferenced) isBy() {}
 | 
			
		||||
 | 
			
		||||
// And creates a `By` combining all the passed in bys using AND logic.
 | 
			
		||||
func And(bys ...By) By {
 | 
			
		||||
	and := make(andCombinator, 0, len(bys))
 | 
			
		||||
	for _, by := range bys {
 | 
			
		||||
		and = append(and, by)
 | 
			
		||||
	}
 | 
			
		||||
	return and
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type andCombinator []By
 | 
			
		||||
 | 
			
		||||
func (andCombinator) isBy() {}
 | 
			
		||||
 | 
			
		||||
// Or creates a `By` combining all the passed in bys using OR logic.
 | 
			
		||||
func Or(bys ...By) By {
 | 
			
		||||
	or := make(orCombinator, 0, len(bys))
 | 
			
		||||
	for _, by := range bys {
 | 
			
		||||
		or = append(or, by)
 | 
			
		||||
	}
 | 
			
		||||
	return or
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type orCombinator []By
 | 
			
		||||
 | 
			
		||||
func (orCombinator) isBy() {}
 | 
			
		||||
 | 
			
		||||
// CustomFilter is a `By` that is used by callers to provide custom filtering
 | 
			
		||||
// logic.
 | 
			
		||||
type CustomFilter filterFunc
 | 
			
		||||
 | 
			
		||||
func (CustomFilter) isBy() {}
 | 
			
		||||
 | 
			
		||||
// FromList returns a By which sets the initial list of volumes to use
 | 
			
		||||
func FromList(ls *[]volume.Volume, by By) By {
 | 
			
		||||
	return &fromList{by: by, ls: ls}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type fromList struct {
 | 
			
		||||
	by By
 | 
			
		||||
	ls *[]volume.Volume
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fromList) isBy() {}
 | 
			
		||||
 | 
			
		||||
func byLabelFilter(filter filters.Args) By {
 | 
			
		||||
	return CustomFilter(func(v volume.Volume) bool {
 | 
			
		||||
		dv, ok := v.(volume.DetailedVolume)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		labels := dv.Labels()
 | 
			
		||||
		if !filter.MatchKVList("label", labels) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if filter.Contains("label!") {
 | 
			
		||||
			if filter.MatchKVList("label!", labels) {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								volume/service/convert.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								volume/service/convert.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,132 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/pkg/directory"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// convertOpts are used to pass options to `volumeToAPI`
 | 
			
		||||
type convertOpt interface {
 | 
			
		||||
	isConvertOpt()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type useCachedPath bool
 | 
			
		||||
 | 
			
		||||
func (useCachedPath) isConvertOpt() {}
 | 
			
		||||
 | 
			
		||||
type calcSize bool
 | 
			
		||||
 | 
			
		||||
func (calcSize) isConvertOpt() {}
 | 
			
		||||
 | 
			
		||||
type pathCacher interface {
 | 
			
		||||
	CachedPath() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *VolumesService) volumesToAPI(ctx context.Context, volumes []volume.Volume, opts ...convertOpt) []*types.Volume {
 | 
			
		||||
	var (
 | 
			
		||||
		out        = make([]*types.Volume, 0, len(volumes))
 | 
			
		||||
		getSize    bool
 | 
			
		||||
		cachedPath bool
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for _, o := range opts {
 | 
			
		||||
		switch t := o.(type) {
 | 
			
		||||
		case calcSize:
 | 
			
		||||
			getSize = bool(t)
 | 
			
		||||
		case useCachedPath:
 | 
			
		||||
			cachedPath = bool(t)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range volumes {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			return nil
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
		apiV := volumeToAPIType(v)
 | 
			
		||||
 | 
			
		||||
		if cachedPath {
 | 
			
		||||
			if vv, ok := v.(pathCacher); ok {
 | 
			
		||||
				apiV.Mountpoint = vv.CachedPath()
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			apiV.Mountpoint = v.Path()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if getSize {
 | 
			
		||||
			p := v.Path()
 | 
			
		||||
			if apiV.Mountpoint == "" {
 | 
			
		||||
				apiV.Mountpoint = p
 | 
			
		||||
			}
 | 
			
		||||
			sz, err := directory.Size(ctx, p)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.WithError(err).WithField("volume", v.Name()).Warnf("Failed to determine size of volume")
 | 
			
		||||
				sz = -1
 | 
			
		||||
			}
 | 
			
		||||
			apiV.UsageData = &types.VolumeUsageData{Size: sz, RefCount: int64(s.vs.CountReferences(v))}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		out = append(out, &apiV)
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func volumeToAPIType(v volume.Volume) types.Volume {
 | 
			
		||||
	createdAt, _ := v.CreatedAt()
 | 
			
		||||
	tv := types.Volume{
 | 
			
		||||
		Name:      v.Name(),
 | 
			
		||||
		Driver:    v.DriverName(),
 | 
			
		||||
		CreatedAt: createdAt.Format(time.RFC3339),
 | 
			
		||||
	}
 | 
			
		||||
	if v, ok := v.(volume.DetailedVolume); ok {
 | 
			
		||||
		tv.Labels = v.Labels()
 | 
			
		||||
		tv.Options = v.Options()
 | 
			
		||||
		tv.Scope = v.Scope()
 | 
			
		||||
	}
 | 
			
		||||
	if cp, ok := v.(pathCacher); ok {
 | 
			
		||||
		tv.Mountpoint = cp.CachedPath()
 | 
			
		||||
	}
 | 
			
		||||
	return tv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filtersToBy(filter filters.Args, acceptedFilters map[string]bool) (By, error) {
 | 
			
		||||
	if err := filter.Validate(acceptedFilters); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var bys []By
 | 
			
		||||
	if drivers := filter.Get("driver"); len(drivers) > 0 {
 | 
			
		||||
		bys = append(bys, ByDriver(drivers...))
 | 
			
		||||
	}
 | 
			
		||||
	if filter.Contains("name") {
 | 
			
		||||
		bys = append(bys, CustomFilter(func(v volume.Volume) bool {
 | 
			
		||||
			return filter.Match("name", v.Name())
 | 
			
		||||
		}))
 | 
			
		||||
	}
 | 
			
		||||
	bys = append(bys, byLabelFilter(filter))
 | 
			
		||||
 | 
			
		||||
	if filter.Contains("dangling") {
 | 
			
		||||
		var dangling bool
 | 
			
		||||
		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
 | 
			
		||||
			dangling = true
 | 
			
		||||
		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
 | 
			
		||||
			return nil, invalidFilter{"dangling", filter.Get("dangling")}
 | 
			
		||||
		}
 | 
			
		||||
		bys = append(bys, ByReferenced(!dangling))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var by By
 | 
			
		||||
	switch len(bys) {
 | 
			
		||||
	case 0:
 | 
			
		||||
	case 1:
 | 
			
		||||
		by = bys[0]
 | 
			
		||||
	default:
 | 
			
		||||
		by = And(bys...)
 | 
			
		||||
	}
 | 
			
		||||
	return by, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package store
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
							
								
								
									
										21
									
								
								volume/service/default_driver.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								volume/service/default_driver.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
// +build linux windows
 | 
			
		||||
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/local"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func setupDefaultDriver(store *drivers.Store, root string, rootIDs idtools.IDPair) error {
 | 
			
		||||
	d, err := local.New(root, rootIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "error setting up default driver")
 | 
			
		||||
	}
 | 
			
		||||
	if !store.Register(d, volume.DefaultDriverName) {
 | 
			
		||||
		return errors.New("local volume driver could not be registered")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								volume/service/default_driver_stubs.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								volume/service/default_driver_stubs.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
// +build !linux,!windows
 | 
			
		||||
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/volume/drivers"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func setupDefaultDriver(_ *drivers.Store, _ string, _ idtools.IDPair) error { return nil }
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -93,3 +94,18 @@ func isErr(err error, expected error) bool {
 | 
			
		|||
	}
 | 
			
		||||
	return err == expected
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type invalidFilter struct {
 | 
			
		||||
	filter string
 | 
			
		||||
	value  interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e invalidFilter) Error() string {
 | 
			
		||||
	msg := "Invalid filter '" + e.filter
 | 
			
		||||
	if e.value != nil {
 | 
			
		||||
		msg += fmt.Sprintf("=%s", e.value)
 | 
			
		||||
	}
 | 
			
		||||
	return msg + "'"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e invalidFilter) InvalidParameter() {}
 | 
			
		||||
							
								
								
									
										89
									
								
								volume/service/opts/opts.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								volume/service/opts/opts.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
package opts
 | 
			
		||||
 | 
			
		||||
// CreateOption is used to pass options in when creating a volume
 | 
			
		||||
type CreateOption func(*CreateConfig)
 | 
			
		||||
 | 
			
		||||
// CreateConfig is the set of config options that can be set when creating
 | 
			
		||||
// a volume
 | 
			
		||||
type CreateConfig struct {
 | 
			
		||||
	Options   map[string]string
 | 
			
		||||
	Labels    map[string]string
 | 
			
		||||
	Reference string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithCreateLabels creates a CreateOption which sets the labels to the
 | 
			
		||||
// passed in value
 | 
			
		||||
func WithCreateLabels(labels map[string]string) CreateOption {
 | 
			
		||||
	return func(cfg *CreateConfig) {
 | 
			
		||||
		cfg.Labels = labels
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithCreateOptions creates a CreateOption which sets the options passed
 | 
			
		||||
// to the volume driver when creating a volume to the options passed in.
 | 
			
		||||
func WithCreateOptions(opts map[string]string) CreateOption {
 | 
			
		||||
	return func(cfg *CreateConfig) {
 | 
			
		||||
		cfg.Options = opts
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithCreateReference creats a CreateOption which sets a reference to use
 | 
			
		||||
// when creating a volume. This ensures that the volume is created with a reference
 | 
			
		||||
// already attached to it to prevent race conditions with Create and volume cleanup.
 | 
			
		||||
func WithCreateReference(ref string) CreateOption {
 | 
			
		||||
	return func(cfg *CreateConfig) {
 | 
			
		||||
		cfg.Reference = ref
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetConfig is used with `GetOption` to set options for the volumes service's
 | 
			
		||||
// `Get` implementation.
 | 
			
		||||
type GetConfig struct {
 | 
			
		||||
	Driver        string
 | 
			
		||||
	Reference     string
 | 
			
		||||
	ResolveStatus bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetOption is passed to the service `Get` add extra details on the get request
 | 
			
		||||
type GetOption func(*GetConfig)
 | 
			
		||||
 | 
			
		||||
// WithGetDriver provides the driver to get the volume from
 | 
			
		||||
// If no driver is provided to `Get`, first the available metadata is checked
 | 
			
		||||
// to see which driver it belongs to, if that is not available all drivers are
 | 
			
		||||
// probed to find the volume.
 | 
			
		||||
func WithGetDriver(name string) GetOption {
 | 
			
		||||
	return func(o *GetConfig) {
 | 
			
		||||
		o.Driver = name
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithGetReference indicates to `Get` to increment the reference count for the
 | 
			
		||||
// retreived volume with the provided reference ID.
 | 
			
		||||
func WithGetReference(ref string) GetOption {
 | 
			
		||||
	return func(o *GetConfig) {
 | 
			
		||||
		o.Reference = ref
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithGetResolveStatus indicates to `Get` to also fetch the volume status.
 | 
			
		||||
// This can cause significant overhead in the volume lookup.
 | 
			
		||||
func WithGetResolveStatus(cfg *GetConfig) {
 | 
			
		||||
	cfg.ResolveStatus = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveConfig is used by `RemoveOption` to store config options for remove
 | 
			
		||||
type RemoveConfig struct {
 | 
			
		||||
	PurgeOnError bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveOption is used to pass options to the volumes service `Remove` implementation
 | 
			
		||||
type RemoveOption func(*RemoveConfig)
 | 
			
		||||
 | 
			
		||||
// WithPurgeOnError is an option passed to `Remove` which will purge all cached
 | 
			
		||||
// data about a volume even if there was an error while attempting to remove the
 | 
			
		||||
// volume.
 | 
			
		||||
func WithPurgeOnError(b bool) RemoveOption {
 | 
			
		||||
	return func(o *RemoveConfig) {
 | 
			
		||||
		o.PurgeOnError = b
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +21,7 @@ func (s *VolumeStore) restore() {
 | 
			
		|||
		ls = listMeta(tx)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	chRemove := make(chan *volumeMetadata, len(ls))
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +34,7 @@ func (s *VolumeStore) restore() {
 | 
			
		|||
			var v volume.Volume
 | 
			
		||||
			var err error
 | 
			
		||||
			if meta.Driver != "" {
 | 
			
		||||
				v, err = lookupVolume(s.drivers, meta.Driver, meta.Name)
 | 
			
		||||
				v, err = lookupVolume(ctx, s.drivers, meta.Driver, meta.Name)
 | 
			
		||||
				if err != nil && err != errNoSuchVolume {
 | 
			
		||||
					logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
 | 
			
		||||
					return
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +45,7 @@ func (s *VolumeStore) restore() {
 | 
			
		|||
					return
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v, err = s.getVolume(meta.Name)
 | 
			
		||||
				v, err = s.getVolume(ctx, meta.Name, meta.Driver)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					if err == errNoSuchVolume {
 | 
			
		||||
						chRemove <- &meta
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +67,7 @@ func (s *VolumeStore) restore() {
 | 
			
		|||
			s.options[v.Name()] = meta.Options
 | 
			
		||||
			s.labels[v.Name()] = meta.Labels
 | 
			
		||||
			s.names[v.Name()] = v
 | 
			
		||||
			s.refs[v.Name()] = make(map[string]struct{})
 | 
			
		||||
			s.globalLock.Unlock()
 | 
			
		||||
		}(meta)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +1,14 @@
 | 
			
		|||
package store
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	volumetestutils "github.com/docker/docker/volume/testutils"
 | 
			
		||||
	"github.com/gotestyourself/gotestyourself/assert"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -22,24 +24,25 @@ func TestRestore(t *testing.T) {
 | 
			
		|||
	driverName := "test-restore"
 | 
			
		||||
	drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
 | 
			
		||||
 | 
			
		||||
	s, err := New(dir, drivers)
 | 
			
		||||
	s, err := NewStore(dir, drivers)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	defer s.Shutdown()
 | 
			
		||||
 | 
			
		||||
	_, err = s.Create("test1", driverName, nil, nil)
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	_, err = s.Create(ctx, "test1", driverName)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	testLabels := map[string]string{"a": "1"}
 | 
			
		||||
	testOpts := map[string]string{"foo": "bar"}
 | 
			
		||||
	_, err = s.Create("test2", driverName, testOpts, testLabels)
 | 
			
		||||
	_, err = s.Create(ctx, "test2", driverName, opts.WithCreateOptions(testOpts), opts.WithCreateLabels(testLabels))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	s.Shutdown()
 | 
			
		||||
 | 
			
		||||
	s, err = New(dir, drivers)
 | 
			
		||||
	s, err = NewStore(dir, drivers)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	v, err := s.Get("test1")
 | 
			
		||||
	v, err := s.Get(ctx, "test1")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	dv := v.(volume.DetailedVolume)
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +50,7 @@ func TestRestore(t *testing.T) {
 | 
			
		|||
	assert.DeepEqual(t, nilMap, dv.Options())
 | 
			
		||||
	assert.DeepEqual(t, nilMap, dv.Labels())
 | 
			
		||||
 | 
			
		||||
	v, err = s.Get("test2")
 | 
			
		||||
	v, err = s.Get(ctx, "test2")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	dv = v.(volume.DetailedVolume)
 | 
			
		||||
	assert.DeepEqual(t, testOpts, dv.Options())
 | 
			
		||||
							
								
								
									
										243
									
								
								volume/service/service.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								volume/service/service.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,243 @@
 | 
			
		|||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/pkg/directory"
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/plugingetter"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ds interface {
 | 
			
		||||
	GetDriverList() []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type volumeEventLogger interface {
 | 
			
		||||
	LogVolumeEvent(volumeID, action string, attributes map[string]string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumesService manages access to volumes
 | 
			
		||||
type VolumesService struct {
 | 
			
		||||
	vs           *VolumeStore
 | 
			
		||||
	ds           ds
 | 
			
		||||
	pruneRunning int32
 | 
			
		||||
	eventLogger  volumeEventLogger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewVolumeService creates a new volume service
 | 
			
		||||
func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.IDPair, logger volumeEventLogger) (*VolumesService, error) {
 | 
			
		||||
	ds := drivers.NewStore(pg)
 | 
			
		||||
	if err := setupDefaultDriver(ds, root, rootIDs); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vs, err := NewStore(root, ds)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &VolumesService{vs: vs, ds: ds, eventLogger: logger}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDriverList gets the list of registered volume drivers
 | 
			
		||||
func (s *VolumesService) GetDriverList() []string {
 | 
			
		||||
	return s.ds.GetDriverList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create creates a volume
 | 
			
		||||
func (s *VolumesService) Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) {
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		name = stringid.GenerateNonCryptoID()
 | 
			
		||||
	}
 | 
			
		||||
	v, err := s.vs.Create(ctx, name, driverName, opts...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.eventLogger.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
 | 
			
		||||
	apiV := volumeToAPIType(v)
 | 
			
		||||
	return &apiV, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get gets a volume
 | 
			
		||||
func (s *VolumesService) Get(ctx context.Context, name string, getOpts ...opts.GetOption) (*types.Volume, error) {
 | 
			
		||||
	v, err := s.vs.Get(ctx, name, getOpts...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	vol := volumeToAPIType(v)
 | 
			
		||||
 | 
			
		||||
	var cfg opts.GetConfig
 | 
			
		||||
	for _, o := range getOpts {
 | 
			
		||||
		o(&cfg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cfg.ResolveStatus {
 | 
			
		||||
		vol.Status = v.Status()
 | 
			
		||||
	}
 | 
			
		||||
	return &vol, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mount mounts the volume
 | 
			
		||||
func (s *VolumesService) Mount(ctx context.Context, vol *types.Volume, ref string) (string, error) {
 | 
			
		||||
	v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsNotExist(err) {
 | 
			
		||||
			err = errdefs.NotFound(err)
 | 
			
		||||
		}
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return v.Mount(ref)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Unmount unmounts the volume.
 | 
			
		||||
// Note that depending on the implementation, the volume may still be mounted due to other resources using it.
 | 
			
		||||
func (s *VolumesService) Unmount(ctx context.Context, vol *types.Volume, ref string) error {
 | 
			
		||||
	v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsNotExist(err) {
 | 
			
		||||
			err = errdefs.NotFound(err)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return v.Unmount(ref)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Release releases a volume reference
 | 
			
		||||
func (s *VolumesService) Release(ctx context.Context, name string, ref string) error {
 | 
			
		||||
	return s.vs.Release(ctx, name, ref)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove removes a volume
 | 
			
		||||
func (s *VolumesService) Remove(ctx context.Context, name string, rmOpts ...opts.RemoveOption) error {
 | 
			
		||||
	var cfg opts.RemoveConfig
 | 
			
		||||
	for _, o := range rmOpts {
 | 
			
		||||
		o(&cfg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := s.vs.Get(ctx, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsNotExist(err) && cfg.PurgeOnError {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = s.vs.Remove(ctx, v, rmOpts...)
 | 
			
		||||
	if IsNotExist(err) {
 | 
			
		||||
		err = nil
 | 
			
		||||
	} else if IsInUse(err) {
 | 
			
		||||
		err = errdefs.Conflict(err)
 | 
			
		||||
	} else if IsNotExist(err) && cfg.PurgeOnError {
 | 
			
		||||
		err = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		s.eventLogger.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var acceptedPruneFilters = map[string]bool{
 | 
			
		||||
	"label":  true,
 | 
			
		||||
	"label!": true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var acceptedListFilters = map[string]bool{
 | 
			
		||||
	"dangling": true,
 | 
			
		||||
	"name":     true,
 | 
			
		||||
	"driver":   true,
 | 
			
		||||
	"label":    true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LocalVolumesSize gets all local volumes and fetches their size on disk
 | 
			
		||||
// Note that this intentionally skips volumes which have mount options. Typically
 | 
			
		||||
// volumes with mount options are not really local even if they are using the
 | 
			
		||||
// local driver.
 | 
			
		||||
func (s *VolumesService) LocalVolumesSize(ctx context.Context) ([]*types.Volume, error) {
 | 
			
		||||
	ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), CustomFilter(func(v volume.Volume) bool {
 | 
			
		||||
		dv, ok := v.(volume.DetailedVolume)
 | 
			
		||||
		return ok && len(dv.Options()) == 0
 | 
			
		||||
	})))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return s.volumesToAPI(ctx, ls, calcSize(true)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Prune removes (local) volumes which match the past in filter arguments.
 | 
			
		||||
// Note that this intentionally skips volumes with mount options as there would
 | 
			
		||||
// be no space reclaimed in this case.
 | 
			
		||||
func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types.VolumesPruneReport, error) {
 | 
			
		||||
	if !atomic.CompareAndSwapInt32(&s.pruneRunning, 0, 1) {
 | 
			
		||||
		return nil, errdefs.Conflict(errors.New("a prune operation is already running"))
 | 
			
		||||
	}
 | 
			
		||||
	defer atomic.StoreInt32(&s.pruneRunning, 0)
 | 
			
		||||
 | 
			
		||||
	by, err := filtersToBy(filter, acceptedPruneFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), ByReferenced(false), by, CustomFilter(func(v volume.Volume) bool {
 | 
			
		||||
		dv, ok := v.(volume.DetailedVolume)
 | 
			
		||||
		return ok && len(dv.Options()) == 0
 | 
			
		||||
	})))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rep := &types.VolumesPruneReport{VolumesDeleted: make([]string, 0, len(ls))}
 | 
			
		||||
	for _, v := range ls {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			err := ctx.Err()
 | 
			
		||||
			if err == context.Canceled {
 | 
			
		||||
				err = nil
 | 
			
		||||
			}
 | 
			
		||||
			return rep, err
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vSize, err := directory.Size(ctx, v.Path())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.WithField("volume", v.Name()).WithError(err).Warn("could not determine size of volume")
 | 
			
		||||
		}
 | 
			
		||||
		if err := s.vs.Remove(ctx, v); err != nil {
 | 
			
		||||
			logrus.WithError(err).WithField("volume", v.Name()).Warnf("Could not determine size of volume")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		rep.SpaceReclaimed += uint64(vSize)
 | 
			
		||||
		rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name())
 | 
			
		||||
	}
 | 
			
		||||
	return rep, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List gets the list of volumes which match the past in filters
 | 
			
		||||
// If filters is nil or empty all volumes are returned.
 | 
			
		||||
func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) {
 | 
			
		||||
	by, err := filtersToBy(filter, acceptedListFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumes, warnings, err := s.vs.Find(ctx, by)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Shutdown shuts down the image service and dependencies
 | 
			
		||||
func (s *VolumesService) Shutdown() error {
 | 
			
		||||
	return s.vs.Shutdown()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								volume/service/service_linux_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								volume/service/service_linux_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/local"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/docker/docker/volume/testutils"
 | 
			
		||||
	"github.com/gotestyourself/gotestyourself/assert"
 | 
			
		||||
	is "github.com/gotestyourself/gotestyourself/assert/cmp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestLocalVolumeSize(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	ds := volumedrivers.NewStore(nil)
 | 
			
		||||
	dir, err := ioutil.TempDir("", t.Name())
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	defer os.RemoveAll(dir)
 | 
			
		||||
 | 
			
		||||
	l, err := local.New(dir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()})
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, ds.Register(l, volume.DefaultDriverName))
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("fake"), "fake"))
 | 
			
		||||
 | 
			
		||||
	service, cleanup := newTestService(t, ds)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	v1, err := service.Create(ctx, "test1", volume.DefaultDriverName, opts.WithCreateReference("foo"))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	v2, err := service.Create(ctx, "test2", volume.DefaultDriverName)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	_, err = service.Create(ctx, "test3", "fake")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	data := make([]byte, 1024)
 | 
			
		||||
	err = ioutil.WriteFile(filepath.Join(v1.Mountpoint, "data"), data, 0644)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	err = ioutil.WriteFile(filepath.Join(v2.Mountpoint, "data"), data[:1], 0644)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	ls, err := service.LocalVolumesSize(ctx)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(ls, 2))
 | 
			
		||||
 | 
			
		||||
	for _, v := range ls {
 | 
			
		||||
		switch v.Name {
 | 
			
		||||
		case "test1":
 | 
			
		||||
			assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data))))
 | 
			
		||||
			assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(1)))
 | 
			
		||||
		case "test2":
 | 
			
		||||
			assert.Assert(t, is.Equal(v.UsageData.Size, int64(len(data[:1]))))
 | 
			
		||||
			assert.Assert(t, is.Equal(v.UsageData.RefCount, int64(0)))
 | 
			
		||||
		default:
 | 
			
		||||
			t.Fatalf("got unexpected volume: %+v", v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	assert.Assert(t, is.Equal(ls[1].UsageData.Size, int64(len(data[:1]))))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										253
									
								
								volume/service/service_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								volume/service/service_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,253 @@
 | 
			
		|||
package service
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/docker/docker/volume/testutils"
 | 
			
		||||
	"github.com/gotestyourself/gotestyourself/assert"
 | 
			
		||||
	is "github.com/gotestyourself/gotestyourself/assert/cmp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestServiceCreate(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	ds := volumedrivers.NewStore(nil)
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	service, cleanup := newTestService(t, ds)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
 | 
			
		||||
	_, err := service.Create(ctx, "v1", "notexist")
 | 
			
		||||
	assert.Assert(t, errdefs.IsNotFound(err), err)
 | 
			
		||||
 | 
			
		||||
	v, err := service.Create(ctx, "v1", "d1")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	vCopy, err := service.Create(ctx, "v1", "d1")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.DeepEqual(v, vCopy))
 | 
			
		||||
 | 
			
		||||
	_, err = service.Create(ctx, "v1", "d2")
 | 
			
		||||
	assert.Check(t, IsNameConflict(err), err)
 | 
			
		||||
	assert.Check(t, errdefs.IsConflict(err), err)
 | 
			
		||||
 | 
			
		||||
	assert.Assert(t, service.Remove(ctx, "v1"))
 | 
			
		||||
	_, err = service.Create(ctx, "v1", "d2")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	_, err = service.Create(ctx, "v1", "d2")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServiceList(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	ds := volumedrivers.NewStore(nil)
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
 | 
			
		||||
 | 
			
		||||
	service, cleanup := newTestService(t, ds)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	_, err := service.Create(ctx, "v1", "d1")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	_, err = service.Create(ctx, "v2", "d1")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	_, err = service.Create(ctx, "v3", "d2")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	ls, _, err := service.List(ctx, filters.NewArgs(filters.Arg("driver", "d1")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 2))
 | 
			
		||||
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "d2")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 1))
 | 
			
		||||
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("driver", "notexist")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 0))
 | 
			
		||||
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 3))
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 0))
 | 
			
		||||
 | 
			
		||||
	_, err = service.Get(ctx, "v1", opts.WithGetReference("foo"))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 2))
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 1))
 | 
			
		||||
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "false"), filters.Arg("driver", "d2")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 0))
 | 
			
		||||
	ls, _, err = service.List(ctx, filters.NewArgs(filters.Arg("dangling", "true"), filters.Arg("driver", "d2")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Check(t, is.Len(ls, 1))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServiceRemove(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	ds := volumedrivers.NewStore(nil)
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
 | 
			
		||||
 | 
			
		||||
	service, cleanup := newTestService(t, ds)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	_, err := service.Create(ctx, "test", "d1")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	assert.Assert(t, service.Remove(ctx, "test"))
 | 
			
		||||
	assert.Assert(t, service.Remove(ctx, "test", opts.WithPurgeOnError(true)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServiceGet(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	ds := volumedrivers.NewStore(nil)
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d1"), "d1"))
 | 
			
		||||
 | 
			
		||||
	service, cleanup := newTestService(t, ds)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	v, err := service.Get(ctx, "notexist")
 | 
			
		||||
	assert.Assert(t, IsNotExist(err))
 | 
			
		||||
	assert.Check(t, v == nil)
 | 
			
		||||
 | 
			
		||||
	created, err := service.Create(ctx, "test", "d1")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, created != nil)
 | 
			
		||||
 | 
			
		||||
	v, err = service.Get(ctx, "test")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.DeepEqual(created, v))
 | 
			
		||||
 | 
			
		||||
	v, err = service.Get(ctx, "test", opts.WithGetResolveStatus)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(v.Status, 1), v.Status)
 | 
			
		||||
 | 
			
		||||
	v, err = service.Get(ctx, "test", opts.WithGetDriver("notarealdriver"))
 | 
			
		||||
	assert.Assert(t, errdefs.IsConflict(err), err)
 | 
			
		||||
	v, err = service.Get(ctx, "test", opts.WithGetDriver("d1"))
 | 
			
		||||
	assert.Assert(t, err == nil)
 | 
			
		||||
	assert.Assert(t, is.DeepEqual(created, v))
 | 
			
		||||
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("d2"), "d2"))
 | 
			
		||||
	v, err = service.Get(ctx, "test", opts.WithGetDriver("d2"))
 | 
			
		||||
	assert.Assert(t, errdefs.IsConflict(err), err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServicePrune(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	ds := volumedrivers.NewStore(nil)
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver(volume.DefaultDriverName), volume.DefaultDriverName))
 | 
			
		||||
	assert.Assert(t, ds.Register(testutils.NewFakeDriver("other"), "other"))
 | 
			
		||||
 | 
			
		||||
	service, cleanup := newTestService(t, ds)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	_, err := service.Create(ctx, "test", volume.DefaultDriverName)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	_, err = service.Create(ctx, "test2", "other")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	pr, err := service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
 | 
			
		||||
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs())
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
 | 
			
		||||
	assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
 | 
			
		||||
 | 
			
		||||
	_, err = service.Get(ctx, "test")
 | 
			
		||||
	assert.Assert(t, IsNotExist(err), err)
 | 
			
		||||
 | 
			
		||||
	v, err := service.Get(ctx, "test2")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Equal(v.Driver, "other"))
 | 
			
		||||
 | 
			
		||||
	_, err = service.Create(ctx, "test", volume.DefaultDriverName)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
 | 
			
		||||
	assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
 | 
			
		||||
	v, err = service.Get(ctx, "test2")
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Equal(v.Driver, "other"))
 | 
			
		||||
 | 
			
		||||
	_, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": ""}))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
 | 
			
		||||
 | 
			
		||||
	_, err = service.Create(ctx, "test3", volume.DefaultDriverName, opts.WithCreateLabels(map[string]string{"banana": "split"}))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label!", "banana=split")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
 | 
			
		||||
	assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
 | 
			
		||||
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs(filters.Arg("label", "banana=split")))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
 | 
			
		||||
	assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test3"))
 | 
			
		||||
 | 
			
		||||
	v, err = service.Create(ctx, "test", volume.DefaultDriverName, opts.WithCreateReference(t.Name()))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs())
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 0))
 | 
			
		||||
	assert.Assert(t, service.Release(ctx, v.Name, t.Name()))
 | 
			
		||||
 | 
			
		||||
	pr, err = service.Prune(ctx, filters.NewArgs())
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, is.Len(pr.VolumesDeleted, 1))
 | 
			
		||||
	assert.Assert(t, is.Equal(pr.VolumesDeleted[0], "test"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTestService(t *testing.T, ds *volumedrivers.Store) (*VolumesService, func()) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 | 
			
		||||
	dir, err := ioutil.TempDir("", t.Name())
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
 | 
			
		||||
	store, err := NewStore(dir, ds)
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	s := &VolumesService{vs: store, eventLogger: dummyEventLogger{}}
 | 
			
		||||
	return s, func() {
 | 
			
		||||
		assert.Check(t, s.Shutdown())
 | 
			
		||||
		assert.Check(t, os.RemoveAll(dir))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dummyEventLogger struct{}
 | 
			
		||||
 | 
			
		||||
func (dummyEventLogger) LogVolumeEvent(_, _ string, _ map[string]string) {}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,10 +13,12 @@ import (
 | 
			
		|||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/docker/docker/errdefs"
 | 
			
		||||
	"github.com/docker/docker/pkg/locker"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/docker/volume/drivers"
 | 
			
		||||
	volumemounts "github.com/docker/docker/volume/mounts"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -65,9 +69,8 @@ func (v volumeWrapper) CachedPath() string {
 | 
			
		|||
	return v.Volume.Path()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New initializes a VolumeStore to keep
 | 
			
		||||
// reference counting of volumes in the system.
 | 
			
		||||
func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
 | 
			
		||||
// NewStore creates a new volume store at the given path
 | 
			
		||||
func NewStore(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
 | 
			
		||||
	vs := &VolumeStore{
 | 
			
		||||
		locks:   &locker.Locker{},
 | 
			
		||||
		names:   make(map[string]volume.Volume),
 | 
			
		||||
| 
						 | 
				
			
			@ -84,10 +87,8 @@ func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
 | 
			
		|||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dbPath := filepath.Join(volPath, "metadata.db")
 | 
			
		||||
 | 
			
		||||
		var err error
 | 
			
		||||
		vs.db, err = bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
 | 
			
		||||
		vs.db, err = bolt.Open(filepath.Join(volPath, "metadata.db"), 0600, &bolt.Options{Timeout: 1 * time.Second})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "error while opening volume store metadata database")
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -152,10 +153,18 @@ func (s *VolumeStore) getRefs(name string) []string {
 | 
			
		|||
	return refs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Purge allows the cleanup of internal data on docker in case
 | 
			
		||||
// purge allows the cleanup of internal data on docker in case
 | 
			
		||||
// the internal data is out of sync with volumes driver plugins.
 | 
			
		||||
func (s *VolumeStore) Purge(name string) {
 | 
			
		||||
func (s *VolumeStore) purge(ctx context.Context, name string) error {
 | 
			
		||||
	s.globalLock.Lock()
 | 
			
		||||
	defer s.globalLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, exists := s.names[name]
 | 
			
		||||
	if exists {
 | 
			
		||||
		driverName := v.DriverName()
 | 
			
		||||
| 
						 | 
				
			
			@ -170,7 +179,7 @@ func (s *VolumeStore) Purge(name string) {
 | 
			
		|||
	delete(s.refs, name)
 | 
			
		||||
	delete(s.labels, name)
 | 
			
		||||
	delete(s.options, name)
 | 
			
		||||
	s.globalLock.Unlock()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
 | 
			
		||||
| 
						 | 
				
			
			@ -193,14 +202,137 @@ type VolumeStore struct {
 | 
			
		|||
	db      *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List proxies to all registered volume drivers to get the full list of volumes
 | 
			
		||||
func filterByDriver(names []string) filterFunc {
 | 
			
		||||
	return func(v volume.Volume) bool {
 | 
			
		||||
		for _, name := range names {
 | 
			
		||||
			if name == v.DriverName() {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *VolumeStore) byReferenced(referenced bool) filterFunc {
 | 
			
		||||
	return func(v volume.Volume) bool {
 | 
			
		||||
		return s.hasRef(v.Name()) == referenced
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *VolumeStore) filter(ctx context.Context, vols *[]volume.Volume, by By) (warnings []string, err error) {
 | 
			
		||||
	// note that this specifically does not support the `FromList` By type.
 | 
			
		||||
	switch f := by.(type) {
 | 
			
		||||
	case nil:
 | 
			
		||||
		if *vols == nil {
 | 
			
		||||
			var ls []volume.Volume
 | 
			
		||||
			ls, warnings, err = s.list(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return warnings, err
 | 
			
		||||
			}
 | 
			
		||||
			*vols = ls
 | 
			
		||||
		}
 | 
			
		||||
	case byDriver:
 | 
			
		||||
		if *vols != nil {
 | 
			
		||||
			filter(vols, filterByDriver([]string(f)))
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		var ls []volume.Volume
 | 
			
		||||
		ls, warnings, err = s.list(ctx, []string(f)...)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		*vols = ls
 | 
			
		||||
	case ByReferenced:
 | 
			
		||||
		// TODO(@cpuguy83): It would be nice to optimize this by looking at the list
 | 
			
		||||
		// of referenced volumes, however the locking strategy makes this difficult
 | 
			
		||||
		// without either providing inconsistent data or deadlocks.
 | 
			
		||||
		if *vols == nil {
 | 
			
		||||
			var ls []volume.Volume
 | 
			
		||||
			ls, warnings, err = s.list(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			*vols = ls
 | 
			
		||||
		}
 | 
			
		||||
		filter(vols, s.byReferenced(bool(f)))
 | 
			
		||||
	case andCombinator:
 | 
			
		||||
		for _, by := range f {
 | 
			
		||||
			w, err := s.filter(ctx, vols, by)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return warnings, err
 | 
			
		||||
			}
 | 
			
		||||
			warnings = append(warnings, w...)
 | 
			
		||||
		}
 | 
			
		||||
	case orCombinator:
 | 
			
		||||
		for _, by := range f {
 | 
			
		||||
			switch by.(type) {
 | 
			
		||||
			case byDriver:
 | 
			
		||||
				var ls []volume.Volume
 | 
			
		||||
				w, err := s.filter(ctx, &ls, by)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return warnings, err
 | 
			
		||||
				}
 | 
			
		||||
				warnings = append(warnings, w...)
 | 
			
		||||
			default:
 | 
			
		||||
				ls, w, err := s.list(ctx)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return warnings, err
 | 
			
		||||
				}
 | 
			
		||||
				warnings = append(warnings, w...)
 | 
			
		||||
				w, err = s.filter(ctx, &ls, by)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return warnings, err
 | 
			
		||||
				}
 | 
			
		||||
				warnings = append(warnings, w...)
 | 
			
		||||
				*vols = append(*vols, ls...)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		unique(vols)
 | 
			
		||||
	case CustomFilter:
 | 
			
		||||
		if *vols == nil {
 | 
			
		||||
			var ls []volume.Volume
 | 
			
		||||
			ls, warnings, err = s.list(ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			*vols = ls
 | 
			
		||||
		}
 | 
			
		||||
		filter(vols, filterFunc(f))
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errdefs.InvalidParameter(errors.Errorf("unsupported filter: %T", f))
 | 
			
		||||
	}
 | 
			
		||||
	return warnings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unique(ls *[]volume.Volume) {
 | 
			
		||||
	names := make(map[string]bool, len(*ls))
 | 
			
		||||
	filter(ls, func(v volume.Volume) bool {
 | 
			
		||||
		if names[v.Name()] {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		names[v.Name()] = true
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find lists volumes filtered by the past in filter.
 | 
			
		||||
// If a driver returns a volume that has name which conflicts with another volume from a different driver,
 | 
			
		||||
// the first volume is chosen and the conflicting volume is dropped.
 | 
			
		||||
func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
 | 
			
		||||
	vols, warnings, err := s.list()
 | 
			
		||||
func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) {
 | 
			
		||||
	logrus.WithField("ByType", fmt.Sprintf("%T", by)).WithField("ByValue", fmt.Sprintf("%+v", by)).Debug("VolumeStore.Find")
 | 
			
		||||
	switch f := by.(type) {
 | 
			
		||||
	case nil, orCombinator, andCombinator, byDriver, ByReferenced, CustomFilter:
 | 
			
		||||
		warnings, err = s.filter(ctx, &vols, by)
 | 
			
		||||
	case fromList:
 | 
			
		||||
		warnings, err = s.filter(ctx, f.ls, f.by)
 | 
			
		||||
	default:
 | 
			
		||||
		// Really shouldn't be possible, but makes sure that any new By's are added to this check.
 | 
			
		||||
		err = errdefs.InvalidParameter(errors.Errorf("unsupported filter type: %T", f))
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, &OpErr{Err: err, Op: "list"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var out []volume.Volume
 | 
			
		||||
 | 
			
		||||
	for _, v := range vols {
 | 
			
		||||
| 
						 | 
				
			
			@ -222,26 +354,59 @@ func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
 | 
			
		|||
	return out, warnings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type filterFunc func(volume.Volume) bool
 | 
			
		||||
 | 
			
		||||
func filter(vols *[]volume.Volume, fn filterFunc) {
 | 
			
		||||
	var evict []int
 | 
			
		||||
	for i, v := range *vols {
 | 
			
		||||
		if !fn(v) {
 | 
			
		||||
			evict = append(evict, i)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for n, i := range evict {
 | 
			
		||||
		copy((*vols)[i-n:], (*vols)[i-n+1:])
 | 
			
		||||
		(*vols)[len(*vols)-1] = nil
 | 
			
		||||
		*vols = (*vols)[:len(*vols)-1]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// list goes through each volume driver and asks for its list of volumes.
 | 
			
		||||
func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
 | 
			
		||||
// TODO(@cpuguy83): plumb context through
 | 
			
		||||
func (s *VolumeStore) list(ctx context.Context, driverNames ...string) ([]volume.Volume, []string, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		ls       []volume.Volume
 | 
			
		||||
		ls       = []volume.Volume{} // do not return a nil value as this affects filtering
 | 
			
		||||
		warnings []string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	drivers, err := s.drivers.GetAllDrivers()
 | 
			
		||||
	var dls []volume.Driver
 | 
			
		||||
 | 
			
		||||
	all, err := s.drivers.GetAllDrivers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(driverNames) == 0 {
 | 
			
		||||
		dls = all
 | 
			
		||||
	} else {
 | 
			
		||||
		idx := make(map[string]bool, len(driverNames))
 | 
			
		||||
		for _, name := range driverNames {
 | 
			
		||||
			idx[name] = true
 | 
			
		||||
		}
 | 
			
		||||
		for _, d := range all {
 | 
			
		||||
			if idx[d.Name()] {
 | 
			
		||||
				dls = append(dls, d)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type vols struct {
 | 
			
		||||
		vols       []volume.Volume
 | 
			
		||||
		err        error
 | 
			
		||||
		driverName string
 | 
			
		||||
	}
 | 
			
		||||
	chVols := make(chan vols, len(drivers))
 | 
			
		||||
	chVols := make(chan vols, len(dls))
 | 
			
		||||
 | 
			
		||||
	for _, vd := range drivers {
 | 
			
		||||
	for _, vd := range dls {
 | 
			
		||||
		go func(d volume.Driver) {
 | 
			
		||||
			vs, err := d.List()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -259,13 +424,12 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	badDrivers := make(map[string]struct{})
 | 
			
		||||
	for i := 0; i < len(drivers); i++ {
 | 
			
		||||
	for i := 0; i < len(dls); i++ {
 | 
			
		||||
		vs := <-chVols
 | 
			
		||||
 | 
			
		||||
		if vs.err != nil {
 | 
			
		||||
			warnings = append(warnings, vs.err.Error())
 | 
			
		||||
			badDrivers[vs.driverName] = struct{}{}
 | 
			
		||||
			logrus.Warn(vs.err)
 | 
			
		||||
		}
 | 
			
		||||
		ls = append(ls, vs.vols...)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -282,14 +446,26 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
 | 
			
		|||
	return ls, warnings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateWithRef creates a volume with the given name and driver and stores the ref
 | 
			
		||||
// This ensures there's no race between creating a volume and then storing a reference.
 | 
			
		||||
func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels map[string]string) (volume.Volume, error) {
 | 
			
		||||
// Create creates a volume with the given name and driver
 | 
			
		||||
// If the volume needs to be created with a reference to prevent race conditions
 | 
			
		||||
// with volume cleanup, make sure to use the `CreateWithReference` option.
 | 
			
		||||
func (s *VolumeStore) Create(ctx context.Context, name, driverName string, createOpts ...opts.CreateOption) (volume.Volume, error) {
 | 
			
		||||
	var cfg opts.CreateConfig
 | 
			
		||||
	for _, o := range createOpts {
 | 
			
		||||
		o(&cfg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name = normalizeVolumeName(name)
 | 
			
		||||
	s.locks.Lock(name)
 | 
			
		||||
	defer s.locks.Unlock(name)
 | 
			
		||||
 | 
			
		||||
	v, err := s.create(name, driverName, opts, labels)
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		return nil, ctx.Err()
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := s.create(ctx, name, driverName, cfg.Options, cfg.Labels)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if _, ok := err.(*OpErr); ok {
 | 
			
		||||
			return nil, err
 | 
			
		||||
| 
						 | 
				
			
			@ -297,16 +473,10 @@ func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels m
 | 
			
		|||
		return nil, &OpErr{Err: err, Name: name, Op: "create"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.setNamed(v, ref)
 | 
			
		||||
	s.setNamed(v, cfg.Reference)
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create creates a volume with the given name and driver.
 | 
			
		||||
// This is just like CreateWithRef() except we don't store a reference while holding the lock.
 | 
			
		||||
func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
 | 
			
		||||
	return s.CreateWithRef(name, driverName, "", opts, labels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkConflict checks the local cache for name collisions with the passed in name,
 | 
			
		||||
// for existing volumes with the same name but in a different driver.
 | 
			
		||||
// This is used by `Create` as a best effort to prevent name collisions for volumes.
 | 
			
		||||
| 
						 | 
				
			
			@ -320,7 +490,7 @@ func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]st
 | 
			
		|||
// TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially
 | 
			
		||||
// use a connect timeout for this kind of check to ensure we aren't blocking for a
 | 
			
		||||
// long time.
 | 
			
		||||
func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, error) {
 | 
			
		||||
func (s *VolumeStore) checkConflict(ctx context.Context, name, driverName string) (volume.Volume, error) {
 | 
			
		||||
	// check the local cache
 | 
			
		||||
	v, _ := s.getNamed(name)
 | 
			
		||||
	if v == nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -344,7 +514,7 @@ func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, err
 | 
			
		|||
 | 
			
		||||
	// let's check if the found volume ref
 | 
			
		||||
	// is stale by checking with the driver if it still exists
 | 
			
		||||
	exists, err := volumeExists(s.drivers, v)
 | 
			
		||||
	exists, err := volumeExists(ctx, s.drivers, v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(errNameConflict, "found reference to volume '%s' in driver '%s', but got an error while checking the driver: %v", name, vDriverName, err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -363,14 +533,14 @@ func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, err
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// doesn't exist, so purge it from the cache
 | 
			
		||||
	s.Purge(name)
 | 
			
		||||
	s.purge(ctx, name)
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// volumeExists returns if the volume is still present in the driver.
 | 
			
		||||
// An error is returned if there was an issue communicating with the driver.
 | 
			
		||||
func volumeExists(store *drivers.Store, v volume.Volume) (bool, error) {
 | 
			
		||||
	exists, err := lookupVolume(store, v.DriverName(), v.Name())
 | 
			
		||||
func volumeExists(ctx context.Context, store *drivers.Store, v volume.Volume) (bool, error) {
 | 
			
		||||
	exists, err := lookupVolume(ctx, store, v.DriverName(), v.Name())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -383,7 +553,7 @@ func volumeExists(store *drivers.Store, v volume.Volume) (bool, error) {
 | 
			
		|||
//  for the given volume name, an error is returned after checking if the reference is stale.
 | 
			
		||||
// If the reference is stale, it will be purged and this create can continue.
 | 
			
		||||
// It is expected that callers of this function hold any necessary locks.
 | 
			
		||||
func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
 | 
			
		||||
func (s *VolumeStore) create(ctx context.Context, name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
 | 
			
		||||
	// Validate the name in a platform-specific manner
 | 
			
		||||
 | 
			
		||||
	// volume name validation is specific to the host os and not on container image
 | 
			
		||||
| 
						 | 
				
			
			@ -394,7 +564,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := s.checkConflict(name, driverName)
 | 
			
		||||
	v, err := s.checkConflict(ctx, name, driverName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -409,7 +579,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
 | 
			
		|||
 | 
			
		||||
	// Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name
 | 
			
		||||
	if driverName == "" {
 | 
			
		||||
		v, _ = s.getVolume(name)
 | 
			
		||||
		v, _ = s.getVolume(ctx, name, "")
 | 
			
		||||
		if v != nil {
 | 
			
		||||
			return v, nil
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -453,61 +623,57 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
 | 
			
		|||
	return volumeWrapper{v, labels, vd.Scope(), opts}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetWithRef gets a volume with the given name from the passed in driver and stores the ref
 | 
			
		||||
// This is just like Get(), but we store the reference while holding the lock.
 | 
			
		||||
// This makes sure there are no races between checking for the existence of a volume and adding a reference for it
 | 
			
		||||
func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, error) {
 | 
			
		||||
	name = normalizeVolumeName(name)
 | 
			
		||||
	s.locks.Lock(name)
 | 
			
		||||
	defer s.locks.Unlock(name)
 | 
			
		||||
 | 
			
		||||
	if driverName == "" {
 | 
			
		||||
		driverName = volume.DefaultDriverName
 | 
			
		||||
	}
 | 
			
		||||
	vd, err := s.drivers.GetDriver(driverName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, &OpErr{Err: err, Name: name, Op: "get"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := vd.Get(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, &OpErr{Err: err, Name: name, Op: "get"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.setNamed(v, ref)
 | 
			
		||||
 | 
			
		||||
	s.globalLock.RLock()
 | 
			
		||||
	defer s.globalLock.RUnlock()
 | 
			
		||||
	return volumeWrapper{v, s.labels[name], vd.Scope(), s.options[name]}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get looks if a volume with the given name exists and returns it if so
 | 
			
		||||
func (s *VolumeStore) Get(name string) (volume.Volume, error) {
 | 
			
		||||
func (s *VolumeStore) Get(ctx context.Context, name string, getOptions ...opts.GetOption) (volume.Volume, error) {
 | 
			
		||||
	var cfg opts.GetConfig
 | 
			
		||||
	for _, o := range getOptions {
 | 
			
		||||
		o(&cfg)
 | 
			
		||||
	}
 | 
			
		||||
	name = normalizeVolumeName(name)
 | 
			
		||||
	s.locks.Lock(name)
 | 
			
		||||
	defer s.locks.Unlock(name)
 | 
			
		||||
 | 
			
		||||
	v, err := s.getVolume(name)
 | 
			
		||||
	v, err := s.getVolume(ctx, name, cfg.Driver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, &OpErr{Err: err, Name: name, Op: "get"}
 | 
			
		||||
	}
 | 
			
		||||
	s.setNamed(v, "")
 | 
			
		||||
	if cfg.Driver != "" && v.DriverName() != cfg.Driver {
 | 
			
		||||
		return nil, &OpErr{Name: name, Op: "get", Err: errdefs.Conflict(errors.New("found volume driver does not match passed in driver"))}
 | 
			
		||||
	}
 | 
			
		||||
	s.setNamed(v, cfg.Reference)
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getVolume requests the volume, if the driver info is stored it just accesses that driver,
 | 
			
		||||
// if the driver is unknown it probes all drivers until it finds the first volume with that name.
 | 
			
		||||
// it is expected that callers of this function hold any necessary locks
 | 
			
		||||
func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
 | 
			
		||||
func (s *VolumeStore) getVolume(ctx context.Context, name, driverName string) (volume.Volume, error) {
 | 
			
		||||
	var meta volumeMetadata
 | 
			
		||||
	meta, err := s.getMeta(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	driverName := meta.Driver
 | 
			
		||||
	if driverName != "" {
 | 
			
		||||
		if meta.Driver == "" {
 | 
			
		||||
			meta.Driver = driverName
 | 
			
		||||
		}
 | 
			
		||||
		if driverName != meta.Driver {
 | 
			
		||||
			return nil, errdefs.Conflict(errors.New("provided volume driver does not match stored driver"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if driverName == "" {
 | 
			
		||||
		driverName = meta.Driver
 | 
			
		||||
	}
 | 
			
		||||
	if driverName == "" {
 | 
			
		||||
		s.globalLock.RLock()
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			s.globalLock.RUnlock()
 | 
			
		||||
			return nil, ctx.Err()
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
		v, exists := s.names[name]
 | 
			
		||||
		s.globalLock.RUnlock()
 | 
			
		||||
		if exists {
 | 
			
		||||
| 
						 | 
				
			
			@ -519,12 +685,12 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if meta.Driver != "" {
 | 
			
		||||
		vol, err := lookupVolume(s.drivers, meta.Driver, name)
 | 
			
		||||
		vol, err := lookupVolume(ctx, s.drivers, meta.Driver, name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if vol == nil {
 | 
			
		||||
			s.Purge(name)
 | 
			
		||||
			s.purge(ctx, name)
 | 
			
		||||
			return nil, errNoSuchVolume
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -543,6 +709,11 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range drivers {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			return nil, ctx.Err()
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
		v, err := d.Get(name)
 | 
			
		||||
		if err != nil || v == nil {
 | 
			
		||||
			continue
 | 
			
		||||
| 
						 | 
				
			
			@ -561,7 +732,8 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
 | 
			
		|||
// If the driver returns an error that is not communication related the
 | 
			
		||||
//   error is logged but not returned.
 | 
			
		||||
// If the volume is not found it will return `nil, nil``
 | 
			
		||||
func lookupVolume(store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
 | 
			
		||||
// TODO(@cpuguy83): plumb through the context to lower level components
 | 
			
		||||
func lookupVolume(ctx context.Context, store *drivers.Store, driverName, volumeName string) (volume.Volume, error) {
 | 
			
		||||
	if driverName == "" {
 | 
			
		||||
		driverName = volume.DefaultDriverName
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -582,19 +754,35 @@ func lookupVolume(store *drivers.Store, driverName, volumeName string) (volume.V
 | 
			
		|||
 | 
			
		||||
		// At this point, the error could be anything from the driver, such as "no such volume"
 | 
			
		||||
		// Let's not check an error here, and instead check if the driver returned a volume
 | 
			
		||||
		logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Warnf("Error while looking up volume")
 | 
			
		||||
		logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Debug("Error while looking up volume")
 | 
			
		||||
	}
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove removes the requested volume. A volume is not removed if it has any refs
 | 
			
		||||
func (s *VolumeStore) Remove(v volume.Volume) error {
 | 
			
		||||
	name := normalizeVolumeName(v.Name())
 | 
			
		||||
func (s *VolumeStore) Remove(ctx context.Context, v volume.Volume, rmOpts ...opts.RemoveOption) error {
 | 
			
		||||
	var cfg opts.RemoveConfig
 | 
			
		||||
	for _, o := range rmOpts {
 | 
			
		||||
		o(&cfg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name := v.Name()
 | 
			
		||||
	s.locks.Lock(name)
 | 
			
		||||
	defer s.locks.Unlock(name)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if s.hasRef(name) {
 | 
			
		||||
		return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: s.getRefs(name)}
 | 
			
		||||
		return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove", Refs: s.getRefs(name)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := s.getVolume(ctx, name, v.DriverName())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vd, err := s.drivers.GetDriver(v.DriverName())
 | 
			
		||||
| 
						 | 
				
			
			@ -604,85 +792,55 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
 | 
			
		|||
 | 
			
		||||
	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
 | 
			
		||||
	vol := unwrapVolume(v)
 | 
			
		||||
	if err := vd.Remove(vol); err != nil {
 | 
			
		||||
		return &OpErr{Err: err, Name: name, Op: "remove"}
 | 
			
		||||
 | 
			
		||||
	err = vd.Remove(vol)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = &OpErr{Err: err, Name: name, Op: "remove"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.Purge(name)
 | 
			
		||||
	return nil
 | 
			
		||||
	if err == nil || cfg.PurgeOnError {
 | 
			
		||||
		if e := s.purge(ctx, name); e != nil && err == nil {
 | 
			
		||||
			err = e
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dereference removes the specified reference to the volume
 | 
			
		||||
func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
 | 
			
		||||
	name := v.Name()
 | 
			
		||||
 | 
			
		||||
// Release releases the specified reference to the volume
 | 
			
		||||
func (s *VolumeStore) Release(ctx context.Context, name string, ref string) error {
 | 
			
		||||
	s.locks.Lock(name)
 | 
			
		||||
	defer s.locks.Unlock(name)
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.globalLock.Lock()
 | 
			
		||||
	defer s.globalLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if s.refs[name] != nil {
 | 
			
		||||
		delete(s.refs[name], ref)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Refs gets the current list of refs for the given volume
 | 
			
		||||
func (s *VolumeStore) Refs(v volume.Volume) []string {
 | 
			
		||||
	name := v.Name()
 | 
			
		||||
// CountReferences gives a count of all references for a given volume.
 | 
			
		||||
func (s *VolumeStore) CountReferences(v volume.Volume) int {
 | 
			
		||||
	name := normalizeVolumeName(v.Name())
 | 
			
		||||
 | 
			
		||||
	s.locks.Lock(name)
 | 
			
		||||
	defer s.locks.Unlock(name)
 | 
			
		||||
	s.globalLock.Lock()
 | 
			
		||||
	defer s.globalLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	return s.getRefs(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByDriver returns the available volumes filtered by driver name
 | 
			
		||||
func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
 | 
			
		||||
	vd, err := s.drivers.GetDriver(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, &OpErr{Err: err, Name: name, Op: "list"}
 | 
			
		||||
	}
 | 
			
		||||
	ls, err := vd.List()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, &OpErr{Err: err, Name: name, Op: "list"}
 | 
			
		||||
	}
 | 
			
		||||
	for i, v := range ls {
 | 
			
		||||
		options := map[string]string{}
 | 
			
		||||
		s.globalLock.RLock()
 | 
			
		||||
		for key, value := range s.options[v.Name()] {
 | 
			
		||||
			options[key] = value
 | 
			
		||||
		}
 | 
			
		||||
		ls[i] = volumeWrapper{v, s.labels[v.Name()], vd.Scope(), options}
 | 
			
		||||
		s.globalLock.RUnlock()
 | 
			
		||||
	}
 | 
			
		||||
	return ls, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByUsed returns the available volumes filtered by if they are in use or not.
 | 
			
		||||
// `used=true` returns only volumes that are being used, while `used=false` returns
 | 
			
		||||
// only volumes that are not being used.
 | 
			
		||||
func (s *VolumeStore) FilterByUsed(vols []volume.Volume, used bool) []volume.Volume {
 | 
			
		||||
	return s.filter(vols, func(v volume.Volume) bool {
 | 
			
		||||
		s.locks.Lock(v.Name())
 | 
			
		||||
		hasRef := s.hasRef(v.Name())
 | 
			
		||||
		s.locks.Unlock(v.Name())
 | 
			
		||||
		return used == hasRef
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// filterFunc defines a function to allow filter volumes in the store
 | 
			
		||||
type filterFunc func(vol volume.Volume) bool
 | 
			
		||||
 | 
			
		||||
// filter returns the available volumes filtered by a filterFunc function
 | 
			
		||||
func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume {
 | 
			
		||||
	var ls []volume.Volume
 | 
			
		||||
	for _, v := range vols {
 | 
			
		||||
		if f(v) {
 | 
			
		||||
			ls = append(ls, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ls
 | 
			
		||||
	return len(s.refs[name])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unwrapVolume(v volume.Volume) volume.Volume {
 | 
			
		||||
| 
						 | 
				
			
			@ -698,10 +856,3 @@ func unwrapVolume(v volume.Volume) volume.Volume {
 | 
			
		|||
func (s *VolumeStore) Shutdown() error {
 | 
			
		||||
	return s.db.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDriverList gets the list of volume drivers from the configured volume driver
 | 
			
		||||
// store.
 | 
			
		||||
// TODO(@cpuguy83): This should be factored out into a separate service.
 | 
			
		||||
func (s *VolumeStore) GetDriverList() []string {
 | 
			
		||||
	return s.drivers.GetDriverList()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +12,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/service/opts"
 | 
			
		||||
	volumetestutils "github.com/docker/docker/volume/testutils"
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	"github.com/gotestyourself/gotestyourself/assert"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,22 +26,23 @@ func TestCreate(t *testing.T) {
 | 
			
		|||
	defer cleanup()
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
 | 
			
		||||
 | 
			
		||||
	v, err := s.Create("fake1", "fake", nil, nil)
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	v, err := s.Create(ctx, "fake1", "fake")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if v.Name() != "fake1" {
 | 
			
		||||
		t.Fatalf("Expected fake1 volume, got %v", v)
 | 
			
		||||
	}
 | 
			
		||||
	if l, _, _ := s.List(); len(l) != 1 {
 | 
			
		||||
	if l, _, _ := s.Find(ctx, nil); len(l) != 1 {
 | 
			
		||||
		t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := s.Create("none", "none", nil, nil); err == nil {
 | 
			
		||||
	if _, err := s.Create(ctx, "none", "none"); err == nil {
 | 
			
		||||
		t.Fatalf("Expected unknown driver error, got nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"}, nil)
 | 
			
		||||
	_, err = s.Create(ctx, "fakeerror", "fake", opts.WithCreateOptions(map[string]string{"error": "create error"}))
 | 
			
		||||
	expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
 | 
			
		||||
	if err != nil && err.Error() != expected.Error() {
 | 
			
		||||
		t.Fatalf("Expected create fakeError: create error, got %v", err)
 | 
			
		||||
| 
						 | 
				
			
			@ -55,25 +58,28 @@ func TestRemove(t *testing.T) {
 | 
			
		|||
	s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	// doing string compare here since this error comes directly from the driver
 | 
			
		||||
	expected := "no such volume"
 | 
			
		||||
	if err := s.Remove(volumetestutils.NoopVolume{}); err == nil || !strings.Contains(err.Error(), expected) {
 | 
			
		||||
	var v volume.Volume = volumetestutils.NoopVolume{}
 | 
			
		||||
	if err := s.Remove(ctx, v); err == nil || !strings.Contains(err.Error(), expected) {
 | 
			
		||||
		t.Fatalf("Expected error %q, got %v", expected, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := s.CreateWithRef("fake1", "fake", "fake", nil, nil)
 | 
			
		||||
	v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("fake"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := s.Remove(v); !IsInUse(err) {
 | 
			
		||||
	if err := s.Remove(ctx, v); !IsInUse(err) {
 | 
			
		||||
		t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	s.Dereference(v, "fake")
 | 
			
		||||
	if err := s.Remove(v); err != nil {
 | 
			
		||||
	s.Release(ctx, v.Name(), "fake")
 | 
			
		||||
	if err := s.Remove(ctx, v); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if l, _, _ := s.List(); len(l) != 0 {
 | 
			
		||||
	if l, _, _ := s.Find(ctx, nil); len(l) != 0 {
 | 
			
		||||
		t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -89,17 +95,18 @@ func TestList(t *testing.T) {
 | 
			
		|||
	drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
 | 
			
		||||
	drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
 | 
			
		||||
 | 
			
		||||
	s, err := New(dir, drivers)
 | 
			
		||||
	s, err := NewStore(dir, drivers)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	if _, err := s.Create("test", "fake", nil, nil); err != nil {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	if _, err := s.Create(ctx, "test", "fake"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := s.Create("test2", "fake2", nil, nil); err != nil {
 | 
			
		||||
	if _, err := s.Create(ctx, "test2", "fake2"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ls, _, err := s.List()
 | 
			
		||||
	ls, _, err := s.Find(ctx, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -111,11 +118,11 @@ func TestList(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// and again with a new store
 | 
			
		||||
	s, err = New(dir, drivers)
 | 
			
		||||
	s, err = NewStore(dir, drivers)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	ls, _, err = s.List()
 | 
			
		||||
	ls, _, err = s.Find(ctx, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -124,34 +131,38 @@ func TestList(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterByDriver(t *testing.T) {
 | 
			
		||||
func TestFindByDriver(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
	s, cleanup := setupTest(t)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
 | 
			
		||||
	assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake"))
 | 
			
		||||
	assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop"))
 | 
			
		||||
 | 
			
		||||
	if _, err := s.Create("fake1", "fake", nil, nil); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := s.Create("fake3", "noop", nil, nil); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	_, err := s.Create(ctx, "fake1", "fake")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
 | 
			
		||||
		t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
 | 
			
		||||
	}
 | 
			
		||||
	_, err = s.Create(ctx, "fake2", "fake")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
 | 
			
		||||
		t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
 | 
			
		||||
	}
 | 
			
		||||
	_, err = s.Create(ctx, "fake3", "noop")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	l, _, err := s.Find(ctx, ByDriver("fake"))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.Equal(t, len(l), 2)
 | 
			
		||||
 | 
			
		||||
	l, _, err = s.Find(ctx, ByDriver("noop"))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.Equal(t, len(l), 1)
 | 
			
		||||
 | 
			
		||||
	l, _, err = s.Find(ctx, ByDriver("nosuchdriver"))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.Equal(t, len(l), 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterByUsed(t *testing.T) {
 | 
			
		||||
func TestFindByReferenced(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
	s, cleanup := setupTest(t)
 | 
			
		||||
	defer cleanup()
 | 
			
		||||
| 
						 | 
				
			
			@ -159,33 +170,23 @@ func TestFilterByUsed(t *testing.T) {
 | 
			
		|||
	s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
 | 
			
		||||
 | 
			
		||||
	if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil); err != nil {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	if _, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference")); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
 | 
			
		||||
	if _, err := s.Create(ctx, "fake2", "fake"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vols, _, err := s.List()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	dangling, _, err := s.Find(ctx, ByReferenced(false))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, len(dangling) == 1)
 | 
			
		||||
	assert.Check(t, dangling[0].Name() == "fake2")
 | 
			
		||||
 | 
			
		||||
	dangling := s.FilterByUsed(vols, false)
 | 
			
		||||
	if len(dangling) != 1 {
 | 
			
		||||
		t.Fatalf("expected 1 dangling volume, got %v", len(dangling))
 | 
			
		||||
	}
 | 
			
		||||
	if dangling[0].Name() != "fake2" {
 | 
			
		||||
		t.Fatalf("expected dangling volume fake2, got %s", dangling[0].Name())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	used := s.FilterByUsed(vols, true)
 | 
			
		||||
	if len(used) != 1 {
 | 
			
		||||
		t.Fatalf("expected 1 used volume, got %v", len(used))
 | 
			
		||||
	}
 | 
			
		||||
	if used[0].Name() != "fake1" {
 | 
			
		||||
		t.Fatalf("expected used volume fake1, got %s", used[0].Name())
 | 
			
		||||
	}
 | 
			
		||||
	used, _, err := s.Find(ctx, ByReferenced(true))
 | 
			
		||||
	assert.Assert(t, err)
 | 
			
		||||
	assert.Assert(t, len(used) == 1)
 | 
			
		||||
	assert.Check(t, used[0].Name() == "fake1")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDerefMultipleOfSameRef(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -194,17 +195,18 @@ func TestDerefMultipleOfSameRef(t *testing.T) {
 | 
			
		|||
	defer cleanup()
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
 | 
			
		||||
 | 
			
		||||
	v, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil)
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	v, err := s.Create(ctx, "fake1", "fake", opts.WithCreateReference("volReference"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := s.GetWithRef("fake1", "fake", "volReference"); err != nil {
 | 
			
		||||
	if _, err := s.Get(ctx, "fake1", opts.WithGetDriver("fake"), opts.WithGetReference("volReference")); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.Dereference(v, "volReference")
 | 
			
		||||
	if err := s.Remove(v); err != nil {
 | 
			
		||||
	s.Release(ctx, v.Name(), "volReference")
 | 
			
		||||
	if err := s.Remove(ctx, v); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +224,8 @@ func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
 | 
			
		|||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := s.Create("foo", "fake", nil, map[string]string{"hello": "world"})
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	v, err := s.Create(ctx, "foo", "fake", opts.WithCreateLabels(map[string]string{"hello": "world"}))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -265,14 +268,15 @@ func TestDefererencePluginOnCreateError(t *testing.T) {
 | 
			
		|||
	pg := volumetestutils.NewFakePluginGetter(p)
 | 
			
		||||
	s.drivers = volumedrivers.NewStore(pg)
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	// create a good volume so we have a plugin reference
 | 
			
		||||
	_, err = s.Create("fake1", d.Name(), nil, nil)
 | 
			
		||||
	_, err = s.Create(ctx, "fake1", d.Name())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now create another one expecting an error
 | 
			
		||||
	_, err = s.Create("fake2", d.Name(), map[string]string{"error": "some error"}, nil)
 | 
			
		||||
	_, err = s.Create(ctx, "fake2", d.Name(), opts.WithCreateOptions(map[string]string{"error": "some error"}))
 | 
			
		||||
	if err == nil || !strings.Contains(err.Error(), "some error") {
 | 
			
		||||
		t.Fatalf("expected an error on create: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -291,15 +295,16 @@ func TestRefDerefRemove(t *testing.T) {
 | 
			
		|||
	defer cleanup()
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
 | 
			
		||||
 | 
			
		||||
	v, err := s.CreateWithRef("test", driverName, "test-ref", nil, nil)
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	v, err := s.Create(ctx, "test", driverName, opts.WithCreateReference("test-ref"))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	err = s.Remove(v)
 | 
			
		||||
	err = s.Remove(ctx, v)
 | 
			
		||||
	assert.Assert(t, is.ErrorContains(err, ""))
 | 
			
		||||
	assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
 | 
			
		||||
 | 
			
		||||
	s.Dereference(v, "test-ref")
 | 
			
		||||
	err = s.Remove(v)
 | 
			
		||||
	s.Release(ctx, v.Name(), "test-ref")
 | 
			
		||||
	err = s.Remove(ctx, v)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -311,25 +316,26 @@ func TestGet(t *testing.T) {
 | 
			
		|||
	defer cleanup()
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
 | 
			
		||||
 | 
			
		||||
	_, err := s.Get("not-exist")
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	_, err := s.Get(ctx, "not-exist")
 | 
			
		||||
	assert.Assert(t, is.ErrorContains(err, ""))
 | 
			
		||||
	assert.Equal(t, errNoSuchVolume, err.(*OpErr).Err)
 | 
			
		||||
 | 
			
		||||
	v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"})
 | 
			
		||||
	v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	v2, err := s.Get("test")
 | 
			
		||||
	v2, err := s.Get(ctx, "test")
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.DeepEqual(t, v1, v2, cmpVolume)
 | 
			
		||||
 | 
			
		||||
	dv := v2.(volume.DetailedVolume)
 | 
			
		||||
	assert.Equal(t, "1", dv.Labels()["a"])
 | 
			
		||||
 | 
			
		||||
	err = s.Remove(v1)
 | 
			
		||||
	err = s.Remove(ctx, v1)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetWithRef(t *testing.T) {
 | 
			
		||||
func TestGetWithReference(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 | 
			
		||||
	driverName := "test-get-with-ref"
 | 
			
		||||
| 
						 | 
				
			
			@ -337,22 +343,23 @@ func TestGetWithRef(t *testing.T) {
 | 
			
		|||
	defer cleanup()
 | 
			
		||||
	s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
 | 
			
		||||
 | 
			
		||||
	_, err := s.GetWithRef("not-exist", driverName, "test-ref")
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	_, err := s.Get(ctx, "not-exist", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
 | 
			
		||||
	assert.Assert(t, is.ErrorContains(err, ""))
 | 
			
		||||
 | 
			
		||||
	v1, err := s.Create("test", driverName, nil, map[string]string{"a": "1"})
 | 
			
		||||
	v1, err := s.Create(ctx, "test", driverName, opts.WithCreateLabels(map[string]string{"a": "1"}))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	v2, err := s.GetWithRef("test", driverName, "test-ref")
 | 
			
		||||
	v2, err := s.Get(ctx, "test", opts.WithGetDriver(driverName), opts.WithGetReference("test-ref"))
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	assert.DeepEqual(t, v1, v2, cmpVolume)
 | 
			
		||||
 | 
			
		||||
	err = s.Remove(v2)
 | 
			
		||||
	err = s.Remove(ctx, v2)
 | 
			
		||||
	assert.Assert(t, is.ErrorContains(err, ""))
 | 
			
		||||
	assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
 | 
			
		||||
 | 
			
		||||
	s.Dereference(v2, "test-ref")
 | 
			
		||||
	err = s.Remove(v2)
 | 
			
		||||
	s.Release(ctx, v2.Name(), "test-ref")
 | 
			
		||||
	err = s.Remove(ctx, v2)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -366,14 +373,49 @@ func setupTest(t *testing.T) (*VolumeStore, func()) {
 | 
			
		|||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	cleanup := func() {
 | 
			
		||||
		t.Helper()
 | 
			
		||||
		err := os.RemoveAll(dir)
 | 
			
		||||
		assert.Check(t, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s, err := New(dir, volumedrivers.NewStore(nil))
 | 
			
		||||
	s, err := NewStore(dir, volumedrivers.NewStore(nil))
 | 
			
		||||
	assert.Check(t, err)
 | 
			
		||||
	return s, func() {
 | 
			
		||||
		s.Shutdown()
 | 
			
		||||
		cleanup()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterFunc(t *testing.T) {
 | 
			
		||||
	testDriver := volumetestutils.NewFakeDriver("test")
 | 
			
		||||
	testVolume, err := testDriver.Create("test", nil)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	testVolume2, err := testDriver.Create("test2", nil)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	testVolume3, err := testDriver.Create("test3", nil)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
 | 
			
		||||
	for _, test := range []struct {
 | 
			
		||||
		vols   []volume.Volume
 | 
			
		||||
		fn     filterFunc
 | 
			
		||||
		desc   string
 | 
			
		||||
		expect []volume.Volume
 | 
			
		||||
	}{
 | 
			
		||||
		{desc: "test nil list", vols: nil, expect: nil, fn: func(volume.Volume) bool { return true }},
 | 
			
		||||
		{desc: "test empty list", vols: []volume.Volume{}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return true }},
 | 
			
		||||
		{desc: "test filter non-empty to empty", vols: []volume.Volume{testVolume}, expect: []volume.Volume{}, fn: func(volume.Volume) bool { return false }},
 | 
			
		||||
		{desc: "test nothing to fitler non-empty list", vols: []volume.Volume{testVolume}, expect: []volume.Volume{testVolume}, fn: func(volume.Volume) bool { return true }},
 | 
			
		||||
		{desc: "test filter some", vols: []volume.Volume{testVolume, testVolume2}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() == testVolume.Name() }},
 | 
			
		||||
		{desc: "test filter middle", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume, testVolume3}, fn: func(v volume.Volume) bool { return v.Name() != testVolume2.Name() }},
 | 
			
		||||
		{desc: "test filter middle and last", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume}, fn: func(v volume.Volume) bool { return v.Name() != testVolume2.Name() && v.Name() != testVolume3.Name() }},
 | 
			
		||||
		{desc: "test filter first and last", vols: []volume.Volume{testVolume, testVolume2, testVolume3}, expect: []volume.Volume{testVolume2}, fn: func(v volume.Volume) bool { return v.Name() != testVolume.Name() && v.Name() != testVolume3.Name() }},
 | 
			
		||||
	} {
 | 
			
		||||
		t.Run(test.desc, func(t *testing.T) {
 | 
			
		||||
			test := test
 | 
			
		||||
			t.Parallel()
 | 
			
		||||
 | 
			
		||||
			filter(&test.vols, test.fn)
 | 
			
		||||
			assert.DeepEqual(t, test.vols, test.expect, cmpVolume)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
// +build linux freebsd
 | 
			
		||||
// +build linux freebsd darwin
 | 
			
		||||
 | 
			
		||||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
// normalizeVolumeName is a platform specific function to normalize the name
 | 
			
		||||
// of a volume. This is a no-op on Unix-like platforms
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package store // import "github.com/docker/docker/volume/store"
 | 
			
		||||
package service // import "github.com/docker/docker/volume/service"
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +64,9 @@ func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
 | 
			
		|||
func (FakeVolume) Unmount(_ string) error { return nil }
 | 
			
		||||
 | 
			
		||||
// Status provides low-level details about the volume
 | 
			
		||||
func (FakeVolume) Status() map[string]interface{} { return nil }
 | 
			
		||||
func (FakeVolume) Status() map[string]interface{} {
 | 
			
		||||
	return map[string]interface{}{"datakey": "datavalue"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreatedAt provides the time the volume (directory) was created at
 | 
			
		||||
func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue