Move responsibility of ls/inspect to volume driver
| ... | ... |
@@ -65,6 +65,9 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
| 65 | 65 |
|
| 66 | 66 |
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
| 67 | 67 |
if !*quiet {
|
| 68 |
+ for _, warn := range volumes.Warnings {
|
|
| 69 |
+ fmt.Fprintln(cli.err, warn) |
|
| 70 |
+ } |
|
| 68 | 71 |
fmt.Fprintf(w, "DRIVER \tVOLUME NAME") |
| 69 | 72 |
fmt.Fprintf(w, "\n") |
| 70 | 73 |
} |
| ... | ... |
@@ -102,7 +105,7 @@ func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
|
| 102 | 102 |
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher) |
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 |
-// CmdVolumeCreate creates a new container from a given image. |
|
| 105 |
+// CmdVolumeCreate creates a new volume. |
|
| 106 | 106 |
// |
| 107 | 107 |
// Usage: docker volume create [OPTIONS] |
| 108 | 108 |
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
| ... | ... |
@@ -131,7 +134,7 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
| 131 | 131 |
return nil |
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 |
-// CmdVolumeRm removes one or more containers. |
|
| 134 |
+// CmdVolumeRm removes one or more volumes. |
|
| 135 | 135 |
// |
| 136 | 136 |
// Usage: docker volume rm VOLUME [VOLUME...] |
| 137 | 137 |
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
| ... | ... |
@@ -140,6 +143,7 @@ func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
| 140 | 140 |
cmd.ParseFlags(args, true) |
| 141 | 141 |
|
| 142 | 142 |
var status = 0 |
| 143 |
+ |
|
| 143 | 144 |
for _, name := range cmd.Args() {
|
| 144 | 145 |
if err := cli.client.VolumeRemove(name); err != nil {
|
| 145 | 146 |
fmt.Fprintf(cli.err, "%s\n", err) |
| ... | ... |
@@ -8,7 +8,7 @@ import ( |
| 8 | 8 |
// Backend is the methods that need to be implemented to provide |
| 9 | 9 |
// volume specific functionality |
| 10 | 10 |
type Backend interface {
|
| 11 |
- Volumes(filter string) ([]*types.Volume, error) |
|
| 11 |
+ Volumes(filter string) ([]*types.Volume, []string, error) |
|
| 12 | 12 |
VolumeInspect(name string) (*types.Volume, error) |
| 13 | 13 |
VolumeCreate(name, driverName string, |
| 14 | 14 |
opts map[string]string) (*types.Volume, error) |
| ... | ... |
@@ -14,11 +14,11 @@ func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter |
| 14 | 14 |
return err |
| 15 | 15 |
} |
| 16 | 16 |
|
| 17 |
- volumes, err := v.backend.Volumes(r.Form.Get("filters"))
|
|
| 17 |
+ volumes, warnings, err := v.backend.Volumes(r.Form.Get("filters"))
|
|
| 18 | 18 |
if err != nil {
|
| 19 | 19 |
return err |
| 20 | 20 |
} |
| 21 |
- return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
|
|
| 21 |
+ return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes, Warnings: warnings})
|
|
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 | 24 |
func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| ... | ... |
@@ -366,7 +366,8 @@ type Volume struct {
|
| 366 | 366 |
// VolumesListResponse contains the response for the remote API: |
| 367 | 367 |
// GET "/volumes" |
| 368 | 368 |
type VolumesListResponse struct {
|
| 369 |
- Volumes []*Volume // Volumes is the list of volumes being returned |
|
| 369 |
+ Volumes []*Volume // Volumes is the list of volumes being returned |
|
| 370 |
+ Warnings []string // Warnings is a list of warnings that occurred when getting the list from the volume drivers |
|
| 370 | 371 |
} |
| 371 | 372 |
|
| 372 | 373 |
// VolumeCreateRequest contains the response for the remote API: |
| ... | ... |
@@ -10,7 +10,7 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/layer" |
| 11 | 11 |
"github.com/docker/docker/pkg/idtools" |
| 12 | 12 |
"github.com/docker/docker/pkg/stringid" |
| 13 |
- "github.com/docker/docker/volume" |
|
| 13 |
+ volumestore "github.com/docker/docker/volume/store" |
|
| 14 | 14 |
"github.com/opencontainers/runc/libcontainer/label" |
| 15 | 15 |
) |
| 16 | 16 |
|
| ... | ... |
@@ -162,17 +162,12 @@ func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]stri |
| 162 | 162 |
|
| 163 | 163 |
v, err := daemon.volumes.Create(name, driverName, opts) |
| 164 | 164 |
if err != nil {
|
| 165 |
+ if volumestore.IsNameConflict(err) {
|
|
| 166 |
+ return nil, derr.ErrorVolumeNameTaken.WithArgs(name) |
|
| 167 |
+ } |
|
| 165 | 168 |
return nil, err |
| 166 | 169 |
} |
| 167 | 170 |
|
| 168 |
- // keep "docker run -v existing_volume:/foo --volume-driver other_driver" work |
|
| 169 |
- if (driverName != "" && v.DriverName() != driverName) || (driverName == "" && v.DriverName() != volume.DefaultDriverName) {
|
|
| 170 |
- return nil, derr.ErrorVolumeNameTaken.WithArgs(name, v.DriverName()) |
|
| 171 |
- } |
|
| 172 |
- |
|
| 173 |
- if driverName == "" {
|
|
| 174 |
- driverName = volume.DefaultDriverName |
|
| 175 |
- } |
|
| 176 |
- daemon.LogVolumeEvent(name, "create", map[string]string{"driver": driverName})
|
|
| 171 |
+ daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
|
|
| 177 | 172 |
return volumeToAPIType(v), nil |
| 178 | 173 |
} |
| ... | ... |
@@ -51,7 +51,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain |
| 51 | 51 |
} |
| 52 | 52 |
} |
| 53 | 53 |
|
| 54 |
- v, err := daemon.createVolume(name, volumeDriver, nil) |
|
| 54 |
+ v, err := daemon.volumes.CreateWithRef(name, volumeDriver, container.ID, nil) |
|
| 55 | 55 |
if err != nil {
|
| 56 | 56 |
return err |
| 57 | 57 |
} |
| ... | ... |
@@ -42,7 +42,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain |
| 42 | 42 |
|
| 43 | 43 |
// Create the volume in the volume driver. If it doesn't exist, |
| 44 | 44 |
// a new one will be created. |
| 45 |
- v, err := daemon.createVolume(mp.Name, volumeDriver, nil) |
|
| 45 |
+ v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil) |
|
| 46 | 46 |
if err != nil {
|
| 47 | 47 |
return err |
| 48 | 48 |
} |
| ... | ... |
@@ -1487,10 +1487,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, |
| 1487 | 1487 |
} |
| 1488 | 1488 |
|
| 1489 | 1489 |
volumedrivers.Register(volumesDriver, volumesDriver.Name()) |
| 1490 |
- s := store.New() |
|
| 1491 |
- s.AddAll(volumesDriver.List()) |
|
| 1492 |
- |
|
| 1493 |
- return s, nil |
|
| 1490 |
+ return store.New(), nil |
|
| 1494 | 1491 |
} |
| 1495 | 1492 |
|
| 1496 | 1493 |
// AuthenticateToRegistry checks the validity of credentials in authConfig |
| ... | ... |
@@ -392,24 +392,27 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li |
| 392 | 392 |
|
| 393 | 393 |
// Volumes lists known volumes, using the filter to restrict the range |
| 394 | 394 |
// of volumes returned. |
| 395 |
-func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
|
|
| 395 |
+func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) {
|
|
| 396 | 396 |
var volumesOut []*types.Volume |
| 397 | 397 |
volFilters, err := filters.FromParam(filter) |
| 398 | 398 |
if err != nil {
|
| 399 |
- return nil, err |
|
| 399 |
+ return nil, nil, err |
|
| 400 | 400 |
} |
| 401 | 401 |
|
| 402 | 402 |
filterUsed := volFilters.Include("dangling") &&
|
| 403 | 403 |
(volFilters.ExactMatch("dangling", "true") || volFilters.ExactMatch("dangling", "1"))
|
| 404 | 404 |
|
| 405 |
- volumes := daemon.volumes.List() |
|
| 405 |
+ volumes, warnings, err := daemon.volumes.List() |
|
| 406 |
+ if err != nil {
|
|
| 407 |
+ return nil, nil, err |
|
| 408 |
+ } |
|
| 409 |
+ if filterUsed {
|
|
| 410 |
+ volumes = daemon.volumes.FilterByUsed(volumes) |
|
| 411 |
+ } |
|
| 406 | 412 |
for _, v := range volumes {
|
| 407 |
- if filterUsed && daemon.volumes.Count(v) > 0 {
|
|
| 408 |
- continue |
|
| 409 |
- } |
|
| 410 | 413 |
volumesOut = append(volumesOut, volumeToAPIType(v)) |
| 411 | 414 |
} |
| 412 |
- return volumesOut, nil |
|
| 415 |
+ return volumesOut, warnings, nil |
|
| 413 | 416 |
} |
| 414 | 417 |
|
| 415 | 418 |
func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
|
| ... | ... |
@@ -11,10 +11,11 @@ import ( |
| 11 | 11 |
func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
|
| 12 | 12 |
for _, config := range container.MountPoints {
|
| 13 | 13 |
if len(config.Driver) > 0 {
|
| 14 |
- v, err := daemon.createVolume(config.Name, config.Driver, nil) |
|
| 14 |
+ v, err := daemon.volumes.GetWithRef(config.Name, config.Driver, container.ID) |
|
| 15 | 15 |
if err != nil {
|
| 16 | 16 |
return err |
| 17 | 17 |
} |
| 18 |
+ |
|
| 18 | 19 |
config.Volume = v |
| 19 | 20 |
} |
| 20 | 21 |
} |
| ... | ... |
@@ -27,10 +28,10 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool) |
| 27 | 27 |
if m.Volume == nil {
|
| 28 | 28 |
continue |
| 29 | 29 |
} |
| 30 |
- daemon.volumes.Decrement(m.Volume) |
|
| 30 |
+ daemon.volumes.Dereference(m.Volume, container.ID) |
|
| 31 | 31 |
if rm {
|
| 32 | 32 |
err := daemon.volumes.Remove(m.Volume) |
| 33 |
- // ErrVolumeInUse is ignored because having this |
|
| 33 |
+ // Ignore volume in use errors because having this |
|
| 34 | 34 |
// volume being referenced by other container is |
| 35 | 35 |
// not an error, but an implementation detail. |
| 36 | 36 |
// This prevents docker from logging "ERROR: Volume in use" |
| ... | ... |
@@ -32,16 +32,6 @@ func volumeToAPIType(v volume.Volume) *types.Volume {
|
| 32 | 32 |
} |
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 |
-// createVolume creates a volume. |
|
| 36 |
-func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
|
| 37 |
- v, err := daemon.volumes.Create(name, driverName, opts) |
|
| 38 |
- if err != nil {
|
|
| 39 |
- return nil, err |
|
| 40 |
- } |
|
| 41 |
- daemon.volumes.Increment(v) |
|
| 42 |
- return v, nil |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 | 35 |
// Len returns the number of mounts. Used in sorting. |
| 46 | 36 |
func (m mounts) Len() int {
|
| 47 | 37 |
return len(m) |
| ... | ... |
@@ -103,7 +93,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 | 105 |
if len(cp.Source) == 0 {
|
| 106 |
- v, err := daemon.createVolume(cp.Name, cp.Driver, nil) |
|
| 106 |
+ v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID) |
|
| 107 | 107 |
if err != nil {
|
| 108 | 108 |
return err |
| 109 | 109 |
} |
| ... | ... |
@@ -128,7 +118,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 128 | 128 |
|
| 129 | 129 |
if len(bind.Name) > 0 && len(bind.Driver) > 0 {
|
| 130 | 130 |
// create the volume |
| 131 |
- v, err := daemon.createVolume(bind.Name, bind.Driver, nil) |
|
| 131 |
+ v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil) |
|
| 132 | 132 |
if err != nil {
|
| 133 | 133 |
return err |
| 134 | 134 |
} |
| ... | ... |
@@ -153,7 +143,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 153 | 153 |
for _, m := range mountPoints {
|
| 154 | 154 |
if m.BackwardsCompatible() {
|
| 155 | 155 |
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
|
| 156 |
- daemon.volumes.Decrement(mp.Volume) |
|
| 156 |
+ daemon.volumes.Dereference(mp.Volume, container.ID) |
|
| 157 | 157 |
} |
| 158 | 158 |
} |
| 159 | 159 |
} |
| ... | ... |
@@ -908,7 +908,7 @@ var ( |
| 908 | 908 |
// trying to create a volume that has existed using different driver. |
| 909 | 909 |
ErrorVolumeNameTaken = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
| 910 | 910 |
Value: "VOLUME_NAME_TAKEN", |
| 911 |
- Message: "A volume named %q already exists with the %q driver. Choose a different volume name.", |
|
| 911 |
+ Message: "A volume named %s already exists. Choose a different volume name.", |
|
| 912 | 912 |
Description: "An attempt to create a volume using a driver but the volume already exists with a different driver", |
| 913 | 913 |
HTTPStatusCode: http.StatusInternalServerError, |
| 914 | 914 |
}) |
| ... | ... |
@@ -1733,8 +1733,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartRmVolumeInUse(c *check.C) {
|
| 1733 | 1733 |
c.Assert(s.d.Restart(), check.IsNil) |
| 1734 | 1734 |
|
| 1735 | 1735 |
out, err = s.d.Cmd("volume", "rm", "test")
|
| 1736 |
- c.Assert(err, check.Not(check.IsNil), check.Commentf("should not be able to remove in use volume after daemon restart"))
|
|
| 1737 |
- c.Assert(strings.Contains(out, "in use"), check.Equals, true) |
|
| 1736 |
+ c.Assert(err, check.NotNil, check.Commentf("should not be able to remove in use volume after daemon restart"))
|
|
| 1737 |
+ c.Assert(out, checker.Contains, "in use") |
|
| 1738 | 1738 |
} |
| 1739 | 1739 |
|
| 1740 | 1740 |
func (s *DockerDaemonSuite) TestDaemonRestartLocalVolumes(c *check.C) {
|
| ... | ... |
@@ -32,6 +32,8 @@ type eventCounter struct {
|
| 32 | 32 |
mounts int |
| 33 | 33 |
unmounts int |
| 34 | 34 |
paths int |
| 35 |
+ lists int |
|
| 36 |
+ gets int |
|
| 35 | 37 |
} |
| 36 | 38 |
|
| 37 | 39 |
type DockerExternalVolumeSuite struct {
|
| ... | ... |
@@ -64,6 +66,12 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
| 64 | 64 |
Err string `json:",omitempty"` |
| 65 | 65 |
} |
| 66 | 66 |
|
| 67 |
+ type vol struct {
|
|
| 68 |
+ Name string |
|
| 69 |
+ Mountpoint string |
|
| 70 |
+ } |
|
| 71 |
+ var volList []vol |
|
| 72 |
+ |
|
| 67 | 73 |
read := func(b io.ReadCloser) (pluginRequest, error) {
|
| 68 | 74 |
defer b.Close() |
| 69 | 75 |
var pr pluginRequest |
| ... | ... |
@@ -93,29 +101,61 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
| 93 | 93 |
|
| 94 | 94 |
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
| 95 | 95 |
s.ec.creations++ |
| 96 |
+ pr, err := read(r.Body) |
|
| 97 |
+ if err != nil {
|
|
| 98 |
+ send(w, err) |
|
| 99 |
+ return |
|
| 100 |
+ } |
|
| 101 |
+ volList = append(volList, vol{Name: pr.Name})
|
|
| 102 |
+ send(w, nil) |
|
| 103 |
+ }) |
|
| 96 | 104 |
|
| 97 |
- _, err := read(r.Body) |
|
| 105 |
+ mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
|
|
| 106 |
+ s.ec.lists++ |
|
| 107 |
+ send(w, map[string][]vol{"Volumes": volList})
|
|
| 108 |
+ }) |
|
| 109 |
+ |
|
| 110 |
+ mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
|
|
| 111 |
+ s.ec.gets++ |
|
| 112 |
+ pr, err := read(r.Body) |
|
| 98 | 113 |
if err != nil {
|
| 99 | 114 |
send(w, err) |
| 100 | 115 |
return |
| 101 | 116 |
} |
| 102 | 117 |
|
| 103 |
- send(w, nil) |
|
| 118 |
+ for _, v := range volList {
|
|
| 119 |
+ if v.Name == pr.Name {
|
|
| 120 |
+ v.Mountpoint = hostVolumePath(pr.Name) |
|
| 121 |
+ send(w, map[string]vol{"Volume": v})
|
|
| 122 |
+ return |
|
| 123 |
+ } |
|
| 124 |
+ } |
|
| 125 |
+ send(w, `{"Err": "no such volume"}`)
|
|
| 104 | 126 |
}) |
| 105 | 127 |
|
| 106 | 128 |
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
|
| 107 | 129 |
s.ec.removals++ |
| 108 |
- |
|
| 109 | 130 |
pr, err := read(r.Body) |
| 110 | 131 |
if err != nil {
|
| 111 | 132 |
send(w, err) |
| 112 | 133 |
return |
| 113 | 134 |
} |
| 135 |
+ |
|
| 114 | 136 |
if err := os.RemoveAll(hostVolumePath(pr.Name)); err != nil {
|
| 115 | 137 |
send(w, &pluginResp{Err: err.Error()})
|
| 116 | 138 |
return |
| 117 | 139 |
} |
| 118 | 140 |
|
| 141 |
+ for i, v := range volList {
|
|
| 142 |
+ if v.Name == pr.Name {
|
|
| 143 |
+ if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
|
|
| 144 |
+ send(w, fmt.Sprintf(`{"Err": "%v"}`, err))
|
|
| 145 |
+ return |
|
| 146 |
+ } |
|
| 147 |
+ volList = append(volList[:i], volList[i+1:]...) |
|
| 148 |
+ break |
|
| 149 |
+ } |
|
| 150 |
+ } |
|
| 119 | 151 |
send(w, nil) |
| 120 | 152 |
}) |
| 121 | 153 |
|
| ... | ... |
@@ -128,8 +168,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
| 128 | 128 |
return |
| 129 | 129 |
} |
| 130 | 130 |
p := hostVolumePath(pr.Name) |
| 131 |
- |
|
| 132 |
- fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
|
|
| 131 |
+ send(w, &pluginResp{Mountpoint: p})
|
|
| 133 | 132 |
}) |
| 134 | 133 |
|
| 135 | 134 |
mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
|
| ... | ... |
@@ -164,7 +203,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
| 164 | 164 |
return |
| 165 | 165 |
} |
| 166 | 166 |
|
| 167 |
- fmt.Fprintln(w, nil) |
|
| 167 |
+ send(w, nil) |
|
| 168 | 168 |
}) |
| 169 | 169 |
|
| 170 | 170 |
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
| ... | ... |
@@ -287,8 +326,8 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamedCheckBindLocalV |
| 287 | 287 |
// Make sure a request to use a down driver doesn't block other requests |
| 288 | 288 |
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) {
|
| 289 | 289 |
specPath := "/etc/docker/plugins/down-driver.spec" |
| 290 |
- err := ioutil.WriteFile("/etc/docker/plugins/down-driver.spec", []byte("tcp://127.0.0.7:9999"), 0644)
|
|
| 291 |
- c.Assert(err, checker.IsNil) |
|
| 290 |
+ err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644)
|
|
| 291 |
+ c.Assert(err, check.IsNil) |
|
| 292 | 292 |
defer os.RemoveAll(specPath) |
| 293 | 293 |
|
| 294 | 294 |
chCmd1 := make(chan struct{})
|
| ... | ... |
@@ -316,10 +355,11 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c * |
| 316 | 316 |
case err := <-chCmd2: |
| 317 | 317 |
c.Assert(err, checker.IsNil) |
| 318 | 318 |
case <-time.After(5 * time.Second): |
| 319 |
- c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
|
|
| 320 | 319 |
cmd2.Process.Kill() |
| 320 |
+ c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
|
|
| 321 | 321 |
} |
| 322 | 322 |
} |
| 323 |
+ |
|
| 323 | 324 |
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) {
|
| 324 | 325 |
err := s.d.StartWithBusybox() |
| 325 | 326 |
c.Assert(err, checker.IsNil) |
| ... | ... |
@@ -371,3 +411,24 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c |
| 371 | 371 |
c.Assert(mounts[0].Name, checker.Equals, "foo") |
| 372 | 372 |
c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver") |
| 373 | 373 |
} |
| 374 |
+ |
|
| 375 |
+func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverList(c *check.C) {
|
|
| 376 |
+ dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc") |
|
| 377 |
+ out, _ := dockerCmd(c, "volume", "ls") |
|
| 378 |
+ ls := strings.Split(strings.TrimSpace(out), "\n") |
|
| 379 |
+ c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out))
|
|
| 380 |
+ |
|
| 381 |
+ vol := strings.Fields(ls[len(ls)-1]) |
|
| 382 |
+ c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol))
|
|
| 383 |
+ c.Assert(vol[0], check.Equals, "test-external-volume-driver") |
|
| 384 |
+ c.Assert(vol[1], check.Equals, "abc") |
|
| 385 |
+ |
|
| 386 |
+ c.Assert(s.ec.lists, check.Equals, 1) |
|
| 387 |
+} |
|
| 388 |
+ |
|
| 389 |
+func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverGet(c *check.C) {
|
|
| 390 |
+ out, _, err := dockerCmdWithError("volume", "inspect", "dummy")
|
|
| 391 |
+ c.Assert(err, check.NotNil, check.Commentf(out)) |
|
| 392 |
+ c.Assert(s.ec.gets, check.Equals, 1) |
|
| 393 |
+ c.Assert(out, checker.Contains, "No such volume") |
|
| 394 |
+} |
| ... | ... |
@@ -4,9 +4,7 @@ import ( |
| 4 | 4 |
"os/exec" |
| 5 | 5 |
"strings" |
| 6 | 6 |
|
| 7 |
- derr "github.com/docker/docker/errors" |
|
| 8 | 7 |
"github.com/docker/docker/pkg/integration/checker" |
| 9 |
- "github.com/docker/docker/volume" |
|
| 10 | 8 |
"github.com/go-check/check" |
| 11 | 9 |
) |
| 12 | 10 |
|
| ... | ... |
@@ -25,8 +23,7 @@ func (s *DockerSuite) TestVolumeCliCreateOptionConflict(c *check.C) {
|
| 25 | 25 |
dockerCmd(c, "volume", "create", "--name=test") |
| 26 | 26 |
out, _, err := dockerCmdWithError("volume", "create", "--name", "test", "--driver", "nosuchdriver")
|
| 27 | 27 |
c.Assert(err, check.NotNil, check.Commentf("volume create exception name already in use with another driver"))
|
| 28 |
- stderr := derr.ErrorVolumeNameTaken.WithArgs("test", volume.DefaultDriverName).Error()
|
|
| 29 |
- c.Assert(strings.Contains(out, strings.TrimPrefix(stderr, "volume name taken: ")), check.Equals, true) |
|
| 28 |
+ c.Assert(out, checker.Contains, "A volume named test already exists") |
|
| 30 | 29 |
|
| 31 | 30 |
out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Driver }}'", "test")
|
| 32 | 31 |
_, _, err = dockerCmdWithError("volume", "create", "--name", "test", "--driver", strings.TrimSpace(out))
|
| ... | ... |
@@ -25,6 +25,38 @@ func newLocalRegistry() localRegistry {
|
| 25 | 25 |
return localRegistry{}
|
| 26 | 26 |
} |
| 27 | 27 |
|
| 28 |
+// Scan scans all the plugin paths and returns all the names it found |
|
| 29 |
+func Scan() ([]string, error) {
|
|
| 30 |
+ var names []string |
|
| 31 |
+ if err := filepath.Walk(socketsPath, func(path string, fi os.FileInfo, err error) error {
|
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ return nil |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ if fi.Mode()&os.ModeSocket != 0 {
|
|
| 37 |
+ name := strings.TrimSuffix(fi.Name(), filepath.Ext(fi.Name())) |
|
| 38 |
+ names = append(names, name) |
|
| 39 |
+ } |
|
| 40 |
+ return nil |
|
| 41 |
+ }); err != nil {
|
|
| 42 |
+ return nil, err |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ for _, path := range specsPaths {
|
|
| 46 |
+ if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
|
|
| 47 |
+ if err != nil || fi.IsDir() {
|
|
| 48 |
+ return nil |
|
| 49 |
+ } |
|
| 50 |
+ name := strings.TrimSuffix(fi.Name(), filepath.Ext(fi.Name())) |
|
| 51 |
+ names = append(names, name) |
|
| 52 |
+ return nil |
|
| 53 |
+ }); err != nil {
|
|
| 54 |
+ return nil, err |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ return names, nil |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 28 | 60 |
// Plugin returns the plugin registered with the given name (or returns an error). |
| 29 | 61 |
func (l *localRegistry) Plugin(name string) (*Plugin, error) {
|
| 30 | 62 |
socketpaths := pluginPaths(socketsPath, name, ".sock") |
| ... | ... |
@@ -108,6 +108,15 @@ func (p *Plugin) activateWithLock() error {
|
| 108 | 108 |
return nil |
| 109 | 109 |
} |
| 110 | 110 |
|
| 111 |
+func (p *Plugin) implements(kind string) bool {
|
|
| 112 |
+ for _, driver := range p.Manifest.Implements {
|
|
| 113 |
+ if driver == kind {
|
|
| 114 |
+ return true |
|
| 115 |
+ } |
|
| 116 |
+ } |
|
| 117 |
+ return false |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 111 | 120 |
func load(name string) (*Plugin, error) {
|
| 112 | 121 |
return loadWithRetry(name, true) |
| 113 | 122 |
} |
| ... | ... |
@@ -166,11 +175,9 @@ func Get(name, imp string) (*Plugin, error) {
|
| 166 | 166 |
if err != nil {
|
| 167 | 167 |
return nil, err |
| 168 | 168 |
} |
| 169 |
- for _, driver := range pl.Manifest.Implements {
|
|
| 170 |
- logrus.Debugf("%s implements: %s", name, driver)
|
|
| 171 |
- if driver == imp {
|
|
| 172 |
- return pl, nil |
|
| 173 |
- } |
|
| 169 |
+ if pl.implements(imp) {
|
|
| 170 |
+ logrus.Debugf("%s implements: %s", name, imp)
|
|
| 171 |
+ return pl, nil |
|
| 174 | 172 |
} |
| 175 | 173 |
return nil, ErrNotImplements |
| 176 | 174 |
} |
| ... | ... |
@@ -179,3 +186,37 @@ func Get(name, imp string) (*Plugin, error) {
|
| 179 | 179 |
func Handle(iface string, fn func(string, *Client)) {
|
| 180 | 180 |
extpointHandlers[iface] = fn |
| 181 | 181 |
} |
| 182 |
+ |
|
| 183 |
+// GetAll returns all the plugins for the specified implementation |
|
| 184 |
+func GetAll(imp string) ([]*Plugin, error) {
|
|
| 185 |
+ pluginNames, err := Scan() |
|
| 186 |
+ if err != nil {
|
|
| 187 |
+ return nil, err |
|
| 188 |
+ } |
|
| 189 |
+ |
|
| 190 |
+ type plLoad struct {
|
|
| 191 |
+ pl *Plugin |
|
| 192 |
+ err error |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ chPl := make(chan plLoad, len(pluginNames)) |
|
| 196 |
+ for _, name := range pluginNames {
|
|
| 197 |
+ go func(name string) {
|
|
| 198 |
+ pl, err := loadWithRetry(name, false) |
|
| 199 |
+ chPl <- plLoad{pl, err}
|
|
| 200 |
+ }(name) |
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 203 |
+ var out []*Plugin |
|
| 204 |
+ for i := 0; i < len(pluginNames); i++ {
|
|
| 205 |
+ pl := <-chPl |
|
| 206 |
+ if pl.err != nil {
|
|
| 207 |
+ logrus.Error(err) |
|
| 208 |
+ continue |
|
| 209 |
+ } |
|
| 210 |
+ if pl.pl.implements(imp) {
|
|
| 211 |
+ out = append(out, pl.pl) |
|
| 212 |
+ } |
|
| 213 |
+ } |
|
| 214 |
+ return out, nil |
|
| 215 |
+} |
| ... | ... |
@@ -26,6 +26,38 @@ func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
|
| 26 | 26 |
return a.proxy.Remove(v.Name()) |
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 |
+func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
|
|
| 30 |
+ ls, err := a.proxy.List() |
|
| 31 |
+ if err != nil {
|
|
| 32 |
+ return nil, err |
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ var out []volume.Volume |
|
| 36 |
+ for _, vp := range ls {
|
|
| 37 |
+ out = append(out, &volumeAdapter{
|
|
| 38 |
+ proxy: a.proxy, |
|
| 39 |
+ name: vp.Name, |
|
| 40 |
+ driverName: a.name, |
|
| 41 |
+ eMount: vp.Mountpoint, |
|
| 42 |
+ }) |
|
| 43 |
+ } |
|
| 44 |
+ return out, nil |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
|
|
| 48 |
+ v, err := a.proxy.Get(name) |
|
| 49 |
+ if err != nil {
|
|
| 50 |
+ return nil, err |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ return &volumeAdapter{
|
|
| 54 |
+ proxy: a.proxy, |
|
| 55 |
+ name: v.Name, |
|
| 56 |
+ driverName: a.Name(), |
|
| 57 |
+ eMount: v.Mountpoint, |
|
| 58 |
+ }, nil |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 29 | 61 |
type volumeAdapter struct {
|
| 30 | 62 |
proxy *volumeDriverProxy |
| 31 | 63 |
name string |
| ... | ... |
@@ -15,6 +15,8 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver)}
|
| 17 | 17 |
|
| 18 |
+const extName = "VolumeDriver" |
|
| 19 |
+ |
|
| 18 | 20 |
// NewVolumeDriver returns a driver has the given name mapped on the given client. |
| 19 | 21 |
func NewVolumeDriver(name string, c client) volume.Driver {
|
| 20 | 22 |
proxy := &volumeDriverProxy{c}
|
| ... | ... |
@@ -22,6 +24,7 @@ func NewVolumeDriver(name string, c client) volume.Driver {
|
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 | 24 |
type opts map[string]string |
| 25 |
+type list []*proxyVolume |
|
| 25 | 26 |
|
| 26 | 27 |
// volumeDriver defines the available functions that volume plugins must implement. |
| 27 | 28 |
// This interface is only defined to generate the proxy objects. |
| ... | ... |
@@ -37,6 +40,10 @@ type volumeDriver interface {
|
| 37 | 37 |
Mount(name string) (mountpoint string, err error) |
| 38 | 38 |
// Unmount the given volume |
| 39 | 39 |
Unmount(name string) (err error) |
| 40 |
+ // List lists all the volumes known to the driver |
|
| 41 |
+ List() (volumes list, err error) |
|
| 42 |
+ // Get retreives the volume with the requested name |
|
| 43 |
+ Get(name string) (volume *proxyVolume, err error) |
|
| 40 | 44 |
} |
| 41 | 45 |
|
| 42 | 46 |
type driverExtpoint struct {
|
| ... | ... |
@@ -82,7 +89,7 @@ func Lookup(name string) (volume.Driver, error) {
|
| 82 | 82 |
if ok {
|
| 83 | 83 |
return ext, nil |
| 84 | 84 |
} |
| 85 |
- pl, err := plugins.Get(name, "VolumeDriver") |
|
| 85 |
+ pl, err := plugins.Get(name, extName) |
|
| 86 | 86 |
if err != nil {
|
| 87 | 87 |
return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
|
| 88 | 88 |
} |
| ... | ... |
@@ -116,3 +123,30 @@ func GetDriverList() []string {
|
| 116 | 116 |
} |
| 117 | 117 |
return driverList |
| 118 | 118 |
} |
| 119 |
+ |
|
| 120 |
+// GetAllDrivers lists all the registered drivers |
|
| 121 |
+func GetAllDrivers() ([]volume.Driver, error) {
|
|
| 122 |
+ plugins, err := plugins.GetAll(extName) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ return nil, err |
|
| 125 |
+ } |
|
| 126 |
+ var ds []volume.Driver |
|
| 127 |
+ |
|
| 128 |
+ drivers.Lock() |
|
| 129 |
+ defer drivers.Unlock() |
|
| 130 |
+ |
|
| 131 |
+ for _, d := range drivers.extensions {
|
|
| 132 |
+ ds = append(ds, d) |
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ for _, p := range plugins {
|
|
| 136 |
+ ext, ok := drivers.extensions[p.Name] |
|
| 137 |
+ if ok {
|
|
| 138 |
+ continue |
|
| 139 |
+ } |
|
| 140 |
+ ext = NewVolumeDriver(p.Name, p.Client) |
|
| 141 |
+ drivers.extensions[p.Name] = ext |
|
| 142 |
+ ds = append(ds, ext) |
|
| 143 |
+ } |
|
| 144 |
+ return ds, nil |
|
| 145 |
+} |
| ... | ... |
@@ -11,7 +11,8 @@ func TestGetDriver(t *testing.T) {
|
| 11 | 11 |
if err == nil {
|
| 12 | 12 |
t.Fatal("Expected error, was nil")
|
| 13 | 13 |
} |
| 14 |
- Register(volumetestutils.FakeDriver{}, "fake")
|
|
| 14 |
+ |
|
| 15 |
+ Register(volumetestutils.NewFakeDriver("fake"), "fake")
|
|
| 15 | 16 |
d, err := GetDriver("fake")
|
| 16 | 17 |
if err != nil {
|
| 17 | 18 |
t.Fatal(err) |
| ... | ... |
@@ -149,3 +149,59 @@ func (pp *volumeDriverProxy) Unmount(name string) (err error) {
|
| 149 | 149 |
|
| 150 | 150 |
return |
| 151 | 151 |
} |
| 152 |
+ |
|
| 153 |
+type volumeDriverProxyListRequest struct {
|
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+type volumeDriverProxyListResponse struct {
|
|
| 157 |
+ Volumes list |
|
| 158 |
+ Err string |
|
| 159 |
+} |
|
| 160 |
+ |
|
| 161 |
+func (pp *volumeDriverProxy) List() (volumes list, err error) {
|
|
| 162 |
+ var ( |
|
| 163 |
+ req volumeDriverProxyListRequest |
|
| 164 |
+ ret volumeDriverProxyListResponse |
|
| 165 |
+ ) |
|
| 166 |
+ |
|
| 167 |
+ if err = pp.Call("VolumeDriver.List", req, &ret); err != nil {
|
|
| 168 |
+ return |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ volumes = ret.Volumes |
|
| 172 |
+ |
|
| 173 |
+ if ret.Err != "" {
|
|
| 174 |
+ err = errors.New(ret.Err) |
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ return |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+type volumeDriverProxyGetRequest struct {
|
|
| 181 |
+ Name string |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+type volumeDriverProxyGetResponse struct {
|
|
| 185 |
+ Volume *proxyVolume |
|
| 186 |
+ Err string |
|
| 187 |
+} |
|
| 188 |
+ |
|
| 189 |
+func (pp *volumeDriverProxy) Get(name string) (volume *proxyVolume, err error) {
|
|
| 190 |
+ var ( |
|
| 191 |
+ req volumeDriverProxyGetRequest |
|
| 192 |
+ ret volumeDriverProxyGetResponse |
|
| 193 |
+ ) |
|
| 194 |
+ |
|
| 195 |
+ req.Name = name |
|
| 196 |
+ if err = pp.Call("VolumeDriver.Get", req, &ret); err != nil {
|
|
| 197 |
+ return |
|
| 198 |
+ } |
|
| 199 |
+ |
|
| 200 |
+ volume = ret.Volume |
|
| 201 |
+ |
|
| 202 |
+ if ret.Err != "" {
|
|
| 203 |
+ err = errors.New(ret.Err) |
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ return |
|
| 207 |
+} |
| ... | ... |
@@ -42,6 +42,16 @@ func TestVolumeRequestError(t *testing.T) {
|
| 42 | 42 |
fmt.Fprintln(w, `{"Err": "Unknown volume"}`)
|
| 43 | 43 |
}) |
| 44 | 44 |
|
| 45 |
+ mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
|
|
| 46 |
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
|
| 47 |
+ fmt.Fprintln(w, `{"Err": "Cannot list volumes"}`)
|
|
| 48 |
+ }) |
|
| 49 |
+ |
|
| 50 |
+ mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
|
|
| 51 |
+ w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
|
| 52 |
+ fmt.Fprintln(w, `{"Err": "Cannot get volume"}`)
|
|
| 53 |
+ }) |
|
| 54 |
+ |
|
| 45 | 55 |
u, _ := url.Parse(server.URL) |
| 46 | 56 |
client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
|
| 47 | 57 |
if err != nil {
|
| ... | ... |
@@ -93,4 +103,20 @@ func TestVolumeRequestError(t *testing.T) {
|
| 93 | 93 |
if !strings.Contains(err.Error(), "Unknown volume") {
|
| 94 | 94 |
t.Fatalf("Unexpected error: %v\n", err)
|
| 95 | 95 |
} |
| 96 |
+ |
|
| 97 |
+ _, err = driver.List() |
|
| 98 |
+ if err == nil {
|
|
| 99 |
+ t.Fatal("Expected error, was nil")
|
|
| 100 |
+ } |
|
| 101 |
+ if !strings.Contains(err.Error(), "Cannot list volumes") {
|
|
| 102 |
+ t.Fatalf("Unexpected error: %v\n", err)
|
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ _, err = driver.Get("volume")
|
|
| 106 |
+ if err == nil {
|
|
| 107 |
+ t.Fatal("Expected error, was nil")
|
|
| 108 |
+ } |
|
| 109 |
+ if !strings.Contains(err.Error(), "Cannot get volume") {
|
|
| 110 |
+ t.Fatalf("Unexpected error: %v\n", err)
|
|
| 111 |
+ } |
|
| 96 | 112 |
} |
| ... | ... |
@@ -82,12 +82,12 @@ type Root struct {
|
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 | 84 |
// List lists all the volumes |
| 85 |
-func (r *Root) List() []volume.Volume {
|
|
| 85 |
+func (r *Root) List() ([]volume.Volume, error) {
|
|
| 86 | 86 |
var ls []volume.Volume |
| 87 | 87 |
for _, v := range r.volumes {
|
| 88 | 88 |
ls = append(ls, v) |
| 89 | 89 |
} |
| 90 |
- return ls |
|
| 90 |
+ return ls, nil |
|
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 | 93 |
// DataPath returns the constructed path of this volume. |
| ... | ... |
@@ -1,6 +1,9 @@ |
| 1 | 1 |
package store |
| 2 | 2 |
|
| 3 |
-import "errors" |
|
| 3 |
+import ( |
|
| 4 |
+ "errors" |
|
| 5 |
+ "strings" |
|
| 6 |
+) |
|
| 4 | 7 |
|
| 5 | 8 |
var ( |
| 6 | 9 |
// errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container |
| ... | ... |
@@ -9,6 +12,8 @@ var ( |
| 9 | 9 |
errNoSuchVolume = errors.New("no such volume")
|
| 10 | 10 |
// errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform |
| 11 | 11 |
errInvalidName = errors.New("volume name is not valid on this platform")
|
| 12 |
+ // errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver |
|
| 13 |
+ errNameConflict = errors.New("conflict: volume name must be unique")
|
|
| 12 | 14 |
) |
| 13 | 15 |
|
| 14 | 16 |
// OpErr is the error type returned by functions in the store package. It describes |
| ... | ... |
@@ -20,6 +25,8 @@ type OpErr struct {
|
| 20 | 20 |
Op string |
| 21 | 21 |
// Name is the name of the resource being requested for this op, typically the volume name or the driver name. |
| 22 | 22 |
Name string |
| 23 |
+ // Refs is the list of references associated with the resource. |
|
| 24 |
+ Refs []string |
|
| 23 | 25 |
} |
| 24 | 26 |
|
| 25 | 27 |
// Error satisfies the built-in error interface type. |
| ... | ... |
@@ -33,6 +40,9 @@ func (e *OpErr) Error() string {
|
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 | 35 |
s = s + ": " + e.Err.Error() |
| 36 |
+ if len(e.Refs) > 0 {
|
|
| 37 |
+ s = s + " - " + "[" + strings.Join(e.Refs, ", ") + "]" |
|
| 38 |
+ } |
|
| 36 | 39 |
return s |
| 37 | 40 |
} |
| 38 | 41 |
|
| ... | ... |
@@ -47,6 +57,12 @@ func IsNotExist(err error) bool {
|
| 47 | 47 |
return isErr(err, errNoSuchVolume) |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 |
+// IsNameConflict returns a boolean indicating whether the error indicates that a |
|
| 51 |
+// volume name is already taken |
|
| 52 |
+func IsNameConflict(err error) bool {
|
|
| 53 |
+ return isErr(err, errNameConflict) |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 50 | 56 |
func isErr(err error, expected error) bool {
|
| 51 | 57 |
switch pe := err.(type) {
|
| 52 | 58 |
case nil: |
| ... | ... |
@@ -13,66 +13,153 @@ import ( |
| 13 | 13 |
// reference counting of volumes in the system. |
| 14 | 14 |
func New() *VolumeStore {
|
| 15 | 15 |
return &VolumeStore{
|
| 16 |
- vols: make(map[string]*volumeCounter), |
|
| 17 | 16 |
locks: &locker.Locker{},
|
| 17 |
+ names: make(map[string]string), |
|
| 18 |
+ refs: make(map[string][]string), |
|
| 18 | 19 |
} |
| 19 | 20 |
} |
| 20 | 21 |
|
| 21 |
-func (s *VolumeStore) get(name string) (*volumeCounter, bool) {
|
|
| 22 |
+func (s *VolumeStore) getNamed(name string) (string, bool) {
|
|
| 22 | 23 |
s.globalLock.Lock() |
| 23 |
- vc, exists := s.vols[name] |
|
| 24 |
+ driverName, exists := s.names[name] |
|
| 24 | 25 |
s.globalLock.Unlock() |
| 25 |
- return vc, exists |
|
| 26 |
+ return driverName, exists |
|
| 26 | 27 |
} |
| 27 | 28 |
|
| 28 |
-func (s *VolumeStore) set(name string, vc *volumeCounter) {
|
|
| 29 |
+func (s *VolumeStore) setNamed(name, driver, ref string) {
|
|
| 29 | 30 |
s.globalLock.Lock() |
| 30 |
- s.vols[name] = vc |
|
| 31 |
+ s.names[name] = driver |
|
| 32 |
+ if len(ref) > 0 {
|
|
| 33 |
+ s.refs[name] = append(s.refs[name], ref) |
|
| 34 |
+ } |
|
| 31 | 35 |
s.globalLock.Unlock() |
| 32 | 36 |
} |
| 33 | 37 |
|
| 34 |
-func (s *VolumeStore) remove(name string) {
|
|
| 38 |
+func (s *VolumeStore) purge(name string) {
|
|
| 35 | 39 |
s.globalLock.Lock() |
| 36 |
- delete(s.vols, name) |
|
| 40 |
+ delete(s.names, name) |
|
| 41 |
+ delete(s.refs, name) |
|
| 37 | 42 |
s.globalLock.Unlock() |
| 38 | 43 |
} |
| 39 | 44 |
|
| 40 | 45 |
// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts |
| 41 | 46 |
type VolumeStore struct {
|
| 42 |
- vols map[string]*volumeCounter |
|
| 43 | 47 |
locks *locker.Locker |
| 44 | 48 |
globalLock sync.Mutex |
| 49 |
+ // names stores the volume name -> driver name relationship. |
|
| 50 |
+ // This is used for making lookups faster so we don't have to probe all drivers |
|
| 51 |
+ names map[string]string |
|
| 52 |
+ // refs stores the volume name and the list of things referencing it |
|
| 53 |
+ refs map[string][]string |
|
| 45 | 54 |
} |
| 46 | 55 |
|
| 47 |
-// volumeCounter keeps track of references to a volume |
|
| 48 |
-type volumeCounter struct {
|
|
| 49 |
- volume.Volume |
|
| 50 |
- count uint |
|
| 51 |
-} |
|
| 56 |
+// List proxies to all registered volume drivers to get the full list of volumes |
|
| 57 |
+// If a driver returns a volume that has name which conflicts with a another volume from a different driver, |
|
| 58 |
+// the first volume is chosen and the conflicting volume is dropped. |
|
| 59 |
+func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
|
|
| 60 |
+ vols, warnings, err := s.list() |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return nil, nil, &OpErr{Err: err, Op: "list"}
|
|
| 63 |
+ } |
|
| 64 |
+ var out []volume.Volume |
|
| 52 | 65 |
|
| 53 |
-// AddAll adds a list of volumes to the store |
|
| 54 |
-func (s *VolumeStore) AddAll(vols []volume.Volume) {
|
|
| 55 | 66 |
for _, v := range vols {
|
| 56 |
- s.vols[normaliseVolumeName(v.Name())] = &volumeCounter{v, 0}
|
|
| 67 |
+ name := normaliseVolumeName(v.Name()) |
|
| 68 |
+ |
|
| 69 |
+ s.locks.Lock(name) |
|
| 70 |
+ driverName, exists := s.getNamed(name) |
|
| 71 |
+ if !exists {
|
|
| 72 |
+ s.setNamed(name, v.DriverName(), "") |
|
| 73 |
+ } |
|
| 74 |
+ if exists && driverName != v.DriverName() {
|
|
| 75 |
+ logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), driverName, v.DriverName())
|
|
| 76 |
+ s.locks.Unlock(v.Name()) |
|
| 77 |
+ continue |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ out = append(out, v) |
|
| 81 |
+ s.locks.Unlock(v.Name()) |
|
| 57 | 82 |
} |
| 83 |
+ return out, warnings, nil |
|
| 58 | 84 |
} |
| 59 | 85 |
|
| 60 |
-// Create tries to find an existing volume with the given name or create a new one from the passed in driver |
|
| 61 |
-func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
|
| 86 |
+// list goes through each volume driver and asks for its list of volumes. |
|
| 87 |
+func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
|
|
| 88 |
+ drivers, err := volumedrivers.GetAllDrivers() |
|
| 89 |
+ if err != nil {
|
|
| 90 |
+ return nil, nil, err |
|
| 91 |
+ } |
|
| 92 |
+ var ( |
|
| 93 |
+ ls []volume.Volume |
|
| 94 |
+ warnings []string |
|
| 95 |
+ ) |
|
| 96 |
+ |
|
| 97 |
+ type vols struct {
|
|
| 98 |
+ vols []volume.Volume |
|
| 99 |
+ err error |
|
| 100 |
+ } |
|
| 101 |
+ chVols := make(chan vols, len(drivers)) |
|
| 102 |
+ |
|
| 103 |
+ for _, vd := range drivers {
|
|
| 104 |
+ go func(d volume.Driver) {
|
|
| 105 |
+ vs, err := d.List() |
|
| 106 |
+ if err != nil {
|
|
| 107 |
+ chVols <- vols{err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
|
|
| 108 |
+ return |
|
| 109 |
+ } |
|
| 110 |
+ chVols <- vols{vols: vs}
|
|
| 111 |
+ }(vd) |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ for i := 0; i < len(drivers); i++ {
|
|
| 115 |
+ vs := <-chVols |
|
| 116 |
+ |
|
| 117 |
+ if vs.err != nil {
|
|
| 118 |
+ warnings = append(warnings, vs.err.Error()) |
|
| 119 |
+ logrus.Warn(vs.err) |
|
| 120 |
+ continue |
|
| 121 |
+ } |
|
| 122 |
+ ls = append(ls, vs.vols...) |
|
| 123 |
+ } |
|
| 124 |
+ return ls, warnings, nil |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+// CreateWithRef creates a volume with the given name and driver and stores the ref |
|
| 128 |
+// This is just like Create() except we store the reference while holding the lock. |
|
| 129 |
+// This ensures there's no race between creating a volume and then storing a reference. |
|
| 130 |
+func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[string]string) (volume.Volume, error) {
|
|
| 62 | 131 |
name = normaliseVolumeName(name) |
| 63 | 132 |
s.locks.Lock(name) |
| 64 | 133 |
defer s.locks.Unlock(name) |
| 65 | 134 |
|
| 66 |
- if vc, exists := s.get(name); exists {
|
|
| 67 |
- v := vc.Volume |
|
| 68 |
- return v, nil |
|
| 135 |
+ v, err := s.create(name, driverName, opts) |
|
| 136 |
+ if err != nil {
|
|
| 137 |
+ return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
|
| 69 | 138 |
} |
| 70 | 139 |
|
| 71 |
- vd, err := volumedrivers.GetDriver(driverName) |
|
| 140 |
+ s.setNamed(name, v.DriverName(), ref) |
|
| 141 |
+ return v, nil |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+// Create creates a volume with the given name and driver. |
|
| 145 |
+func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
|
| 146 |
+ name = normaliseVolumeName(name) |
|
| 147 |
+ s.locks.Lock(name) |
|
| 148 |
+ defer s.locks.Unlock(name) |
|
| 149 |
+ |
|
| 150 |
+ v, err := s.create(name, driverName, opts) |
|
| 72 | 151 |
if err != nil {
|
| 73 |
- return nil, &OpErr{Err: err, Name: driverName, Op: "create"}
|
|
| 152 |
+ return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
|
| 74 | 153 |
} |
| 154 |
+ s.setNamed(name, v.DriverName(), "") |
|
| 155 |
+ return v, nil |
|
| 156 |
+} |
|
| 75 | 157 |
|
| 158 |
+// create asks the given driver to create a volume with the name/opts. |
|
| 159 |
+// If a volume with the name is already known, it will ask the stored driver for the volume. |
|
| 160 |
+// If the passed in driver name does not match the driver name which is stored for the given volume name, an error is returned. |
|
| 161 |
+// It is expected that callers of this function hold any neccessary locks. |
|
| 162 |
+func (s *VolumeStore) create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
|
| 76 | 163 |
// Validate the name in a platform-specific manner |
| 77 | 164 |
valid, err := volume.IsVolumeNameValid(name) |
| 78 | 165 |
if err != nil {
|
| ... | ... |
@@ -82,135 +169,164 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v |
| 82 | 82 |
return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
|
| 83 | 83 |
} |
| 84 | 84 |
|
| 85 |
- v, err := vd.Create(name, opts) |
|
| 85 |
+ vdName, exists := s.getNamed(name) |
|
| 86 |
+ if exists {
|
|
| 87 |
+ if vdName != driverName && driverName != "" && driverName != volume.DefaultDriverName {
|
|
| 88 |
+ return nil, errNameConflict |
|
| 89 |
+ } |
|
| 90 |
+ driverName = vdName |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
|
|
| 94 |
+ vd, err := volumedrivers.GetDriver(driverName) |
|
| 86 | 95 |
if err != nil {
|
| 87 | 96 |
return nil, &OpErr{Op: "create", Name: name, Err: err}
|
| 88 | 97 |
} |
| 89 | 98 |
|
| 90 |
- s.set(name, &volumeCounter{v, 0})
|
|
| 91 |
- return v, nil |
|
| 99 |
+ if v, err := vd.Get(name); err == nil {
|
|
| 100 |
+ return v, nil |
|
| 101 |
+ } |
|
| 102 |
+ return vd.Create(name, opts) |
|
| 92 | 103 |
} |
| 93 | 104 |
|
| 94 |
-// Get looks if a volume with the given name exists and returns it if so |
|
| 95 |
-func (s *VolumeStore) Get(name string) (volume.Volume, error) {
|
|
| 105 |
+// GetWithRef gets a volume with the given name from the passed in driver and stores the ref |
|
| 106 |
+// This is just like Get(), but we store the reference while holding the lock. |
|
| 107 |
+// This makes sure there are no races between checking for the existance of a volume and adding a reference for it |
|
| 108 |
+func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, error) {
|
|
| 96 | 109 |
name = normaliseVolumeName(name) |
| 97 | 110 |
s.locks.Lock(name) |
| 98 | 111 |
defer s.locks.Unlock(name) |
| 99 | 112 |
|
| 100 |
- vc, exists := s.get(name) |
|
| 101 |
- if !exists {
|
|
| 102 |
- return nil, &OpErr{Err: errNoSuchVolume, Name: name, Op: "get"}
|
|
| 113 |
+ vd, err := volumedrivers.GetDriver(driverName) |
|
| 114 |
+ if err != nil {
|
|
| 115 |
+ return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ v, err := vd.Get(name) |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 103 | 121 |
} |
| 104 |
- return vc.Volume, nil |
|
| 122 |
+ |
|
| 123 |
+ s.setNamed(name, v.DriverName(), ref) |
|
| 124 |
+ return v, nil |
|
| 105 | 125 |
} |
| 106 | 126 |
|
| 107 |
-// Remove removes the requested volume. A volume is not removed if the usage count is > 0 |
|
| 108 |
-func (s *VolumeStore) Remove(v volume.Volume) error {
|
|
| 109 |
- name := normaliseVolumeName(v.Name()) |
|
| 127 |
+// Get looks if a volume with the given name exists and returns it if so |
|
| 128 |
+func (s *VolumeStore) Get(name string) (volume.Volume, error) {
|
|
| 129 |
+ name = normaliseVolumeName(name) |
|
| 110 | 130 |
s.locks.Lock(name) |
| 111 | 131 |
defer s.locks.Unlock(name) |
| 112 | 132 |
|
| 113 |
- logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
|
| 114 |
- vc, exists := s.get(name) |
|
| 115 |
- if !exists {
|
|
| 116 |
- return &OpErr{Err: errNoSuchVolume, Name: name, Op: "remove"}
|
|
| 133 |
+ v, err := s.getVolume(name) |
|
| 134 |
+ if err != nil {
|
|
| 135 |
+ return nil, &OpErr{Err: err, Name: name, Op: "get"}
|
|
| 117 | 136 |
} |
| 137 |
+ return v, nil |
|
| 138 |
+} |
|
| 118 | 139 |
|
| 119 |
- if vc.count > 0 {
|
|
| 120 |
- return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove"}
|
|
| 140 |
+// get requests the volume, if the driver info is stored it just access that driver, |
|
| 141 |
+// if the driver is unknown it probes all drivers until it finds the first volume with that name. |
|
| 142 |
+// it is expected that callers of this function hold any neccessary locks |
|
| 143 |
+func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
|
| 144 |
+ logrus.Debugf("Getting volume reference for name: %s", name)
|
|
| 145 |
+ if vdName, exists := s.names[name]; exists {
|
|
| 146 |
+ vd, err := volumedrivers.GetDriver(vdName) |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ return nil, err |
|
| 149 |
+ } |
|
| 150 |
+ return vd.Get(name) |
|
| 121 | 151 |
} |
| 122 | 152 |
|
| 123 |
- vd, err := volumedrivers.GetDriver(vc.DriverName()) |
|
| 153 |
+ logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
|
| 154 |
+ drivers, err := volumedrivers.GetAllDrivers() |
|
| 124 | 155 |
if err != nil {
|
| 125 |
- return &OpErr{Err: err, Name: vc.DriverName(), Op: "remove"}
|
|
| 126 |
- } |
|
| 127 |
- if err := vd.Remove(vc.Volume); err != nil {
|
|
| 128 |
- return &OpErr{Err: err, Name: name, Op: "remove"}
|
|
| 156 |
+ return nil, err |
|
| 129 | 157 |
} |
| 130 | 158 |
|
| 131 |
- s.remove(name) |
|
| 132 |
- return nil |
|
| 159 |
+ for _, d := range drivers {
|
|
| 160 |
+ v, err := d.Get(name) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ continue |
|
| 163 |
+ } |
|
| 164 |
+ return v, nil |
|
| 165 |
+ } |
|
| 166 |
+ return nil, errNoSuchVolume |
|
| 133 | 167 |
} |
| 134 | 168 |
|
| 135 |
-// Increment increments the usage count of the passed in volume by 1 |
|
| 136 |
-func (s *VolumeStore) Increment(v volume.Volume) {
|
|
| 169 |
+// Remove removes the requested volume. A volume is not removed if it has any refs |
|
| 170 |
+func (s *VolumeStore) Remove(v volume.Volume) error {
|
|
| 137 | 171 |
name := normaliseVolumeName(v.Name()) |
| 138 | 172 |
s.locks.Lock(name) |
| 139 | 173 |
defer s.locks.Unlock(name) |
| 140 | 174 |
|
| 141 |
- logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
|
|
| 142 |
- vc, exists := s.get(name) |
|
| 143 |
- if !exists {
|
|
| 144 |
- s.set(name, &volumeCounter{v, 1})
|
|
| 145 |
- return |
|
| 175 |
+ if refs, exists := s.refs[name]; exists && len(refs) > 0 {
|
|
| 176 |
+ return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: refs}
|
|
| 146 | 177 |
} |
| 147 |
- vc.count++ |
|
| 148 |
-} |
|
| 149 | 178 |
|
| 150 |
-// Decrement decrements the usage count of the passed in volume by 1 |
|
| 151 |
-func (s *VolumeStore) Decrement(v volume.Volume) {
|
|
| 152 |
- name := normaliseVolumeName(v.Name()) |
|
| 153 |
- s.locks.Lock(name) |
|
| 154 |
- defer s.locks.Unlock(name) |
|
| 155 |
- logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
|
|
| 156 |
- |
|
| 157 |
- vc, exists := s.get(name) |
|
| 158 |
- if !exists {
|
|
| 159 |
- return |
|
| 179 |
+ vd, err := volumedrivers.GetDriver(v.DriverName()) |
|
| 180 |
+ if err != nil {
|
|
| 181 |
+ return &OpErr{Err: err, Name: vd.Name(), Op: "remove"}
|
|
| 160 | 182 |
} |
| 161 |
- if vc.count == 0 {
|
|
| 162 |
- return |
|
| 183 |
+ |
|
| 184 |
+ logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
|
| 185 |
+ if err := vd.Remove(v); err != nil {
|
|
| 186 |
+ return &OpErr{Err: err, Name: name, Op: "remove"}
|
|
| 163 | 187 |
} |
| 164 |
- vc.count-- |
|
| 188 |
+ |
|
| 189 |
+ s.purge(name) |
|
| 190 |
+ return nil |
|
| 165 | 191 |
} |
| 166 | 192 |
|
| 167 |
-// Count returns the usage count of the passed in volume |
|
| 168 |
-func (s *VolumeStore) Count(v volume.Volume) uint {
|
|
| 169 |
- name := normaliseVolumeName(v.Name()) |
|
| 170 |
- s.locks.Lock(name) |
|
| 171 |
- defer s.locks.Unlock(name) |
|
| 193 |
+// Dereference removes the specified reference to the volume |
|
| 194 |
+func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
|
|
| 195 |
+ s.locks.Lock(v.Name()) |
|
| 196 |
+ defer s.locks.Unlock(v.Name()) |
|
| 172 | 197 |
|
| 173 |
- vc, exists := s.get(name) |
|
| 198 |
+ s.globalLock.Lock() |
|
| 199 |
+ refs, exists := s.refs[v.Name()] |
|
| 174 | 200 |
if !exists {
|
| 175 |
- return 0 |
|
| 201 |
+ return |
|
| 176 | 202 |
} |
| 177 |
- return vc.count |
|
| 178 |
-} |
|
| 179 | 203 |
|
| 180 |
-// List returns all the available volumes |
|
| 181 |
-func (s *VolumeStore) List() []volume.Volume {
|
|
| 182 |
- s.globalLock.Lock() |
|
| 183 |
- defer s.globalLock.Unlock() |
|
| 184 |
- var ls []volume.Volume |
|
| 185 |
- for _, vc := range s.vols {
|
|
| 186 |
- ls = append(ls, vc.Volume) |
|
| 204 |
+ for i, r := range refs {
|
|
| 205 |
+ if r == ref {
|
|
| 206 |
+ s.refs[v.Name()] = append(s.refs[v.Name()][:i], s.refs[v.Name()][i+1:]...) |
|
| 207 |
+ } |
|
| 187 | 208 |
} |
| 188 |
- return ls |
|
| 209 |
+ s.globalLock.Unlock() |
|
| 189 | 210 |
} |
| 190 | 211 |
|
| 191 | 212 |
// FilterByDriver returns the available volumes filtered by driver name |
| 192 |
-func (s *VolumeStore) FilterByDriver(name string) []volume.Volume {
|
|
| 193 |
- return s.filter(byDriver(name)) |
|
| 213 |
+func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
|
|
| 214 |
+ vd, err := volumedrivers.GetDriver(name) |
|
| 215 |
+ if err != nil {
|
|
| 216 |
+ return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
|
| 217 |
+ } |
|
| 218 |
+ ls, err := vd.List() |
|
| 219 |
+ if err != nil {
|
|
| 220 |
+ return nil, &OpErr{Err: err, Name: name, Op: "list"}
|
|
| 221 |
+ } |
|
| 222 |
+ return ls, nil |
|
| 223 |
+} |
|
| 224 |
+ |
|
| 225 |
+// FilterByUsed returns the available volumes filtered by if they are not in use |
|
| 226 |
+func (s *VolumeStore) FilterByUsed(vols []volume.Volume) []volume.Volume {
|
|
| 227 |
+ return s.filter(vols, func(v volume.Volume) bool {
|
|
| 228 |
+ s.locks.Lock(v.Name()) |
|
| 229 |
+ defer s.locks.Unlock(v.Name()) |
|
| 230 |
+ return len(s.refs[v.Name()]) == 0 |
|
| 231 |
+ }) |
|
| 194 | 232 |
} |
| 195 | 233 |
|
| 196 | 234 |
// filterFunc defines a function to allow filter volumes in the store |
| 197 | 235 |
type filterFunc func(vol volume.Volume) bool |
| 198 | 236 |
|
| 199 |
-// byDriver generates a filterFunc to filter volumes by their driver name |
|
| 200 |
-func byDriver(name string) filterFunc {
|
|
| 201 |
- return func(vol volume.Volume) bool {
|
|
| 202 |
- return vol.DriverName() == name |
|
| 203 |
- } |
|
| 204 |
-} |
|
| 205 |
- |
|
| 206 | 237 |
// filter returns the available volumes filtered by a filterFunc function |
| 207 |
-func (s *VolumeStore) filter(f filterFunc) []volume.Volume {
|
|
| 208 |
- s.globalLock.Lock() |
|
| 209 |
- defer s.globalLock.Unlock() |
|
| 238 |
+func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume {
|
|
| 210 | 239 |
var ls []volume.Volume |
| 211 |
- for _, vc := range s.vols {
|
|
| 212 |
- if f(vc.Volume) {
|
|
| 213 |
- ls = append(ls, vc.Volume) |
|
| 240 |
+ for _, v := range vols {
|
|
| 241 |
+ if f(v) {
|
|
| 242 |
+ ls = append(ls, v) |
|
| 214 | 243 |
} |
| 215 | 244 |
} |
| 216 | 245 |
return ls |
| ... | ... |
@@ -2,42 +2,16 @@ package store |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"errors" |
| 5 |
+ "strings" |
|
| 5 | 6 |
"testing" |
| 6 | 7 |
|
| 7 |
- "github.com/docker/docker/volume" |
|
| 8 | 8 |
"github.com/docker/docker/volume/drivers" |
| 9 | 9 |
vt "github.com/docker/docker/volume/testutils" |
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 |
-func TestList(t *testing.T) {
|
|
| 13 |
- volumedrivers.Register(vt.FakeDriver{}, "fake")
|
|
| 14 |
- s := New() |
|
| 15 |
- s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
|
|
| 16 |
- l := s.List() |
|
| 17 |
- if len(l) != 2 {
|
|
| 18 |
- t.Fatalf("Expected 2 volumes in the store, got %v: %v", len(l), l)
|
|
| 19 |
- } |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-func TestGet(t *testing.T) {
|
|
| 23 |
- volumedrivers.Register(vt.FakeDriver{}, "fake")
|
|
| 24 |
- s := New() |
|
| 25 |
- s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
|
|
| 26 |
- v, err := s.Get("fake1")
|
|
| 27 |
- if err != nil {
|
|
| 28 |
- t.Fatal(err) |
|
| 29 |
- } |
|
| 30 |
- if v.Name() != "fake1" {
|
|
| 31 |
- t.Fatalf("Expected fake1 volume, got %v", v)
|
|
| 32 |
- } |
|
| 33 |
- |
|
| 34 |
- if _, err := s.Get("fake4"); !IsNotExist(err) {
|
|
| 35 |
- t.Fatalf("Expected IsNotExist error, got %v", err)
|
|
| 36 |
- } |
|
| 37 |
-} |
|
| 38 |
- |
|
| 39 | 12 |
func TestCreate(t *testing.T) {
|
| 40 |
- volumedrivers.Register(vt.FakeDriver{}, "fake")
|
|
| 13 |
+ volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
|
| 14 |
+ defer volumedrivers.Unregister("fake")
|
|
| 41 | 15 |
s := New() |
| 42 | 16 |
v, err := s.Create("fake1", "fake", nil)
|
| 43 | 17 |
if err != nil {
|
| ... | ... |
@@ -46,7 +20,7 @@ func TestCreate(t *testing.T) {
|
| 46 | 46 |
if v.Name() != "fake1" {
|
| 47 | 47 |
t.Fatalf("Expected fake1 volume, got %v", v)
|
| 48 | 48 |
} |
| 49 |
- if l := s.List(); len(l) != 1 {
|
|
| 49 |
+ if l, _, _ := s.List(); len(l) != 1 {
|
|
| 50 | 50 |
t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
| 51 | 51 |
} |
| 52 | 52 |
|
| ... | ... |
@@ -62,93 +36,90 @@ func TestCreate(t *testing.T) {
|
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 | 64 |
func TestRemove(t *testing.T) {
|
| 65 |
- volumedrivers.Register(vt.FakeDriver{}, "fake")
|
|
| 65 |
+ volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
|
| 66 |
+ volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
|
| 67 |
+ defer volumedrivers.Unregister("fake")
|
|
| 68 |
+ defer volumedrivers.Unregister("noop")
|
|
| 66 | 69 |
s := New() |
| 67 |
- if err := s.Remove(vt.NoopVolume{}); !IsNotExist(err) {
|
|
| 68 |
- t.Fatalf("Expected IsNotExist error, got %v", err)
|
|
| 70 |
+ |
|
| 71 |
+ // doing string compare here since this error comes directly from the driver |
|
| 72 |
+ expected := "no such volume" |
|
| 73 |
+ if err := s.Remove(vt.NoopVolume{}); err == nil || !strings.Contains(err.Error(), expected) {
|
|
| 74 |
+ t.Fatalf("Expected error %q, got %v", expected, err)
|
|
| 69 | 75 |
} |
| 70 |
- v, err := s.Create("fake1", "fake", nil)
|
|
| 76 |
+ |
|
| 77 |
+ v, err := s.CreateWithRef("fake1", "fake", "fake", nil)
|
|
| 71 | 78 |
if err != nil {
|
| 72 | 79 |
t.Fatal(err) |
| 73 | 80 |
} |
| 74 |
- s.Increment(v) |
|
| 81 |
+ |
|
| 75 | 82 |
if err := s.Remove(v); !IsInUse(err) {
|
| 76 |
- t.Fatalf("Expected IsInUse error, got %v", err)
|
|
| 83 |
+ t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
|
| 77 | 84 |
} |
| 78 |
- s.Decrement(v) |
|
| 85 |
+ s.Dereference(v, "fake") |
|
| 79 | 86 |
if err := s.Remove(v); err != nil {
|
| 80 | 87 |
t.Fatal(err) |
| 81 | 88 |
} |
| 82 |
- if l := s.List(); len(l) != 0 {
|
|
| 89 |
+ if l, _, _ := s.List(); len(l) != 0 {
|
|
| 83 | 90 |
t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
|
| 84 | 91 |
} |
| 85 | 92 |
} |
| 86 | 93 |
|
| 87 |
-func TestIncrement(t *testing.T) {
|
|
| 94 |
+func TestList(t *testing.T) {
|
|
| 95 |
+ volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
|
| 96 |
+ volumedrivers.Register(vt.NewFakeDriver("fake2"), "fake2")
|
|
| 97 |
+ defer volumedrivers.Unregister("fake")
|
|
| 98 |
+ defer volumedrivers.Unregister("fake2")
|
|
| 99 |
+ |
|
| 88 | 100 |
s := New() |
| 89 |
- v := vt.NewFakeVolume("fake1")
|
|
| 90 |
- s.Increment(v) |
|
| 91 |
- if l := s.List(); len(l) != 1 {
|
|
| 92 |
- t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
|
| 101 |
+ if _, err := s.Create("test", "fake", nil); err != nil {
|
|
| 102 |
+ t.Fatal(err) |
|
| 93 | 103 |
} |
| 94 |
- if c := s.Count(v); c != 1 {
|
|
| 95 |
- t.Fatalf("Expected 1 counter, got %v", c)
|
|
| 104 |
+ if _, err := s.Create("test2", "fake2", nil); err != nil {
|
|
| 105 |
+ t.Fatal(err) |
|
| 96 | 106 |
} |
| 97 | 107 |
|
| 98 |
- s.Increment(v) |
|
| 99 |
- if l := s.List(); len(l) != 1 {
|
|
| 100 |
- t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
|
| 108 |
+ ls, _, err := s.List() |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ t.Fatal(err) |
|
| 101 | 111 |
} |
| 102 |
- if c := s.Count(v); c != 2 {
|
|
| 103 |
- t.Fatalf("Expected 2 counter, got %v", c)
|
|
| 112 |
+ if len(ls) != 2 {
|
|
| 113 |
+ t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
|
| 104 | 114 |
} |
| 105 | 115 |
|
| 106 |
- v2 := vt.NewFakeVolume("fake2")
|
|
| 107 |
- s.Increment(v2) |
|
| 108 |
- if l := s.List(); len(l) != 2 {
|
|
| 109 |
- t.Fatalf("Expected 2 volume, got %v, %v", len(l), l)
|
|
| 116 |
+ // and again with a new store |
|
| 117 |
+ s = New() |
|
| 118 |
+ ls, _, err = s.List() |
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ t.Fatal(err) |
|
| 121 |
+ } |
|
| 122 |
+ if len(ls) != 2 {
|
|
| 123 |
+ t.Fatalf("expected 2 volumes, got: %d", len(ls))
|
|
| 110 | 124 |
} |
| 111 | 125 |
} |
| 112 | 126 |
|
| 113 |
-func TestDecrement(t *testing.T) {
|
|
| 127 |
+func TestFilterByDriver(t *testing.T) {
|
|
| 128 |
+ volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
|
| 129 |
+ volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
|
| 130 |
+ defer volumedrivers.Unregister("fake")
|
|
| 131 |
+ defer volumedrivers.Unregister("noop")
|
|
| 114 | 132 |
s := New() |
| 115 |
- v := vt.NoopVolume{}
|
|
| 116 |
- s.Decrement(v) |
|
| 117 |
- if c := s.Count(v); c != 0 {
|
|
| 118 |
- t.Fatalf("Expected 0 volumes, got %v", c)
|
|
| 119 |
- } |
|
| 120 | 133 |
|
| 121 |
- s.Increment(v) |
|
| 122 |
- s.Increment(v) |
|
| 123 |
- s.Decrement(v) |
|
| 124 |
- if c := s.Count(v); c != 1 {
|
|
| 125 |
- t.Fatalf("Expected 1 volume, got %v", c)
|
|
| 134 |
+ if _, err := s.Create("fake1", "fake", nil); err != nil {
|
|
| 135 |
+ t.Fatal(err) |
|
| 126 | 136 |
} |
| 127 |
- |
|
| 128 |
- s.Decrement(v) |
|
| 129 |
- if c := s.Count(v); c != 0 {
|
|
| 130 |
- t.Fatalf("Expected 0 volumes, got %v", c)
|
|
| 137 |
+ if _, err := s.Create("fake2", "fake", nil); err != nil {
|
|
| 138 |
+ t.Fatal(err) |
|
| 131 | 139 |
} |
| 132 |
- |
|
| 133 |
- // Test counter cannot be negative. |
|
| 134 |
- s.Decrement(v) |
|
| 135 |
- if c := s.Count(v); c != 0 {
|
|
| 136 |
- t.Fatalf("Expected 0 volumes, got %v", c)
|
|
| 140 |
+ if _, err := s.Create("fake3", "noop", nil); err != nil {
|
|
| 141 |
+ t.Fatal(err) |
|
| 137 | 142 |
} |
| 138 |
-} |
|
| 139 |
- |
|
| 140 |
-func TestFilterByDriver(t *testing.T) {
|
|
| 141 |
- s := New() |
|
| 142 |
- |
|
| 143 |
- s.Increment(vt.NewFakeVolume("fake1"))
|
|
| 144 |
- s.Increment(vt.NewFakeVolume("fake2"))
|
|
| 145 |
- s.Increment(vt.NoopVolume{})
|
|
| 146 | 143 |
|
| 147 |
- if l := s.FilterByDriver("fake"); len(l) != 2 {
|
|
| 144 |
+ if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
|
|
| 148 | 145 |
t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
|
| 149 | 146 |
} |
| 150 | 147 |
|
| 151 |
- if l := s.FilterByDriver("noop"); len(l) != 1 {
|
|
| 148 |
+ if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
|
|
| 152 | 149 |
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
| 153 | 150 |
} |
| 154 | 151 |
} |
| ... | ... |
@@ -50,19 +50,55 @@ func (FakeVolume) Mount() (string, error) { return "fake", nil }
|
| 50 | 50 |
func (FakeVolume) Unmount() error { return nil }
|
| 51 | 51 |
|
| 52 | 52 |
// FakeDriver is a driver that generates fake volumes |
| 53 |
-type FakeDriver struct{}
|
|
| 53 |
+type FakeDriver struct {
|
|
| 54 |
+ name string |
|
| 55 |
+ vols map[string]volume.Volume |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// NewFakeDriver creates a new FakeDriver with the specified name |
|
| 59 |
+func NewFakeDriver(name string) volume.Driver {
|
|
| 60 |
+ return &FakeDriver{
|
|
| 61 |
+ name: name, |
|
| 62 |
+ vols: make(map[string]volume.Volume), |
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 54 | 65 |
|
| 55 | 66 |
// Name is the name of the driver |
| 56 |
-func (FakeDriver) Name() string { return "fake" }
|
|
| 67 |
+func (d *FakeDriver) Name() string { return d.name }
|
|
| 57 | 68 |
|
| 58 | 69 |
// Create initializes a fake volume. |
| 59 | 70 |
// It returns an error if the options include an "error" key with a message |
| 60 |
-func (FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
|
|
| 71 |
+func (d *FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
|
|
| 61 | 72 |
if opts != nil && opts["error"] != "" {
|
| 62 | 73 |
return nil, fmt.Errorf(opts["error"]) |
| 63 | 74 |
} |
| 64 |
- return NewFakeVolume(name), nil |
|
| 75 |
+ v := NewFakeVolume(name) |
|
| 76 |
+ d.vols[name] = v |
|
| 77 |
+ return v, nil |
|
| 65 | 78 |
} |
| 66 | 79 |
|
| 67 | 80 |
// Remove deletes a volume. |
| 68 |
-func (FakeDriver) Remove(v volume.Volume) error { return nil }
|
|
| 81 |
+func (d *FakeDriver) Remove(v volume.Volume) error {
|
|
| 82 |
+ if _, exists := d.vols[v.Name()]; !exists {
|
|
| 83 |
+ return fmt.Errorf("no such volume")
|
|
| 84 |
+ } |
|
| 85 |
+ delete(d.vols, v.Name()) |
|
| 86 |
+ return nil |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+// List lists the volumes |
|
| 90 |
+func (d *FakeDriver) List() ([]volume.Volume, error) {
|
|
| 91 |
+ var vols []volume.Volume |
|
| 92 |
+ for _, v := range d.vols {
|
|
| 93 |
+ vols = append(vols, v) |
|
| 94 |
+ } |
|
| 95 |
+ return vols, nil |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+// Get gets the volume |
|
| 99 |
+func (d *FakeDriver) Get(name string) (volume.Volume, error) {
|
|
| 100 |
+ if v, exists := d.vols[name]; exists {
|
|
| 101 |
+ return v, nil |
|
| 102 |
+ } |
|
| 103 |
+ return nil, fmt.Errorf("no such volume")
|
|
| 104 |
+} |
| ... | ... |
@@ -21,7 +21,11 @@ type Driver interface {
|
| 21 | 21 |
// Create makes a new volume with the given id. |
| 22 | 22 |
Create(name string, opts map[string]string) (Volume, error) |
| 23 | 23 |
// Remove deletes the volume. |
| 24 |
- Remove(Volume) error |
|
| 24 |
+ Remove(vol Volume) (err error) |
|
| 25 |
+ // List lists all the volumes the driver has |
|
| 26 |
+ List() ([]Volume, error) |
|
| 27 |
+ // Get retreives the volume with the requested name |
|
| 28 |
+ Get(name string) (Volume, error) |
|
| 25 | 29 |
} |
| 26 | 30 |
|
| 27 | 31 |
// Volume is a place to store data. It is backed by a specific driver, and can be mounted. |