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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/docker/volume/service/opts"
|
||||||
// TODO return types need to be refactored into pkg
|
// TODO return types need to be refactored into pkg
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
@ -11,9 +12,9 @@ import (
|
||||||
// Backend is the methods that need to be implemented to provide
|
// Backend is the methods that need to be implemented to provide
|
||||||
// volume specific functionality
|
// volume specific functionality
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
Volumes(filter string) ([]*types.Volume, []string, error)
|
List(ctx context.Context, filter filters.Args) ([]*types.Volume, []string, error)
|
||||||
VolumeInspect(name string) (*types.Volume, error)
|
Get(ctx context.Context, name string, opts ...opts.GetOption) (*types.Volume, error)
|
||||||
VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error)
|
Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error)
|
||||||
VolumeRm(name string, force bool) error
|
Remove(ctx context.Context, name string, opts ...opts.RemoveOption) error
|
||||||
VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -11,6 +10,8 @@ import (
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
volumetypes "github.com/docker/docker/api/types/volume"
|
volumetypes "github.com/docker/docker/api/types/volume"
|
||||||
"github.com/docker/docker/errdefs"
|
"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 {
|
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
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -30,7 +35,7 @@ func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWrite
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
volume, err := v.backend.VolumeInspect(vars["name"])
|
volume, err := v.backend.Get(ctx, vars["name"], opts.WithGetResolveStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -54,7 +59,7 @@ func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWri
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -66,7 +71,7 @@ func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
force := httputils.BoolValue(r, "force")
|
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
|
return err
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
@ -83,7 +88,7 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneReport, err := v.backend.VolumesPrune(ctx, pruneFilters)
|
pruneReport, err := v.backend.Prune(ctx, pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -453,7 +453,7 @@ func initRouter(opts routerOptions) {
|
||||||
container.NewRouter(opts.daemon, decoder),
|
container.NewRouter(opts.daemon, decoder),
|
||||||
image.NewRouter(opts.daemon.ImageService()),
|
image.NewRouter(opts.daemon.ImageService()),
|
||||||
systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildCache),
|
systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildCache),
|
||||||
volume.NewRouter(opts.daemon),
|
volume.NewRouter(opts.daemon.VolumesService()),
|
||||||
build.NewRouter(opts.buildBackend, opts.daemon),
|
build.NewRouter(opts.buildBackend, opts.daemon),
|
||||||
sessionrouter.NewRouter(opts.sessionManager),
|
sessionrouter.NewRouter(opts.sessionManager),
|
||||||
swarmrouter.NewRouter(opts.cluster),
|
swarmrouter.NewRouter(opts.cluster),
|
||||||
|
@ -595,6 +595,7 @@ func createAndStartCluster(cli *DaemonCli, d *daemon.Daemon) (*cluster.Cluster,
|
||||||
Root: cli.Config.Root,
|
Root: cli.Config.Root,
|
||||||
Name: name,
|
Name: name,
|
||||||
Backend: d,
|
Backend: d,
|
||||||
|
VolumeBackend: d.VolumesService(),
|
||||||
ImageBackend: d.ImageService(),
|
ImageBackend: d.ImageService(),
|
||||||
PluginBackend: d.PluginManager(),
|
PluginBackend: d.PluginManager(),
|
||||||
NetworkSubnetsProvider: d,
|
NetworkSubnetsProvider: d,
|
||||||
|
|
|
@ -127,7 +127,7 @@ func (container *Container) CopyImagePathContent(v volume.Volume, destination st
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = ioutil.ReadDir(rootfs); err != nil {
|
if _, err := os.Stat(rootfs); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ type Config struct {
|
||||||
Backend executorpkg.Backend
|
Backend executorpkg.Backend
|
||||||
ImageBackend executorpkg.ImageBackend
|
ImageBackend executorpkg.ImageBackend
|
||||||
PluginBackend plugin.Backend
|
PluginBackend plugin.Backend
|
||||||
|
VolumeBackend executorpkg.VolumeBackend
|
||||||
NetworkSubnetsProvider NetworkSubnetsProvider
|
NetworkSubnetsProvider NetworkSubnetsProvider
|
||||||
|
|
||||||
// DefaultAdvertiseAddr is the default host/IP or network interface to use
|
// DefaultAdvertiseAddr is the default host/IP or network interface to use
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
||||||
networkSettings "github.com/docker/docker/daemon/network"
|
networkSettings "github.com/docker/docker/daemon/network"
|
||||||
"github.com/docker/docker/plugin"
|
"github.com/docker/docker/plugin"
|
||||||
|
volumeopts "github.com/docker/docker/volume/service/opts"
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
"github.com/docker/libnetwork/cluster"
|
"github.com/docker/libnetwork/cluster"
|
||||||
networktypes "github.com/docker/libnetwork/types"
|
networktypes "github.com/docker/libnetwork/types"
|
||||||
|
@ -47,7 +48,6 @@ type Backend interface {
|
||||||
SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error
|
SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error
|
||||||
SetContainerConfigReferences(name string, refs []*swarmtypes.ConfigReference) error
|
SetContainerConfigReferences(name string, refs []*swarmtypes.ConfigReference) error
|
||||||
SystemInfo() (*types.Info, error)
|
SystemInfo() (*types.Info, error)
|
||||||
VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error)
|
|
||||||
Containers(config *types.ContainerListOptions) ([]*types.Container, error)
|
Containers(config *types.ContainerListOptions) ([]*types.Container, error)
|
||||||
SetNetworkBootstrapKeys([]*networktypes.EncryptionKey) error
|
SetNetworkBootstrapKeys([]*networktypes.EncryptionKey) error
|
||||||
DaemonJoinsCluster(provider cluster.Provider)
|
DaemonJoinsCluster(provider cluster.Provider)
|
||||||
|
@ -62,6 +62,11 @@ type Backend interface {
|
||||||
GetAttachmentStore() *networkSettings.AttachmentStore
|
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
|
// ImageBackend is used by an executor to perform image operations
|
||||||
type ImageBackend interface {
|
type ImageBackend interface {
|
||||||
PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
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"
|
||||||
"github.com/docker/docker/daemon/cluster/convert"
|
"github.com/docker/docker/daemon/cluster/convert"
|
||||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||||
|
volumeopts "github.com/docker/docker/volume/service/opts"
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
"github.com/docker/swarmkit/agent/exec"
|
"github.com/docker/swarmkit/agent/exec"
|
||||||
"github.com/docker/swarmkit/api"
|
"github.com/docker/swarmkit/api"
|
||||||
|
@ -36,23 +37,25 @@ import (
|
||||||
// are mostly naked calls to the client API, seeded with information from
|
// are mostly naked calls to the client API, seeded with information from
|
||||||
// containerConfig.
|
// containerConfig.
|
||||||
type containerAdapter struct {
|
type containerAdapter struct {
|
||||||
backend executorpkg.Backend
|
backend executorpkg.Backend
|
||||||
imageBackend executorpkg.ImageBackend
|
imageBackend executorpkg.ImageBackend
|
||||||
container *containerConfig
|
volumeBackend executorpkg.VolumeBackend
|
||||||
dependencies exec.DependencyGetter
|
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)
|
ctnr, err := newContainerConfig(task, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &containerAdapter{
|
return &containerAdapter{
|
||||||
container: ctnr,
|
container: ctnr,
|
||||||
backend: b,
|
backend: b,
|
||||||
imageBackend: i,
|
imageBackend: i,
|
||||||
dependencies: dependencies,
|
volumeBackend: v,
|
||||||
|
dependencies: dependencies,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,7 +391,10 @@ func (c *containerAdapter) createVolumes(ctx context.Context) error {
|
||||||
req := c.container.volumeCreateRequest(&mount)
|
req := c.container.volumeCreateRequest(&mount)
|
||||||
|
|
||||||
// Check if this volume exists on the engine
|
// 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
|
// TODO(amitshukla): Today, volume create through the engine api does not return an error
|
||||||
// when the named volume with the same parameters already exists.
|
// 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
|
// 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{}
|
closed chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNetworkAttacherController(b executorpkg.Backend, i executorpkg.ImageBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*networkAttacherController, error) {
|
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, task, node, dependencies)
|
adapter, err := newContainerAdapter(b, i, v, task, node, dependencies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,8 @@ type controller struct {
|
||||||
var _ exec.Controller = &controller{}
|
var _ exec.Controller = &controller{}
|
||||||
|
|
||||||
// NewController returns a docker exec runner for the provided task.
|
// 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) {
|
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, task, node, dependencies)
|
adapter, err := newContainerAdapter(b, i, v, task, node, dependencies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,17 +28,19 @@ type executor struct {
|
||||||
backend executorpkg.Backend
|
backend executorpkg.Backend
|
||||||
imageBackend executorpkg.ImageBackend
|
imageBackend executorpkg.ImageBackend
|
||||||
pluginBackend plugin.Backend
|
pluginBackend plugin.Backend
|
||||||
|
volumeBackend executorpkg.VolumeBackend
|
||||||
dependencies exec.DependencyManager
|
dependencies exec.DependencyManager
|
||||||
mutex sync.Mutex // This mutex protects the following node field
|
mutex sync.Mutex // This mutex protects the following node field
|
||||||
node *api.NodeDescription
|
node *api.NodeDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExecutor returns an executor from the docker client.
|
// 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{
|
return &executor{
|
||||||
backend: b,
|
backend: b,
|
||||||
pluginBackend: p,
|
pluginBackend: p,
|
||||||
imageBackend: i,
|
imageBackend: i,
|
||||||
|
volumeBackend: v,
|
||||||
dependencies: agent.NewDependencyManager(),
|
dependencies: agent.NewDependencyManager(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +213,7 @@ func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
|
||||||
e.mutex.Unlock()
|
e.mutex.Unlock()
|
||||||
|
|
||||||
if t.Spec.GetAttachment() != nil {
|
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
|
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)
|
return ctlr, fmt.Errorf("unsupported runtime type: %q", runtimeKind)
|
||||||
}
|
}
|
||||||
case *api.TaskSpec_Container:
|
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 {
|
if err != nil {
|
||||||
return ctlr, err
|
return ctlr, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ func TestHealthStates(t *testing.T) {
|
||||||
EventsService: e,
|
EventsService: e,
|
||||||
}
|
}
|
||||||
|
|
||||||
controller, err := newController(daemon, nil, task, nil, nil)
|
controller, err := newController(daemon, nil, nil, task, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("create controller fail %v", err)
|
t.Fatalf("create controller fail %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestControllerWithMount(m api.Mount) (*controller, error) {
|
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(),
|
ID: stringid.GenerateRandomID(),
|
||||||
ServiceID: stringid.GenerateRandomID(),
|
ServiceID: stringid.GenerateRandomID(),
|
||||||
Spec: api.TaskSpec{
|
Spec: api.TaskSpec{
|
||||||
|
|
|
@ -123,7 +123,9 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
|
||||||
Executor: container.NewExecutor(
|
Executor: container.NewExecutor(
|
||||||
n.cluster.config.Backend,
|
n.cluster.config.Backend,
|
||||||
n.cluster.config.PluginBackend,
|
n.cluster.config.PluginBackend,
|
||||||
n.cluster.config.ImageBackend),
|
n.cluster.config.ImageBackend,
|
||||||
|
n.cluster.config.VolumeBackend,
|
||||||
|
),
|
||||||
HeartbeatTick: n.cluster.config.RaftHeartbeatTick,
|
HeartbeatTick: n.cluster.config.RaftHeartbeatTick,
|
||||||
// Recommended value in etcd/raft is 10 x (HeartbeatTick).
|
// Recommended value in etcd/raft is 10 x (HeartbeatTick).
|
||||||
// Lower values were seen to have caused instability because of
|
// Lower values were seen to have caused instability because of
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
containertypes "github.com/docker/docker/api/types/container"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
|
@ -16,10 +14,10 @@ import (
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -255,24 +253,6 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig)
|
||||||
return nil, nil
|
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 {
|
func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error {
|
||||||
if img != nil && img.Config != nil {
|
if img != nil && img.Config != nil {
|
||||||
if err := merge(config, img.Config); err != nil {
|
if err := merge(config, img.Config); err != nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package daemon // import "github.com/docker/docker/daemon"
|
package daemon // import "github.com/docker/docker/daemon"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
mounttypes "github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/container"
|
"github.com/docker/docker/container"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
volumeopts "github.com/docker/docker/volume/service/opts"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
"github.com/sirupsen/logrus"
|
"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)
|
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 {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
container.AddMountPointWithVolume(destination, v, true)
|
container.AddMountPointWithVolume(destination, &volumeWrapper{v: v, s: daemon.volumes}, true)
|
||||||
}
|
}
|
||||||
return daemon.populateVolumes(container)
|
return daemon.populateVolumes(container)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package daemon // import "github.com/docker/docker/daemon"
|
package daemon // import "github.com/docker/docker/daemon"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ import (
|
||||||
"github.com/docker/docker/container"
|
"github.com/docker/docker/container"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
volumemounts "github.com/docker/docker/volume/mounts"
|
volumemounts "github.com/docker/docker/volume/mounts"
|
||||||
|
volumeopts "github.com/docker/docker/volume/service/opts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createContainerOSSpecificSettings performs host-OS specific container create functionality
|
// 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,
|
// Create the volume in the volume driver. If it doesn't exist,
|
||||||
// a new one will be created.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -85,7 +87,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Add it to container.MountPoints
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,7 @@ import (
|
||||||
refstore "github.com/docker/docker/reference"
|
refstore "github.com/docker/docker/reference"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
volumesservice "github.com/docker/docker/volume/service"
|
||||||
"github.com/docker/docker/volume/local"
|
|
||||||
"github.com/docker/docker/volume/store"
|
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
"github.com/docker/libnetwork/cluster"
|
"github.com/docker/libnetwork/cluster"
|
||||||
nwconfig "github.com/docker/libnetwork/config"
|
nwconfig "github.com/docker/libnetwork/config"
|
||||||
|
@ -83,7 +81,7 @@ type Daemon struct {
|
||||||
RegistryService registry.Service
|
RegistryService registry.Service
|
||||||
EventsService *events.Events
|
EventsService *events.Events
|
||||||
netController libnetwork.NetworkController
|
netController libnetwork.NetworkController
|
||||||
volumes *store.VolumeStore
|
volumes *volumesservice.VolumesService
|
||||||
discoveryWatcher discovery.Reloader
|
discoveryWatcher discovery.Reloader
|
||||||
root string
|
root string
|
||||||
seccompEnabled bool
|
seccompEnabled bool
|
||||||
|
@ -784,8 +782,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the volumes driver
|
d.volumes, err = volumesservice.NewVolumeService(config.Root, d.PluginStore, rootIDs, d)
|
||||||
volStore, err := d.configureVolumes(rootIDs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -855,7 +852,6 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
|
||||||
d.statsCollector = d.newStatsCollector(1 * time.Second)
|
d.statsCollector = d.newStatsCollector(1 * time.Second)
|
||||||
|
|
||||||
d.EventsService = events.New()
|
d.EventsService = events.New()
|
||||||
d.volumes = volStore
|
|
||||||
d.root = config.Root
|
d.root = config.Root
|
||||||
d.idMappings = idMappings
|
d.idMappings = idMappings
|
||||||
d.seccompEnabled = sysInfo.Seccomp
|
d.seccompEnabled = sysInfo.Seccomp
|
||||||
|
@ -1144,18 +1140,6 @@ func setDefaultMtu(conf *config.Config) {
|
||||||
conf.Mtu = config.DefaultNetworkMtu
|
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
|
// IsShuttingDown tells whether the daemon is shutting down or not
|
||||||
func (daemon *Daemon) IsShuttingDown() bool {
|
func (daemon *Daemon) IsShuttingDown() bool {
|
||||||
return daemon.shutdown
|
return daemon.shutdown
|
||||||
|
|
|
@ -13,9 +13,7 @@ import (
|
||||||
_ "github.com/docker/docker/pkg/discovery/memory"
|
_ "github.com/docker/docker/pkg/discovery/memory"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/truncindex"
|
"github.com/docker/docker/pkg/truncindex"
|
||||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
volumesservice "github.com/docker/docker/volume/service"
|
||||||
"github.com/docker/docker/volume/local"
|
|
||||||
"github.com/docker/docker/volume/store"
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
"github.com/gotestyourself/gotestyourself/assert"
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
|
@ -120,18 +118,10 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
|
||||||
repository: tmp,
|
repository: tmp,
|
||||||
root: tmp,
|
root: tmp,
|
||||||
}
|
}
|
||||||
drivers := volumedrivers.NewStore(nil)
|
daemon.volumes, err = volumesservice.NewVolumeService(tmp, nil, idtools.IDPair{UID: 0, GID: 0}, daemon)
|
||||||
daemon.volumes, err = store.New(tmp, drivers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return daemon, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,6 @@ import (
|
||||||
"github.com/docker/docker/container"
|
"github.com/docker/docker/container"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/volume"
|
|
||||||
volumestore "github.com/docker/docker/volume/store"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -152,35 +150,3 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
|
||||||
daemon.LogContainerEvent(container, "destroy")
|
daemon.LogContainerEvent(container, "destroy")
|
||||||
return nil
|
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"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"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
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
allLayersSize, err := daemon.imageService.LayerDiskUsage(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -75,7 +44,7 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
|
||||||
return &types.DiskUsage{
|
return &types.DiskUsage{
|
||||||
LayersSize: allLayersSize,
|
LayersSize: allLayersSize,
|
||||||
Containers: allContainers,
|
Containers: allContainers,
|
||||||
Volumes: allVolumes,
|
Volumes: localVolumes,
|
||||||
Images: allImages,
|
Images: allImages,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,6 @@ func containerNotFound(id string) error {
|
||||||
return objNotFoundError{"container", id}
|
return objNotFoundError{"container", id}
|
||||||
}
|
}
|
||||||
|
|
||||||
func volumeNotFound(id string) error {
|
|
||||||
return objNotFoundError{"volume", id}
|
|
||||||
}
|
|
||||||
|
|
||||||
type objNotFoundError struct {
|
type objNotFoundError struct {
|
||||||
object string
|
object string
|
||||||
id string
|
id string
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/layer"
|
"github.com/docker/docker/layer"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ var imagesAcceptedFilters = map[string]bool{
|
||||||
|
|
||||||
// errPruneRunning is returned when a prune request is received while
|
// errPruneRunning is returned when a prune request is received while
|
||||||
// one is in progress
|
// 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
|
// ImagesPrune removes unused images
|
||||||
func (i *ImageService) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
|
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/container"
|
||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
volumestore "github.com/docker/docker/volume/store"
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -236,22 +235,6 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, err
|
||||||
}, nil
|
}, 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 {
|
func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings {
|
||||||
result := &v1p20.NetworkSettings{
|
result := &v1p20.NetworkSettings{
|
||||||
NetworkSettingsBase: types.NetworkSettingsBase{
|
NetworkSettingsBase: types.NetworkSettingsBase{
|
||||||
|
|
|
@ -12,19 +12,11 @@ import (
|
||||||
"github.com/docker/docker/daemon/images"
|
"github.com/docker/docker/daemon/images"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/volume"
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var acceptedVolumeFilterTags = map[string]bool{
|
|
||||||
"dangling": true,
|
|
||||||
"name": true,
|
|
||||||
"driver": true,
|
|
||||||
"label": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var acceptedPsFilterTags = map[string]bool{
|
var acceptedPsFilterTags = map[string]bool{
|
||||||
"ancestor": true,
|
"ancestor": true,
|
||||||
"before": true,
|
"before": true,
|
||||||
|
@ -605,87 +597,6 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
|
||||||
return &c, nil
|
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) {
|
func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
|
||||||
if !ancestorMap[imageID] {
|
if !ancestorMap[imageID] {
|
||||||
for _, id := range getChildren(imageID) {
|
for _, id := range getChildren(imageID) {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package daemon // import "github.com/docker/docker/daemon"
|
package daemon // import "github.com/docker/docker/daemon"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
mounttypes "github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/container"
|
"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 {
|
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 {
|
func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool) error {
|
||||||
var rmErrors []string
|
var rmErrors []string
|
||||||
|
ctx := context.TODO()
|
||||||
for _, m := range container.MountPoints {
|
for _, m := range container.MountPoints {
|
||||||
if m.Type != mounttypes.TypeVolume || m.Volume == nil {
|
if m.Type != mounttypes.TypeVolume || m.Volume == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
daemon.volumes.Dereference(m.Volume, container.ID)
|
daemon.volumes.Release(ctx, m.Volume.Name(), container.ID)
|
||||||
if !rm {
|
if !rm {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -35,13 +37,13 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := daemon.volumes.Remove(m.Volume)
|
err := daemon.volumes.Remove(ctx, m.Volume.Name())
|
||||||
// Ignore volume in use errors because having this
|
// Ignore volume in use errors because having this
|
||||||
// volume being referenced by other container is
|
// volume being referenced by other container is
|
||||||
// not an error, but an implementation detail.
|
// not an error, but an implementation detail.
|
||||||
// This prevents docker from logging "ERROR: Volume in use"
|
// This prevents docker from logging "ERROR: Volume in use"
|
||||||
// where there is another container using the volume.
|
// 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())
|
rmErrors = append(rmErrors, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,27 +10,23 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
timetypes "github.com/docker/docker/api/types/time"
|
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/runconfig"
|
||||||
"github.com/docker/docker/volume"
|
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// errPruneRunning is returned when a prune request is received while
|
// errPruneRunning is returned when a prune request is received while
|
||||||
// one is in progress
|
// 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{
|
containersAcceptedFilters = map[string]bool{
|
||||||
"label": true,
|
"label": true,
|
||||||
"label!": true,
|
"label!": true,
|
||||||
"until": true,
|
"until": true,
|
||||||
}
|
}
|
||||||
volumesAcceptedFilters = map[string]bool{
|
|
||||||
"label": true,
|
|
||||||
"label!": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
networksAcceptedFilters = map[string]bool{
|
networksAcceptedFilters = map[string]bool{
|
||||||
"label": true,
|
"label": true,
|
||||||
|
@ -92,67 +88,6 @@ func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.
|
||||||
return rep, nil
|
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
|
// localNetworksPrune removes unused local networks
|
||||||
func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
|
func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
|
||||||
rep := &types.NetworksPruneReport{}
|
rep := &types.NetworksPruneReport{}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package daemon // import "github.com/docker/docker/daemon"
|
package daemon // import "github.com/docker/docker/daemon"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -15,6 +16,8 @@ import (
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
volumemounts "github.com/docker/docker/volume/mounts"
|
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/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -27,23 +30,6 @@ var (
|
||||||
|
|
||||||
type mounts []container.Mount
|
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.
|
// Len returns the number of mounts. Used in sorting.
|
||||||
func (m mounts) Len() int {
|
func (m mounts) Len() int {
|
||||||
return len(m)
|
return len(m)
|
||||||
|
@ -78,6 +64,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||||
mountPoints := map[string]*volumemounts.MountPoint{}
|
mountPoints := map[string]*volumemounts.MountPoint{}
|
||||||
parser := volumemounts.NewParser(container.OS)
|
parser := volumemounts.NewParser(container.OS)
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
defer func() {
|
defer func() {
|
||||||
// clean up the container mountpoints once return with error
|
// clean up the container mountpoints once return with error
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
|
@ -85,7 +72,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||||
if m.Volume == nil {
|
if m.Volume == nil {
|
||||||
continue
|
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 {
|
if v, ok := mountPoints[destination]; ok {
|
||||||
logrus.Debugf("Duplicate mount point '%s'", destination)
|
logrus.Debugf("Duplicate mount point '%s'", destination)
|
||||||
if v.Volume != nil {
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cp.Volume = v
|
cp.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
||||||
}
|
}
|
||||||
dereferenceIfExists(cp.Destination)
|
dereferenceIfExists(cp.Destination)
|
||||||
mountPoints[cp.Destination] = cp
|
mountPoints[cp.Destination] = cp
|
||||||
|
@ -163,14 +150,14 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||||
|
|
||||||
if bind.Type == mounttypes.TypeVolume {
|
if bind.Type == mounttypes.TypeVolume {
|
||||||
// create the volume
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bind.Volume = v
|
bind.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
||||||
bind.Source = v.Path()
|
bind.Source = v.Mountpoint
|
||||||
// bind.Name is an already existing volume, we need to use that here
|
// 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 {
|
if bind.Driver == volume.DefaultDriverName {
|
||||||
setBindModeIfNull(bind)
|
setBindModeIfNull(bind)
|
||||||
}
|
}
|
||||||
|
@ -199,30 +186,30 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||||
}
|
}
|
||||||
|
|
||||||
if mp.Type == mounttypes.TypeVolume {
|
if mp.Type == mounttypes.TypeVolume {
|
||||||
var v volume.Volume
|
var v *types.Volume
|
||||||
if cfg.VolumeOptions != nil {
|
if cfg.VolumeOptions != nil {
|
||||||
var driverOpts map[string]string
|
var driverOpts map[string]string
|
||||||
if cfg.VolumeOptions.DriverConfig != nil {
|
if cfg.VolumeOptions.DriverConfig != nil {
|
||||||
driverOpts = cfg.VolumeOptions.DriverConfig.Options
|
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 {
|
} 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mp.Volume = v
|
mp.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
||||||
mp.Name = v.Name()
|
mp.Name = v.Name
|
||||||
mp.Driver = v.DriverName()
|
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 {
|
if mp.Driver == volume.DefaultDriverName {
|
||||||
setBindModeIfNull(mp)
|
setBindModeIfNull(mp)
|
||||||
}
|
}
|
||||||
|
@ -239,7 +226,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
||||||
for _, m := range mountPoints {
|
for _, m := range mountPoints {
|
||||||
if parser.IsBackwardCompatible(m) {
|
if parser.IsBackwardCompatible(m) {
|
||||||
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
|
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.
|
// This happens after a daemon restart.
|
||||||
func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
|
func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
|
||||||
if len(m.Driver) > 0 && m.Volume == nil {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.Volume = v
|
m.Volume = &volumeWrapper{v: v, s: daemon.volumes}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -385,3 +372,46 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
|
||||||
cm.Spec.ReadOnly = !cm.RW
|
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)
|
c.Assert(len(events), checker.GreaterThan, 4)
|
||||||
|
|
||||||
volumeEvents := eventActionsByIDAndType(c, events, "test-event-volume-local", "volume")
|
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[0], checker.Equals, "create")
|
||||||
c.Assert(volumeEvents[1], checker.Equals, "mount")
|
c.Assert(volumeEvents[1], checker.Equals, "create")
|
||||||
c.Assert(volumeEvents[2], checker.Equals, "unmount")
|
c.Assert(volumeEvents[2], checker.Equals, "mount")
|
||||||
c.Assert(volumeEvents[3], checker.Equals, "destroy")
|
c.Assert(volumeEvents[3], checker.Equals, "unmount")
|
||||||
|
c.Assert(volumeEvents[4], checker.Equals, "destroy")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestNetworkEvents(c *check.C) {
|
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`
|
// 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) {
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
|
||||||
s.d.Start(c)
|
s.d.Start(c)
|
||||||
c.Assert(s.ec.paths, checker.Equals, 0)
|
c.Assert(s.ec.paths, checker.Equals, 0)
|
||||||
|
|
||||||
out, err := s.d.Cmd("volume", "create", "test", "--driver=test-external-volume-driver")
|
out, err := s.d.Cmd("volume", "create", "test", "--driver=test-external-volume-driver")
|
||||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
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")
|
out, err = s.d.Cmd("volume", "ls")
|
||||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
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", "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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) {
|
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"
|
package directory // import "github.com/docker/docker/pkg/directory"
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// `GetCapabilities` is a not a required endpoint.
|
// `GetCapabilities` is a not a required endpoint.
|
||||||
// On error assume it's a local-only driver
|
// 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}
|
return volume.Capability{Scope: volume.LocalScope}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
|
||||||
|
|
||||||
cap.Scope = strings.ToLower(cap.Scope)
|
cap.Scope = strings.ToLower(cap.Scope)
|
||||||
if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope {
|
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
|
cap.Scope = volume.LocalScope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -167,10 +167,10 @@ func (s *Store) ReleaseDriver(name string) (volume.Driver, error) {
|
||||||
func (s *Store) GetDriverList() []string {
|
func (s *Store) GetDriverList() []string {
|
||||||
var driverList []string
|
var driverList []string
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
for driverName := range s.extensions {
|
for driverName := range s.extensions {
|
||||||
driverList = append(driverList, driverName)
|
driverList = append(driverList, driverName)
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
|
||||||
sort.Strings(driverList)
|
sort.Strings(driverList)
|
||||||
return 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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
||||||
package store
|
package service // import "github.com/docker/docker/volume/service"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"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 (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,3 +94,18 @@ func isErr(err error, expected error) bool {
|
||||||
}
|
}
|
||||||
return err == expected
|
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 (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
@ -20,6 +21,7 @@ func (s *VolumeStore) restore() {
|
||||||
ls = listMeta(tx)
|
ls = listMeta(tx)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
chRemove := make(chan *volumeMetadata, len(ls))
|
chRemove := make(chan *volumeMetadata, len(ls))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -32,7 +34,7 @@ func (s *VolumeStore) restore() {
|
||||||
var v volume.Volume
|
var v volume.Volume
|
||||||
var err error
|
var err error
|
||||||
if meta.Driver != "" {
|
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 {
|
if err != nil && err != errNoSuchVolume {
|
||||||
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
|
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
|
||||||
return
|
return
|
||||||
|
@ -43,7 +45,7 @@ func (s *VolumeStore) restore() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v, err = s.getVolume(meta.Name)
|
v, err = s.getVolume(ctx, meta.Name, meta.Driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errNoSuchVolume {
|
if err == errNoSuchVolume {
|
||||||
chRemove <- &meta
|
chRemove <- &meta
|
||||||
|
@ -65,6 +67,7 @@ func (s *VolumeStore) restore() {
|
||||||
s.options[v.Name()] = meta.Options
|
s.options[v.Name()] = meta.Options
|
||||||
s.labels[v.Name()] = meta.Labels
|
s.labels[v.Name()] = meta.Labels
|
||||||
s.names[v.Name()] = v
|
s.names[v.Name()] = v
|
||||||
|
s.refs[v.Name()] = make(map[string]struct{})
|
||||||
s.globalLock.Unlock()
|
s.globalLock.Unlock()
|
||||||
}(meta)
|
}(meta)
|
||||||
}
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
package store
|
package service // import "github.com/docker/docker/volume/service"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||||
|
"github.com/docker/docker/volume/service/opts"
|
||||||
volumetestutils "github.com/docker/docker/volume/testutils"
|
volumetestutils "github.com/docker/docker/volume/testutils"
|
||||||
"github.com/gotestyourself/gotestyourself/assert"
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
)
|
)
|
||||||
|
@ -22,24 +24,25 @@ func TestRestore(t *testing.T) {
|
||||||
driverName := "test-restore"
|
driverName := "test-restore"
|
||||||
drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
||||||
|
|
||||||
s, err := New(dir, drivers)
|
s, err := NewStore(dir, drivers)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
defer s.Shutdown()
|
defer s.Shutdown()
|
||||||
|
|
||||||
_, err = s.Create("test1", driverName, nil, nil)
|
ctx := context.Background()
|
||||||
|
_, err = s.Create(ctx, "test1", driverName)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
testLabels := map[string]string{"a": "1"}
|
testLabels := map[string]string{"a": "1"}
|
||||||
testOpts := map[string]string{"foo": "bar"}
|
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)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
|
|
||||||
s, err = New(dir, drivers)
|
s, err = NewStore(dir, drivers)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
v, err := s.Get("test1")
|
v, err := s.Get(ctx, "test1")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
dv := v.(volume.DetailedVolume)
|
dv := v.(volume.DetailedVolume)
|
||||||
|
@ -47,7 +50,7 @@ func TestRestore(t *testing.T) {
|
||||||
assert.DeepEqual(t, nilMap, dv.Options())
|
assert.DeepEqual(t, nilMap, dv.Options())
|
||||||
assert.DeepEqual(t, nilMap, dv.Labels())
|
assert.DeepEqual(t, nilMap, dv.Labels())
|
||||||
|
|
||||||
v, err = s.Get("test2")
|
v, err = s.Get(ctx, "test2")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
dv = v.(volume.DetailedVolume)
|
dv = v.(volume.DetailedVolume)
|
||||||
assert.DeepEqual(t, testOpts, dv.Options())
|
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 (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -11,10 +13,12 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/locker"
|
"github.com/docker/docker/pkg/locker"
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
"github.com/docker/docker/volume/drivers"
|
"github.com/docker/docker/volume/drivers"
|
||||||
volumemounts "github.com/docker/docker/volume/mounts"
|
volumemounts "github.com/docker/docker/volume/mounts"
|
||||||
|
"github.com/docker/docker/volume/service/opts"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,9 +69,8 @@ func (v volumeWrapper) CachedPath() string {
|
||||||
return v.Volume.Path()
|
return v.Volume.Path()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes a VolumeStore to keep
|
// NewStore creates a new volume store at the given path
|
||||||
// reference counting of volumes in the system.
|
func NewStore(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
|
||||||
func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
|
|
||||||
vs := &VolumeStore{
|
vs := &VolumeStore{
|
||||||
locks: &locker.Locker{},
|
locks: &locker.Locker{},
|
||||||
names: make(map[string]volume.Volume),
|
names: make(map[string]volume.Volume),
|
||||||
|
@ -84,10 +87,8 @@ func New(rootPath string, drivers *drivers.Store) (*VolumeStore, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dbPath := filepath.Join(volPath, "metadata.db")
|
|
||||||
|
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error while opening volume store metadata database")
|
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
|
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.
|
// 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()
|
s.globalLock.Lock()
|
||||||
|
defer s.globalLock.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
v, exists := s.names[name]
|
v, exists := s.names[name]
|
||||||
if exists {
|
if exists {
|
||||||
driverName := v.DriverName()
|
driverName := v.DriverName()
|
||||||
|
@ -170,7 +179,7 @@ func (s *VolumeStore) Purge(name string) {
|
||||||
delete(s.refs, name)
|
delete(s.refs, name)
|
||||||
delete(s.labels, name)
|
delete(s.labels, name)
|
||||||
delete(s.options, 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
|
// 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
|
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,
|
// 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.
|
// the first volume is chosen and the conflicting volume is dropped.
|
||||||
func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
|
func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) {
|
||||||
vols, warnings, err := s.list()
|
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 {
|
if err != nil {
|
||||||
return nil, nil, &OpErr{Err: err, Op: "list"}
|
return nil, nil, &OpErr{Err: err, Op: "list"}
|
||||||
}
|
}
|
||||||
|
|
||||||
var out []volume.Volume
|
var out []volume.Volume
|
||||||
|
|
||||||
for _, v := range vols {
|
for _, v := range vols {
|
||||||
|
@ -222,26 +354,59 @@ func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
|
||||||
return out, warnings, nil
|
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.
|
// 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 (
|
var (
|
||||||
ls []volume.Volume
|
ls = []volume.Volume{} // do not return a nil value as this affects filtering
|
||||||
warnings []string
|
warnings []string
|
||||||
)
|
)
|
||||||
|
|
||||||
drivers, err := s.drivers.GetAllDrivers()
|
var dls []volume.Driver
|
||||||
|
|
||||||
|
all, err := s.drivers.GetAllDrivers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
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 {
|
type vols struct {
|
||||||
vols []volume.Volume
|
vols []volume.Volume
|
||||||
err error
|
err error
|
||||||
driverName string
|
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) {
|
go func(d volume.Driver) {
|
||||||
vs, err := d.List()
|
vs, err := d.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -259,13 +424,12 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
badDrivers := make(map[string]struct{})
|
badDrivers := make(map[string]struct{})
|
||||||
for i := 0; i < len(drivers); i++ {
|
for i := 0; i < len(dls); i++ {
|
||||||
vs := <-chVols
|
vs := <-chVols
|
||||||
|
|
||||||
if vs.err != nil {
|
if vs.err != nil {
|
||||||
warnings = append(warnings, vs.err.Error())
|
warnings = append(warnings, vs.err.Error())
|
||||||
badDrivers[vs.driverName] = struct{}{}
|
badDrivers[vs.driverName] = struct{}{}
|
||||||
logrus.Warn(vs.err)
|
|
||||||
}
|
}
|
||||||
ls = append(ls, vs.vols...)
|
ls = append(ls, vs.vols...)
|
||||||
}
|
}
|
||||||
|
@ -282,14 +446,26 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
|
||||||
return ls, warnings, nil
|
return ls, warnings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateWithRef creates a volume with the given name and driver and stores the ref
|
// Create creates a volume with the given name and driver
|
||||||
// This ensures there's no race between creating a volume and then storing a reference.
|
// If the volume needs to be created with a reference to prevent race conditions
|
||||||
func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels map[string]string) (volume.Volume, error) {
|
// 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)
|
name = normalizeVolumeName(name)
|
||||||
s.locks.Lock(name)
|
s.locks.Lock(name)
|
||||||
defer s.locks.Unlock(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 err != nil {
|
||||||
if _, ok := err.(*OpErr); ok {
|
if _, ok := err.(*OpErr); ok {
|
||||||
return nil, err
|
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"}
|
return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setNamed(v, ref)
|
s.setNamed(v, cfg.Reference)
|
||||||
return v, nil
|
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,
|
// 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.
|
// 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.
|
// 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
|
// 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
|
// use a connect timeout for this kind of check to ensure we aren't blocking for a
|
||||||
// long time.
|
// 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
|
// check the local cache
|
||||||
v, _ := s.getNamed(name)
|
v, _ := s.getNamed(name)
|
||||||
if v == nil {
|
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
|
// let's check if the found volume ref
|
||||||
// is stale by checking with the driver if it still exists
|
// 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 {
|
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)
|
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
|
// doesn't exist, so purge it from the cache
|
||||||
s.Purge(name)
|
s.purge(ctx, name)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// volumeExists returns if the volume is still present in the driver.
|
// volumeExists returns if the volume is still present in the driver.
|
||||||
// An error is returned if there was an issue communicating with 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) {
|
func volumeExists(ctx context.Context, store *drivers.Store, v volume.Volume) (bool, error) {
|
||||||
exists, err := lookupVolume(store, v.DriverName(), v.Name())
|
exists, err := lookupVolume(ctx, store, v.DriverName(), v.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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.
|
// 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.
|
// 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.
|
// 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
|
// Validate the name in a platform-specific manner
|
||||||
|
|
||||||
// volume name validation is specific to the host os and not on container image
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := s.checkConflict(name, driverName)
|
v, err := s.checkConflict(ctx, name, driverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name
|
||||||
if driverName == "" {
|
if driverName == "" {
|
||||||
v, _ = s.getVolume(name)
|
v, _ = s.getVolume(ctx, name, "")
|
||||||
if v != nil {
|
if v != nil {
|
||||||
return 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
|
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
|
// 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)
|
name = normalizeVolumeName(name)
|
||||||
s.locks.Lock(name)
|
s.locks.Lock(name)
|
||||||
defer s.locks.Unlock(name)
|
defer s.locks.Unlock(name)
|
||||||
|
|
||||||
v, err := s.getVolume(name)
|
v, err := s.getVolume(ctx, name, cfg.Driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
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
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVolume requests the volume, if the driver info is stored it just accesses that driver,
|
// 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.
|
// 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
|
// 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
|
var meta volumeMetadata
|
||||||
meta, err := s.getMeta(name)
|
meta, err := s.getMeta(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 == "" {
|
if driverName == "" {
|
||||||
s.globalLock.RLock()
|
s.globalLock.RLock()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.globalLock.RUnlock()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
v, exists := s.names[name]
|
v, exists := s.names[name]
|
||||||
s.globalLock.RUnlock()
|
s.globalLock.RUnlock()
|
||||||
if exists {
|
if exists {
|
||||||
|
@ -519,12 +685,12 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if meta.Driver != "" {
|
if meta.Driver != "" {
|
||||||
vol, err := lookupVolume(s.drivers, meta.Driver, name)
|
vol, err := lookupVolume(ctx, s.drivers, meta.Driver, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if vol == nil {
|
if vol == nil {
|
||||||
s.Purge(name)
|
s.purge(ctx, name)
|
||||||
return nil, errNoSuchVolume
|
return nil, errNoSuchVolume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,6 +709,11 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range drivers {
|
for _, d := range drivers {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
v, err := d.Get(name)
|
v, err := d.Get(name)
|
||||||
if err != nil || v == nil {
|
if err != nil || v == nil {
|
||||||
continue
|
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
|
// If the driver returns an error that is not communication related the
|
||||||
// error is logged but not returned.
|
// error is logged but not returned.
|
||||||
// If the volume is not found it will return `nil, nil``
|
// 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 == "" {
|
if driverName == "" {
|
||||||
driverName = volume.DefaultDriverName
|
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"
|
// 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
|
// 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
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the requested volume. A volume is not removed if it has any refs
|
// Remove removes the requested volume. A volume is not removed if it has any refs
|
||||||
func (s *VolumeStore) Remove(v volume.Volume) error {
|
func (s *VolumeStore) Remove(ctx context.Context, v volume.Volume, rmOpts ...opts.RemoveOption) error {
|
||||||
name := normalizeVolumeName(v.Name())
|
var cfg opts.RemoveConfig
|
||||||
|
for _, o := range rmOpts {
|
||||||
|
o(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := v.Name()
|
||||||
s.locks.Lock(name)
|
s.locks.Lock(name)
|
||||||
defer s.locks.Unlock(name)
|
defer s.locks.Unlock(name)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
if s.hasRef(name) {
|
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())
|
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)
|
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
||||||
vol := unwrapVolume(v)
|
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)
|
if err == nil || cfg.PurgeOnError {
|
||||||
return nil
|
if e := s.purge(ctx, name); e != nil && err == nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dereference removes the specified reference to the volume
|
// Release releases the specified reference to the volume
|
||||||
func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
|
func (s *VolumeStore) Release(ctx context.Context, name string, ref string) error {
|
||||||
name := v.Name()
|
|
||||||
|
|
||||||
s.locks.Lock(name)
|
s.locks.Lock(name)
|
||||||
defer s.locks.Unlock(name)
|
defer s.locks.Unlock(name)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
s.globalLock.Lock()
|
s.globalLock.Lock()
|
||||||
defer s.globalLock.Unlock()
|
defer s.globalLock.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
if s.refs[name] != nil {
|
if s.refs[name] != nil {
|
||||||
delete(s.refs[name], ref)
|
delete(s.refs[name], ref)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refs gets the current list of refs for the given volume
|
// CountReferences gives a count of all references for a given volume.
|
||||||
func (s *VolumeStore) Refs(v volume.Volume) []string {
|
func (s *VolumeStore) CountReferences(v volume.Volume) int {
|
||||||
name := v.Name()
|
name := normalizeVolumeName(v.Name())
|
||||||
|
|
||||||
s.locks.Lock(name)
|
s.locks.Lock(name)
|
||||||
defer s.locks.Unlock(name)
|
defer s.locks.Unlock(name)
|
||||||
|
s.globalLock.Lock()
|
||||||
|
defer s.globalLock.Unlock()
|
||||||
|
|
||||||
return s.getRefs(name)
|
return len(s.refs[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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unwrapVolume(v volume.Volume) volume.Volume {
|
func unwrapVolume(v volume.Volume) volume.Volume {
|
||||||
|
@ -698,10 +856,3 @@ func unwrapVolume(v volume.Volume) volume.Volume {
|
||||||
func (s *VolumeStore) Shutdown() error {
|
func (s *VolumeStore) Shutdown() error {
|
||||||
return s.db.Close()
|
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 (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||||
|
"github.com/docker/docker/volume/service/opts"
|
||||||
volumetestutils "github.com/docker/docker/volume/testutils"
|
volumetestutils "github.com/docker/docker/volume/testutils"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/gotestyourself/gotestyourself/assert"
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
|
@ -24,22 +26,23 @@ func TestCreate(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if v.Name() != "fake1" {
|
if v.Name() != "fake1" {
|
||||||
t.Fatalf("Expected fake1 volume, got %v", v)
|
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)
|
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")
|
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")}
|
expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
|
||||||
if err != nil && err.Error() != expected.Error() {
|
if err != nil && err.Error() != expected.Error() {
|
||||||
t.Fatalf("Expected create fakeError: create error, got %v", err)
|
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("fake"), "fake")
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// doing string compare here since this error comes directly from the driver
|
// doing string compare here since this error comes directly from the driver
|
||||||
expected := "no such volume"
|
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)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
||||||
}
|
}
|
||||||
s.Dereference(v, "fake")
|
s.Release(ctx, v.Name(), "fake")
|
||||||
if err := s.Remove(v); err != nil {
|
if err := s.Remove(ctx, v); err != nil {
|
||||||
t.Fatal(err)
|
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)
|
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("fake"), "fake")
|
||||||
drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
|
drivers.Register(volumetestutils.NewFakeDriver("fake2"), "fake2")
|
||||||
|
|
||||||
s, err := New(dir, drivers)
|
s, err := NewStore(dir, drivers)
|
||||||
assert.NilError(t, err)
|
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)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ls, _, err := s.List()
|
ls, _, err := s.Find(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -111,11 +118,11 @@ func TestList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// and again with a new store
|
// and again with a new store
|
||||||
s, err = New(dir, drivers)
|
s, err = NewStore(dir, drivers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
ls, _, err = s.List()
|
ls, _, err = s.Find(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -124,34 +131,38 @@ func TestList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterByDriver(t *testing.T) {
|
func TestFindByDriver(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s, cleanup := setupTest(t)
|
s, cleanup := setupTest(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake"))
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
assert.Assert(t, s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop"))
|
||||||
|
|
||||||
if _, err := s.Create("fake1", "fake", nil, nil); err != nil {
|
ctx := context.Background()
|
||||||
t.Fatal(err)
|
_, err := s.Create(ctx, "fake1", "fake")
|
||||||
}
|
assert.NilError(t, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
|
_, err = s.Create(ctx, "fake2", "fake")
|
||||||
t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
|
assert.NilError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
|
_, err = s.Create(ctx, "fake3", "noop")
|
||||||
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
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()
|
t.Parallel()
|
||||||
s, cleanup := setupTest(t)
|
s, cleanup := setupTest(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
@ -159,33 +170,23 @@ func TestFilterByUsed(t *testing.T) {
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("noop"), "noop")
|
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)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vols, _, err := s.List()
|
dangling, _, err := s.Find(ctx, ByReferenced(false))
|
||||||
if err != nil {
|
assert.Assert(t, err)
|
||||||
t.Fatal(err)
|
assert.Assert(t, len(dangling) == 1)
|
||||||
}
|
assert.Check(t, dangling[0].Name() == "fake2")
|
||||||
|
|
||||||
dangling := s.FilterByUsed(vols, false)
|
used, _, err := s.Find(ctx, ByReferenced(true))
|
||||||
if len(dangling) != 1 {
|
assert.Assert(t, err)
|
||||||
t.Fatalf("expected 1 dangling volume, got %v", len(dangling))
|
assert.Assert(t, len(used) == 1)
|
||||||
}
|
assert.Check(t, used[0].Name() == "fake1")
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDerefMultipleOfSameRef(t *testing.T) {
|
func TestDerefMultipleOfSameRef(t *testing.T) {
|
||||||
|
@ -194,17 +195,18 @@ func TestDerefMultipleOfSameRef(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Dereference(v, "volReference")
|
s.Release(ctx, v.Name(), "volReference")
|
||||||
if err := s.Remove(v); err != nil {
|
if err := s.Remove(ctx, v); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,7 +224,8 @@ func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
|
||||||
t.Fatal(err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -265,14 +268,15 @@ func TestDefererencePluginOnCreateError(t *testing.T) {
|
||||||
pg := volumetestutils.NewFakePluginGetter(p)
|
pg := volumetestutils.NewFakePluginGetter(p)
|
||||||
s.drivers = volumedrivers.NewStore(pg)
|
s.drivers = volumedrivers.NewStore(pg)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
// create a good volume so we have a plugin reference
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now create another one expecting an error
|
// 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") {
|
if err == nil || !strings.Contains(err.Error(), "some error") {
|
||||||
t.Fatalf("expected an error on create: %v", err)
|
t.Fatalf("expected an error on create: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -291,15 +295,16 @@ func TestRefDerefRemove(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
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)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
err = s.Remove(v)
|
err = s.Remove(ctx, v)
|
||||||
assert.Assert(t, is.ErrorContains(err, ""))
|
assert.Assert(t, is.ErrorContains(err, ""))
|
||||||
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
|
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
|
||||||
|
|
||||||
s.Dereference(v, "test-ref")
|
s.Release(ctx, v.Name(), "test-ref")
|
||||||
err = s.Remove(v)
|
err = s.Remove(ctx, v)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,25 +316,26 @@ func TestGet(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
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.Assert(t, is.ErrorContains(err, ""))
|
||||||
assert.Equal(t, errNoSuchVolume, err.(*OpErr).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)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
v2, err := s.Get("test")
|
v2, err := s.Get(ctx, "test")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, v1, v2, cmpVolume)
|
assert.DeepEqual(t, v1, v2, cmpVolume)
|
||||||
|
|
||||||
dv := v2.(volume.DetailedVolume)
|
dv := v2.(volume.DetailedVolume)
|
||||||
assert.Equal(t, "1", dv.Labels()["a"])
|
assert.Equal(t, "1", dv.Labels()["a"])
|
||||||
|
|
||||||
err = s.Remove(v1)
|
err = s.Remove(ctx, v1)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetWithRef(t *testing.T) {
|
func TestGetWithReference(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
driverName := "test-get-with-ref"
|
driverName := "test-get-with-ref"
|
||||||
|
@ -337,22 +343,23 @@ func TestGetWithRef(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
s.drivers.Register(volumetestutils.NewFakeDriver(driverName), driverName)
|
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, ""))
|
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)
|
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.NilError(t, err)
|
||||||
assert.DeepEqual(t, v1, v2, cmpVolume)
|
assert.DeepEqual(t, v1, v2, cmpVolume)
|
||||||
|
|
||||||
err = s.Remove(v2)
|
err = s.Remove(ctx, v2)
|
||||||
assert.Assert(t, is.ErrorContains(err, ""))
|
assert.Assert(t, is.ErrorContains(err, ""))
|
||||||
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
|
assert.Equal(t, errVolumeInUse, err.(*OpErr).Err)
|
||||||
|
|
||||||
s.Dereference(v2, "test-ref")
|
s.Release(ctx, v2.Name(), "test-ref")
|
||||||
err = s.Remove(v2)
|
err = s.Remove(ctx, v2)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,14 +373,49 @@ func setupTest(t *testing.T) (*VolumeStore, func()) {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
|
t.Helper()
|
||||||
err := os.RemoveAll(dir)
|
err := os.RemoveAll(dir)
|
||||||
assert.Check(t, err)
|
assert.Check(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := New(dir, volumedrivers.NewStore(nil))
|
s, err := NewStore(dir, volumedrivers.NewStore(nil))
|
||||||
assert.Check(t, err)
|
assert.Check(t, err)
|
||||||
return s, func() {
|
return s, func() {
|
||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
cleanup()
|
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
|
// normalizeVolumeName is a platform specific function to normalize the name
|
||||||
// of a volume. This is a no-op on Unix-like platforms
|
// 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"
|
import "strings"
|
||||||
|
|
|
@ -64,7 +64,9 @@ func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
|
||||||
func (FakeVolume) Unmount(_ string) error { return nil }
|
func (FakeVolume) Unmount(_ string) error { return nil }
|
||||||
|
|
||||||
// Status provides low-level details about the volume
|
// 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
|
// CreatedAt provides the time the volume (directory) was created at
|
||||||
func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
|
func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
|
||||||
|
|
Loading…
Add table
Reference in a new issue