Merge pull request #16534 from cpuguy83/make_volume_drivers_responsible

Move responsibility of ls/inspect to volume driver
This commit is contained in:
Phil Estes 2016-01-05 17:29:51 -05:00
commit 55137c19f4
30 changed files with 700 additions and 285 deletions

View File

@ -65,6 +65,9 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet { if !*quiet {
for _, warn := range volumes.Warnings {
fmt.Fprintln(cli.err, warn)
}
fmt.Fprintf(w, "DRIVER \tVOLUME NAME") fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
fmt.Fprintf(w, "\n") fmt.Fprintf(w, "\n")
} }
@ -102,7 +105,7 @@ func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher) return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
} }
// CmdVolumeCreate creates a new container from a given image. // CmdVolumeCreate creates a new volume.
// //
// Usage: docker volume create [OPTIONS] // Usage: docker volume create [OPTIONS]
func (cli *DockerCli) CmdVolumeCreate(args ...string) error { func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
@ -131,7 +134,7 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
return nil return nil
} }
// CmdVolumeRm removes one or more containers. // CmdVolumeRm removes one or more volumes.
// //
// Usage: docker volume rm VOLUME [VOLUME...] // Usage: docker volume rm VOLUME [VOLUME...]
func (cli *DockerCli) CmdVolumeRm(args ...string) error { func (cli *DockerCli) CmdVolumeRm(args ...string) error {
@ -140,6 +143,7 @@ func (cli *DockerCli) CmdVolumeRm(args ...string) error {
cmd.ParseFlags(args, true) cmd.ParseFlags(args, true)
var status = 0 var status = 0
for _, name := range cmd.Args() { for _, name := range cmd.Args() {
if err := cli.client.VolumeRemove(name); err != nil { if err := cli.client.VolumeRemove(name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err) fmt.Fprintf(cli.err, "%s\n", err)

View File

@ -8,7 +8,7 @@ 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, error) Volumes(filter string) ([]*types.Volume, []string, error)
VolumeInspect(name string) (*types.Volume, error) VolumeInspect(name string) (*types.Volume, error)
VolumeCreate(name, driverName string, VolumeCreate(name, driverName string,
opts map[string]string) (*types.Volume, error) opts map[string]string) (*types.Volume, error)

View File

@ -14,11 +14,11 @@ func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter
return err return err
} }
volumes, err := v.backend.Volumes(r.Form.Get("filters")) volumes, warnings, err := v.backend.Volumes(r.Form.Get("filters"))
if err != nil { if err != nil {
return err return err
} }
return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes}) return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes, Warnings: warnings})
} }
func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View File

@ -366,7 +366,8 @@ type Volume struct {
// VolumesListResponse contains the response for the remote API: // VolumesListResponse contains the response for the remote API:
// GET "/volumes" // GET "/volumes"
type VolumesListResponse struct { type VolumesListResponse struct {
Volumes []*Volume // Volumes is the list of volumes being returned Volumes []*Volume // Volumes is the list of volumes being returned
Warnings []string // Warnings is a list of warnings that occurred when getting the list from the volume drivers
} }
// VolumeCreateRequest contains the response for the remote API: // VolumeCreateRequest contains the response for the remote API:

View File

@ -10,7 +10,7 @@ import (
"github.com/docker/docker/layer" "github.com/docker/docker/layer"
"github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/volume" volumestore "github.com/docker/docker/volume/store"
"github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/runc/libcontainer/label"
) )
@ -162,17 +162,12 @@ func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]stri
v, err := daemon.volumes.Create(name, driverName, opts) v, err := daemon.volumes.Create(name, driverName, opts)
if err != nil { if err != nil {
if volumestore.IsNameConflict(err) {
return nil, derr.ErrorVolumeNameTaken.WithArgs(name)
}
return nil, err return nil, err
} }
// keep "docker run -v existing_volume:/foo --volume-driver other_driver" work daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
if (driverName != "" && v.DriverName() != driverName) || (driverName == "" && v.DriverName() != volume.DefaultDriverName) {
return nil, derr.ErrorVolumeNameTaken.WithArgs(name, v.DriverName())
}
if driverName == "" {
driverName = volume.DefaultDriverName
}
daemon.LogVolumeEvent(name, "create", map[string]string{"driver": driverName})
return volumeToAPIType(v), nil return volumeToAPIType(v), nil
} }

View File

@ -51,7 +51,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain
} }
} }
v, err := daemon.createVolume(name, volumeDriver, nil) v, err := daemon.volumes.CreateWithRef(name, volumeDriver, container.ID, nil)
if err != nil { if err != nil {
return err return err
} }

View File

@ -42,7 +42,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain
// 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.createVolume(mp.Name, volumeDriver, nil) v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1487,10 +1487,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
} }
volumedrivers.Register(volumesDriver, volumesDriver.Name()) volumedrivers.Register(volumesDriver, volumesDriver.Name())
s := store.New() return store.New(), nil
s.AddAll(volumesDriver.List())
return s, nil
} }
// AuthenticateToRegistry checks the validity of credentials in authConfig // AuthenticateToRegistry checks the validity of credentials in authConfig

View File

@ -151,6 +151,7 @@ func (daemon *Daemon) VolumeRm(name string) error {
if err != nil { if err != nil {
return err return err
} }
if err := daemon.volumes.Remove(v); err != nil { if err := daemon.volumes.Remove(v); err != nil {
if volumestore.IsInUse(err) { if volumestore.IsInUse(err) {
return derr.ErrorCodeRmVolumeInUse.WithArgs(err) return derr.ErrorCodeRmVolumeInUse.WithArgs(err)

View File

@ -392,24 +392,27 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
// Volumes lists known volumes, using the filter to restrict the range // Volumes lists known volumes, using the filter to restrict the range
// of volumes returned. // of volumes returned.
func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) { func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) {
var volumesOut []*types.Volume var volumesOut []*types.Volume
volFilters, err := filters.FromParam(filter) volFilters, err := filters.FromParam(filter)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
filterUsed := volFilters.Include("dangling") && filterUsed := volFilters.Include("dangling") &&
(volFilters.ExactMatch("dangling", "true") || volFilters.ExactMatch("dangling", "1")) (volFilters.ExactMatch("dangling", "true") || volFilters.ExactMatch("dangling", "1"))
volumes := daemon.volumes.List() volumes, warnings, err := daemon.volumes.List()
if err != nil {
return nil, nil, err
}
if filterUsed {
volumes = daemon.volumes.FilterByUsed(volumes)
}
for _, v := range volumes { for _, v := range volumes {
if filterUsed && daemon.volumes.Count(v) > 0 {
continue
}
volumesOut = append(volumesOut, volumeToAPIType(v)) volumesOut = append(volumesOut, volumeToAPIType(v))
} }
return volumesOut, nil return volumesOut, warnings, 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) {

View File

@ -11,10 +11,11 @@ import (
func (daemon *Daemon) prepareMountPoints(container *container.Container) error { func (daemon *Daemon) prepareMountPoints(container *container.Container) error {
for _, config := range container.MountPoints { for _, config := range container.MountPoints {
if len(config.Driver) > 0 { if len(config.Driver) > 0 {
v, err := daemon.createVolume(config.Name, config.Driver, nil) v, err := daemon.volumes.GetWithRef(config.Name, config.Driver, container.ID)
if err != nil { if err != nil {
return err return err
} }
config.Volume = v config.Volume = v
} }
} }
@ -27,10 +28,10 @@ func (daemon *Daemon) removeMountPoints(container *container.Container, rm bool)
if m.Volume == nil { if m.Volume == nil {
continue continue
} }
daemon.volumes.Decrement(m.Volume) daemon.volumes.Dereference(m.Volume, container.ID)
if rm { if rm {
err := daemon.volumes.Remove(m.Volume) err := daemon.volumes.Remove(m.Volume)
// ErrVolumeInUse is ignored 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"

View File

@ -32,16 +32,6 @@ func volumeToAPIType(v volume.Volume) *types.Volume {
} }
} }
// createVolume creates a volume.
func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
v, err := daemon.volumes.Create(name, driverName, opts)
if err != nil {
return nil, err
}
daemon.volumes.Increment(v)
return v, nil
}
// 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)
@ -103,7 +93,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
} }
if len(cp.Source) == 0 { if len(cp.Source) == 0 {
v, err := daemon.createVolume(cp.Name, cp.Driver, nil) v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID)
if err != nil { if err != nil {
return err return err
} }
@ -128,7 +118,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
if len(bind.Name) > 0 && len(bind.Driver) > 0 { if len(bind.Name) > 0 && len(bind.Driver) > 0 {
// create the volume // create the volume
v, err := daemon.createVolume(bind.Name, bind.Driver, nil) v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil)
if err != nil { if err != nil {
return err return err
} }
@ -153,7 +143,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
for _, m := range mountPoints { for _, m := range mountPoints {
if m.BackwardsCompatible() { if m.BackwardsCompatible() {
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
daemon.volumes.Decrement(mp.Volume) daemon.volumes.Dereference(mp.Volume, container.ID)
} }
} }
} }

View File

@ -908,7 +908,7 @@ var (
// trying to create a volume that has existed using different driver. // trying to create a volume that has existed using different driver.
ErrorVolumeNameTaken = errcode.Register(errGroup, errcode.ErrorDescriptor{ ErrorVolumeNameTaken = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "VOLUME_NAME_TAKEN", Value: "VOLUME_NAME_TAKEN",
Message: "A volume named %q already exists with the %q driver. Choose a different volume name.", Message: "A volume named %s already exists. Choose a different volume name.",
Description: "An attempt to create a volume using a driver but the volume already exists with a different driver", Description: "An attempt to create a volume using a driver but the volume already exists with a different driver",
HTTPStatusCode: http.StatusInternalServerError, HTTPStatusCode: http.StatusInternalServerError,
}) })

View File

@ -1733,8 +1733,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartRmVolumeInUse(c *check.C) {
c.Assert(s.d.Restart(), check.IsNil) c.Assert(s.d.Restart(), check.IsNil)
out, err = s.d.Cmd("volume", "rm", "test") out, err = s.d.Cmd("volume", "rm", "test")
c.Assert(err, check.Not(check.IsNil), check.Commentf("should not be able to remove in use volume after daemon restart")) c.Assert(err, check.NotNil, check.Commentf("should not be able to remove in use volume after daemon restart"))
c.Assert(strings.Contains(out, "in use"), check.Equals, true) c.Assert(out, checker.Contains, "in use")
} }
func (s *DockerDaemonSuite) TestDaemonRestartLocalVolumes(c *check.C) { func (s *DockerDaemonSuite) TestDaemonRestartLocalVolumes(c *check.C) {

View File

@ -32,6 +32,8 @@ type eventCounter struct {
mounts int mounts int
unmounts int unmounts int
paths int paths int
lists int
gets int
} }
type DockerExternalVolumeSuite struct { type DockerExternalVolumeSuite struct {
@ -64,6 +66,12 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
Err string `json:",omitempty"` Err string `json:",omitempty"`
} }
type vol struct {
Name string
Mountpoint string
}
var volList []vol
read := func(b io.ReadCloser) (pluginRequest, error) { read := func(b io.ReadCloser) (pluginRequest, error) {
defer b.Close() defer b.Close()
var pr pluginRequest var pr pluginRequest
@ -93,29 +101,61 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
s.ec.creations++ s.ec.creations++
_, err := read(r.Body)
if err != nil {
send(w, err)
return
}
send(w, nil)
})
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
s.ec.removals++
pr, err := read(r.Body) pr, err := read(r.Body)
if err != nil { if err != nil {
send(w, err) send(w, err)
return return
} }
volList = append(volList, vol{Name: pr.Name})
send(w, nil)
})
mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
s.ec.lists++
send(w, map[string][]vol{"Volumes": volList})
})
mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
s.ec.gets++
pr, err := read(r.Body)
if err != nil {
send(w, err)
return
}
for _, v := range volList {
if v.Name == pr.Name {
v.Mountpoint = hostVolumePath(pr.Name)
send(w, map[string]vol{"Volume": v})
return
}
}
send(w, `{"Err": "no such volume"}`)
})
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
s.ec.removals++
pr, err := read(r.Body)
if err != nil {
send(w, err)
return
}
if err := os.RemoveAll(hostVolumePath(pr.Name)); err != nil { if err := os.RemoveAll(hostVolumePath(pr.Name)); err != nil {
send(w, &pluginResp{Err: err.Error()}) send(w, &pluginResp{Err: err.Error()})
return return
} }
for i, v := range volList {
if v.Name == pr.Name {
if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
send(w, fmt.Sprintf(`{"Err": "%v"}`, err))
return
}
volList = append(volList[:i], volList[i+1:]...)
break
}
}
send(w, nil) send(w, nil)
}) })
@ -128,8 +168,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
return return
} }
p := hostVolumePath(pr.Name) p := hostVolumePath(pr.Name)
send(w, &pluginResp{Mountpoint: p})
fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
}) })
mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
@ -164,7 +203,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
return return
} }
fmt.Fprintln(w, nil) send(w, nil)
}) })
err := os.MkdirAll("/etc/docker/plugins", 0755) err := os.MkdirAll("/etc/docker/plugins", 0755)
@ -287,8 +326,8 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamedCheckBindLocalV
// Make sure a request to use a down driver doesn't block other requests // Make sure a request to use a down driver doesn't block other requests
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) { func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) {
specPath := "/etc/docker/plugins/down-driver.spec" specPath := "/etc/docker/plugins/down-driver.spec"
err := ioutil.WriteFile("/etc/docker/plugins/down-driver.spec", []byte("tcp://127.0.0.7:9999"), 0644) err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644)
c.Assert(err, checker.IsNil) c.Assert(err, check.IsNil)
defer os.RemoveAll(specPath) defer os.RemoveAll(specPath)
chCmd1 := make(chan struct{}) chCmd1 := make(chan struct{})
@ -316,10 +355,11 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *
case err := <-chCmd2: case err := <-chCmd2:
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
cmd2.Process.Kill() cmd2.Process.Kill()
c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
} }
} }
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) { func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) {
err := s.d.StartWithBusybox() err := s.d.StartWithBusybox()
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
@ -371,3 +411,24 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c
c.Assert(mounts[0].Name, checker.Equals, "foo") c.Assert(mounts[0].Name, checker.Equals, "foo")
c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver") c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver")
} }
func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverList(c *check.C) {
dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc")
out, _ := dockerCmd(c, "volume", "ls")
ls := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out))
vol := strings.Fields(ls[len(ls)-1])
c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol))
c.Assert(vol[0], check.Equals, "test-external-volume-driver")
c.Assert(vol[1], check.Equals, "abc")
c.Assert(s.ec.lists, check.Equals, 1)
}
func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverGet(c *check.C) {
out, _, err := dockerCmdWithError("volume", "inspect", "dummy")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(s.ec.gets, check.Equals, 1)
c.Assert(out, checker.Contains, "No such volume")
}

View File

@ -4,9 +4,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/integration/checker"
"github.com/docker/docker/volume"
"github.com/go-check/check" "github.com/go-check/check"
) )
@ -25,8 +23,7 @@ func (s *DockerSuite) TestVolumeCliCreateOptionConflict(c *check.C) {
dockerCmd(c, "volume", "create", "--name=test") dockerCmd(c, "volume", "create", "--name=test")
out, _, err := dockerCmdWithError("volume", "create", "--name", "test", "--driver", "nosuchdriver") out, _, err := dockerCmdWithError("volume", "create", "--name", "test", "--driver", "nosuchdriver")
c.Assert(err, check.NotNil, check.Commentf("volume create exception name already in use with another driver")) c.Assert(err, check.NotNil, check.Commentf("volume create exception name already in use with another driver"))
stderr := derr.ErrorVolumeNameTaken.WithArgs("test", volume.DefaultDriverName).Error() c.Assert(out, checker.Contains, "A volume named test already exists")
c.Assert(strings.Contains(out, strings.TrimPrefix(stderr, "volume name taken: ")), check.Equals, true)
out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Driver }}'", "test") out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Driver }}'", "test")
_, _, err = dockerCmdWithError("volume", "create", "--name", "test", "--driver", strings.TrimSpace(out)) _, _, err = dockerCmdWithError("volume", "create", "--name", "test", "--driver", strings.TrimSpace(out))

View File

@ -25,6 +25,38 @@ func newLocalRegistry() localRegistry {
return localRegistry{} return localRegistry{}
} }
// Scan scans all the plugin paths and returns all the names it found
func Scan() ([]string, error) {
var names []string
if err := filepath.Walk(socketsPath, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return nil
}
if fi.Mode()&os.ModeSocket != 0 {
name := strings.TrimSuffix(fi.Name(), filepath.Ext(fi.Name()))
names = append(names, name)
}
return nil
}); err != nil {
return nil, err
}
for _, path := range specsPaths {
if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() {
return nil
}
name := strings.TrimSuffix(fi.Name(), filepath.Ext(fi.Name()))
names = append(names, name)
return nil
}); err != nil {
return nil, err
}
}
return names, nil
}
// Plugin returns the plugin registered with the given name (or returns an error). // Plugin returns the plugin registered with the given name (or returns an error).
func (l *localRegistry) Plugin(name string) (*Plugin, error) { func (l *localRegistry) Plugin(name string) (*Plugin, error) {
socketpaths := pluginPaths(socketsPath, name, ".sock") socketpaths := pluginPaths(socketsPath, name, ".sock")

View File

@ -108,6 +108,15 @@ func (p *Plugin) activateWithLock() error {
return nil return nil
} }
func (p *Plugin) implements(kind string) bool {
for _, driver := range p.Manifest.Implements {
if driver == kind {
return true
}
}
return false
}
func load(name string) (*Plugin, error) { func load(name string) (*Plugin, error) {
return loadWithRetry(name, true) return loadWithRetry(name, true)
} }
@ -166,11 +175,9 @@ func Get(name, imp string) (*Plugin, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, driver := range pl.Manifest.Implements { if pl.implements(imp) {
logrus.Debugf("%s implements: %s", name, driver) logrus.Debugf("%s implements: %s", name, imp)
if driver == imp { return pl, nil
return pl, nil
}
} }
return nil, ErrNotImplements return nil, ErrNotImplements
} }
@ -179,3 +186,37 @@ func Get(name, imp string) (*Plugin, error) {
func Handle(iface string, fn func(string, *Client)) { func Handle(iface string, fn func(string, *Client)) {
extpointHandlers[iface] = fn extpointHandlers[iface] = fn
} }
// GetAll returns all the plugins for the specified implementation
func GetAll(imp string) ([]*Plugin, error) {
pluginNames, err := Scan()
if err != nil {
return nil, err
}
type plLoad struct {
pl *Plugin
err error
}
chPl := make(chan plLoad, len(pluginNames))
for _, name := range pluginNames {
go func(name string) {
pl, err := loadWithRetry(name, false)
chPl <- plLoad{pl, err}
}(name)
}
var out []*Plugin
for i := 0; i < len(pluginNames); i++ {
pl := <-chPl
if pl.err != nil {
logrus.Error(err)
continue
}
if pl.pl.implements(imp) {
out = append(out, pl.pl)
}
}
return out, nil
}

View File

@ -26,6 +26,38 @@ func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
return a.proxy.Remove(v.Name()) return a.proxy.Remove(v.Name())
} }
func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
ls, err := a.proxy.List()
if err != nil {
return nil, err
}
var out []volume.Volume
for _, vp := range ls {
out = append(out, &volumeAdapter{
proxy: a.proxy,
name: vp.Name,
driverName: a.name,
eMount: vp.Mountpoint,
})
}
return out, nil
}
func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
v, err := a.proxy.Get(name)
if err != nil {
return nil, err
}
return &volumeAdapter{
proxy: a.proxy,
name: v.Name,
driverName: a.Name(),
eMount: v.Mountpoint,
}, nil
}
type volumeAdapter struct { type volumeAdapter struct {
proxy *volumeDriverProxy proxy *volumeDriverProxy
name string name string

View File

@ -15,6 +15,8 @@ import (
var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver)} var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver)}
const extName = "VolumeDriver"
// NewVolumeDriver returns a driver has the given name mapped on the given client. // NewVolumeDriver returns a driver has the given name mapped on the given client.
func NewVolumeDriver(name string, c client) volume.Driver { func NewVolumeDriver(name string, c client) volume.Driver {
proxy := &volumeDriverProxy{c} proxy := &volumeDriverProxy{c}
@ -22,6 +24,7 @@ func NewVolumeDriver(name string, c client) volume.Driver {
} }
type opts map[string]string type opts map[string]string
type list []*proxyVolume
// volumeDriver defines the available functions that volume plugins must implement. // volumeDriver defines the available functions that volume plugins must implement.
// This interface is only defined to generate the proxy objects. // This interface is only defined to generate the proxy objects.
@ -37,6 +40,10 @@ type volumeDriver interface {
Mount(name string) (mountpoint string, err error) Mount(name string) (mountpoint string, err error)
// Unmount the given volume // Unmount the given volume
Unmount(name string) (err error) Unmount(name string) (err error)
// List lists all the volumes known to the driver
List() (volumes list, err error)
// Get retreives the volume with the requested name
Get(name string) (volume *proxyVolume, err error)
} }
type driverExtpoint struct { type driverExtpoint struct {
@ -82,7 +89,7 @@ func Lookup(name string) (volume.Driver, error) {
if ok { if ok {
return ext, nil return ext, nil
} }
pl, err := plugins.Get(name, "VolumeDriver") pl, err := plugins.Get(name, extName)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err) return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
} }
@ -116,3 +123,30 @@ func GetDriverList() []string {
} }
return driverList return driverList
} }
// GetAllDrivers lists all the registered drivers
func GetAllDrivers() ([]volume.Driver, error) {
plugins, err := plugins.GetAll(extName)
if err != nil {
return nil, err
}
var ds []volume.Driver
drivers.Lock()
defer drivers.Unlock()
for _, d := range drivers.extensions {
ds = append(ds, d)
}
for _, p := range plugins {
ext, ok := drivers.extensions[p.Name]
if ok {
continue
}
ext = NewVolumeDriver(p.Name, p.Client)
drivers.extensions[p.Name] = ext
ds = append(ds, ext)
}
return ds, nil
}

View File

@ -11,7 +11,8 @@ func TestGetDriver(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("Expected error, was nil") t.Fatal("Expected error, was nil")
} }
Register(volumetestutils.FakeDriver{}, "fake")
Register(volumetestutils.NewFakeDriver("fake"), "fake")
d, err := GetDriver("fake") d, err := GetDriver("fake")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -149,3 +149,59 @@ func (pp *volumeDriverProxy) Unmount(name string) (err error) {
return return
} }
type volumeDriverProxyListRequest struct {
}
type volumeDriverProxyListResponse struct {
Volumes list
Err string
}
func (pp *volumeDriverProxy) List() (volumes list, err error) {
var (
req volumeDriverProxyListRequest
ret volumeDriverProxyListResponse
)
if err = pp.Call("VolumeDriver.List", req, &ret); err != nil {
return
}
volumes = ret.Volumes
if ret.Err != "" {
err = errors.New(ret.Err)
}
return
}
type volumeDriverProxyGetRequest struct {
Name string
}
type volumeDriverProxyGetResponse struct {
Volume *proxyVolume
Err string
}
func (pp *volumeDriverProxy) Get(name string) (volume *proxyVolume, err error) {
var (
req volumeDriverProxyGetRequest
ret volumeDriverProxyGetResponse
)
req.Name = name
if err = pp.Call("VolumeDriver.Get", req, &ret); err != nil {
return
}
volume = ret.Volume
if ret.Err != "" {
err = errors.New(ret.Err)
}
return
}

View File

@ -42,6 +42,16 @@ func TestVolumeRequestError(t *testing.T) {
fmt.Fprintln(w, `{"Err": "Unknown volume"}`) fmt.Fprintln(w, `{"Err": "Unknown volume"}`)
}) })
mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, `{"Err": "Cannot list volumes"}`)
})
mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, `{"Err": "Cannot get volume"}`)
})
u, _ := url.Parse(server.URL) u, _ := url.Parse(server.URL)
client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true}) client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
if err != nil { if err != nil {
@ -93,4 +103,20 @@ func TestVolumeRequestError(t *testing.T) {
if !strings.Contains(err.Error(), "Unknown volume") { if !strings.Contains(err.Error(), "Unknown volume") {
t.Fatalf("Unexpected error: %v\n", err) t.Fatalf("Unexpected error: %v\n", err)
} }
_, err = driver.List()
if err == nil {
t.Fatal("Expected error, was nil")
}
if !strings.Contains(err.Error(), "Cannot list volumes") {
t.Fatalf("Unexpected error: %v\n", err)
}
_, err = driver.Get("volume")
if err == nil {
t.Fatal("Expected error, was nil")
}
if !strings.Contains(err.Error(), "Cannot get volume") {
t.Fatalf("Unexpected error: %v\n", err)
}
} }

View File

@ -82,12 +82,12 @@ type Root struct {
} }
// List lists all the volumes // List lists all the volumes
func (r *Root) List() []volume.Volume { func (r *Root) List() ([]volume.Volume, error) {
var ls []volume.Volume var ls []volume.Volume
for _, v := range r.volumes { for _, v := range r.volumes {
ls = append(ls, v) ls = append(ls, v)
} }
return ls return ls, nil
} }
// DataPath returns the constructed path of this volume. // DataPath returns the constructed path of this volume.

View File

@ -43,7 +43,7 @@ func TestRemove(t *testing.T) {
t.Fatal("volume dir not removed") t.Fatal("volume dir not removed")
} }
if len(r.List()) != 0 { if l, _ := r.List(); len(l) != 0 {
t.Fatal("expected there to be no volumes") t.Fatal("expected there to be no volumes")
} }
} }

View File

@ -1,6 +1,9 @@
package store package store
import "errors" import (
"errors"
"strings"
)
var ( var (
// errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container // 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 (
errNoSuchVolume = errors.New("no such volume") errNoSuchVolume = errors.New("no such volume")
// errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform // errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
errInvalidName = errors.New("volume name is not valid on this platform") errInvalidName = errors.New("volume name is not valid on this platform")
// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver
errNameConflict = errors.New("conflict: volume name must be unique")
) )
// OpErr is the error type returned by functions in the store package. It describes // OpErr is the error type returned by functions in the store package. It describes
@ -20,6 +25,8 @@ type OpErr struct {
Op string Op string
// Name is the name of the resource being requested for this op, typically the volume name or the driver name. // Name is the name of the resource being requested for this op, typically the volume name or the driver name.
Name string Name string
// Refs is the list of references associated with the resource.
Refs []string
} }
// Error satisfies the built-in error interface type. // Error satisfies the built-in error interface type.
@ -33,6 +40,9 @@ func (e *OpErr) Error() string {
} }
s = s + ": " + e.Err.Error() s = s + ": " + e.Err.Error()
if len(e.Refs) > 0 {
s = s + " - " + "[" + strings.Join(e.Refs, ", ") + "]"
}
return s return s
} }
@ -47,6 +57,12 @@ func IsNotExist(err error) bool {
return isErr(err, errNoSuchVolume) return isErr(err, errNoSuchVolume)
} }
// IsNameConflict returns a boolean indicating whether the error indicates that a
// volume name is already taken
func IsNameConflict(err error) bool {
return isErr(err, errNameConflict)
}
func isErr(err error, expected error) bool { func isErr(err error, expected error) bool {
switch pe := err.(type) { switch pe := err.(type) {
case nil: case nil:

View File

@ -13,66 +13,153 @@ import (
// reference counting of volumes in the system. // reference counting of volumes in the system.
func New() *VolumeStore { func New() *VolumeStore {
return &VolumeStore{ return &VolumeStore{
vols: make(map[string]*volumeCounter),
locks: &locker.Locker{}, locks: &locker.Locker{},
names: make(map[string]string),
refs: make(map[string][]string),
} }
} }
func (s *VolumeStore) get(name string) (*volumeCounter, bool) { func (s *VolumeStore) getNamed(name string) (string, bool) {
s.globalLock.Lock() s.globalLock.Lock()
vc, exists := s.vols[name] driverName, exists := s.names[name]
s.globalLock.Unlock() s.globalLock.Unlock()
return vc, exists return driverName, exists
} }
func (s *VolumeStore) set(name string, vc *volumeCounter) { func (s *VolumeStore) setNamed(name, driver, ref string) {
s.globalLock.Lock() s.globalLock.Lock()
s.vols[name] = vc s.names[name] = driver
if len(ref) > 0 {
s.refs[name] = append(s.refs[name], ref)
}
s.globalLock.Unlock() s.globalLock.Unlock()
} }
func (s *VolumeStore) remove(name string) { func (s *VolumeStore) purge(name string) {
s.globalLock.Lock() s.globalLock.Lock()
delete(s.vols, name) delete(s.names, name)
delete(s.refs, name)
s.globalLock.Unlock() s.globalLock.Unlock()
} }
// 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
type VolumeStore struct { type VolumeStore struct {
vols map[string]*volumeCounter
locks *locker.Locker locks *locker.Locker
globalLock sync.Mutex globalLock sync.Mutex
// names stores the volume name -> driver name relationship.
// This is used for making lookups faster so we don't have to probe all drivers
names map[string]string
// refs stores the volume name and the list of things referencing it
refs map[string][]string
} }
// volumeCounter keeps track of references to a volume // List proxies to all registered volume drivers to get the full list of volumes
type volumeCounter struct { // If a driver returns a volume that has name which conflicts with a another volume from a different driver,
volume.Volume // the first volume is chosen and the conflicting volume is dropped.
count uint func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
} vols, warnings, err := s.list()
if err != nil {
// AddAll adds a list of volumes to the store return nil, nil, &OpErr{Err: err, Op: "list"}
func (s *VolumeStore) AddAll(vols []volume.Volume) {
for _, v := range vols {
s.vols[normaliseVolumeName(v.Name())] = &volumeCounter{v, 0}
} }
var out []volume.Volume
for _, v := range vols {
name := normaliseVolumeName(v.Name())
s.locks.Lock(name)
driverName, exists := s.getNamed(name)
if !exists {
s.setNamed(name, v.DriverName(), "")
}
if exists && driverName != v.DriverName() {
logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), driverName, v.DriverName())
s.locks.Unlock(v.Name())
continue
}
out = append(out, v)
s.locks.Unlock(v.Name())
}
return out, warnings, nil
} }
// Create tries to find an existing volume with the given name or create a new one from the passed in driver // list goes through each volume driver and asks for its list of volumes.
func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
drivers, err := volumedrivers.GetAllDrivers()
if err != nil {
return nil, nil, err
}
var (
ls []volume.Volume
warnings []string
)
type vols struct {
vols []volume.Volume
err error
}
chVols := make(chan vols, len(drivers))
for _, vd := range drivers {
go func(d volume.Driver) {
vs, err := d.List()
if err != nil {
chVols <- vols{err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
return
}
chVols <- vols{vols: vs}
}(vd)
}
for i := 0; i < len(drivers); i++ {
vs := <-chVols
if vs.err != nil {
warnings = append(warnings, vs.err.Error())
logrus.Warn(vs.err)
continue
}
ls = append(ls, vs.vols...)
}
return ls, warnings, nil
}
// CreateWithRef creates a volume with the given name and driver and stores the ref
// This is just like Create() except we store the reference while holding the lock.
// This ensures there's no race between creating a volume and then storing a reference.
func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[string]string) (volume.Volume, error) {
name = normaliseVolumeName(name)
s.locks.Lock(name)
defer s.locks.Unlock(name)
v, err := s.create(name, driverName, opts)
if err != nil {
return nil, &OpErr{Err: err, Name: name, Op: "create"}
}
s.setNamed(name, v.DriverName(), ref)
return v, nil
}
// Create creates a volume with the given name and driver.
func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) { func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
name = normaliseVolumeName(name) name = normaliseVolumeName(name)
s.locks.Lock(name) s.locks.Lock(name)
defer s.locks.Unlock(name) defer s.locks.Unlock(name)
if vc, exists := s.get(name); exists { v, err := s.create(name, driverName, opts)
v := vc.Volume
return v, nil
}
vd, err := volumedrivers.GetDriver(driverName)
if err != nil { if err != nil {
return nil, &OpErr{Err: err, Name: driverName, Op: "create"} return nil, &OpErr{Err: err, Name: name, Op: "create"}
} }
s.setNamed(name, v.DriverName(), "")
return v, nil
}
// create asks the given driver to create a volume with the name/opts.
// If a volume with the name is already known, it will ask the stored driver for the volume.
// If the passed in driver name does not match the driver name which is stored for the given volume name, an error is returned.
// It is expected that callers of this function hold any neccessary locks.
func (s *VolumeStore) create(name, driverName string, opts map[string]string) (volume.Volume, error) {
// Validate the name in a platform-specific manner // Validate the name in a platform-specific manner
valid, err := volume.IsVolumeNameValid(name) valid, err := volume.IsVolumeNameValid(name)
if err != nil { if err != nil {
@ -82,12 +169,45 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v
return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"} return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
} }
v, err := vd.Create(name, opts) vdName, exists := s.getNamed(name)
if exists {
if vdName != driverName && driverName != "" && driverName != volume.DefaultDriverName {
return nil, errNameConflict
}
driverName = vdName
}
logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
vd, err := volumedrivers.GetDriver(driverName)
if err != nil { if err != nil {
return nil, &OpErr{Op: "create", Name: name, Err: err} return nil, &OpErr{Op: "create", Name: name, Err: err}
} }
s.set(name, &volumeCounter{v, 0}) if v, err := vd.Get(name); err == nil {
return v, nil
}
return vd.Create(name, opts)
}
// 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 existance of a volume and adding a reference for it
func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, error) {
name = normaliseVolumeName(name)
s.locks.Lock(name)
defer s.locks.Unlock(name)
vd, err := volumedrivers.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(name, v.DriverName(), ref)
return v, nil return v, nil
} }
@ -97,120 +217,116 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
s.locks.Lock(name) s.locks.Lock(name)
defer s.locks.Unlock(name) defer s.locks.Unlock(name)
vc, exists := s.get(name) v, err := s.getVolume(name)
if !exists { if err != nil {
return nil, &OpErr{Err: errNoSuchVolume, Name: name, Op: "get"} return nil, &OpErr{Err: err, Name: name, Op: "get"}
} }
return vc.Volume, nil return v, nil
} }
// Remove removes the requested volume. A volume is not removed if the usage count is > 0 // get requests the volume, if the driver info is stored it just access that driver,
// if the driver is unknown it probes all drivers until it finds the first volume with that name.
// it is expected that callers of this function hold any neccessary locks
func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
logrus.Debugf("Getting volume reference for name: %s", name)
if vdName, exists := s.names[name]; exists {
vd, err := volumedrivers.GetDriver(vdName)
if err != nil {
return nil, err
}
return vd.Get(name)
}
logrus.Debugf("Probing all drivers for volume with name: %s", name)
drivers, err := volumedrivers.GetAllDrivers()
if err != nil {
return nil, err
}
for _, d := range drivers {
v, err := d.Get(name)
if err != nil {
continue
}
return v, nil
}
return nil, errNoSuchVolume
}
// 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(v volume.Volume) error {
name := normaliseVolumeName(v.Name()) name := normaliseVolumeName(v.Name())
s.locks.Lock(name) s.locks.Lock(name)
defer s.locks.Unlock(name) defer s.locks.Unlock(name)
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name) if refs, exists := s.refs[name]; exists && len(refs) > 0 {
vc, exists := s.get(name) return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: refs}
if !exists {
return &OpErr{Err: errNoSuchVolume, Name: name, Op: "remove"}
} }
if vc.count > 0 { vd, err := volumedrivers.GetDriver(v.DriverName())
return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove"}
}
vd, err := volumedrivers.GetDriver(vc.DriverName())
if err != nil { if err != nil {
return &OpErr{Err: err, Name: vc.DriverName(), Op: "remove"} return &OpErr{Err: err, Name: vd.Name(), Op: "remove"}
} }
if err := vd.Remove(vc.Volume); err != nil {
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
if err := vd.Remove(v); err != nil {
return &OpErr{Err: err, Name: name, Op: "remove"} return &OpErr{Err: err, Name: name, Op: "remove"}
} }
s.remove(name) s.purge(name)
return nil return nil
} }
// Increment increments the usage count of the passed in volume by 1 // Dereference removes the specified reference to the volume
func (s *VolumeStore) Increment(v volume.Volume) { func (s *VolumeStore) Dereference(v volume.Volume, ref string) {
name := normaliseVolumeName(v.Name()) s.locks.Lock(v.Name())
s.locks.Lock(name) defer s.locks.Unlock(v.Name())
defer s.locks.Unlock(name)
logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
vc, exists := s.get(name)
if !exists {
s.set(name, &volumeCounter{v, 1})
return
}
vc.count++
}
// Decrement decrements the usage count of the passed in volume by 1
func (s *VolumeStore) Decrement(v volume.Volume) {
name := normaliseVolumeName(v.Name())
s.locks.Lock(name)
defer s.locks.Unlock(name)
logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
vc, exists := s.get(name)
if !exists {
return
}
if vc.count == 0 {
return
}
vc.count--
}
// Count returns the usage count of the passed in volume
func (s *VolumeStore) Count(v volume.Volume) uint {
name := normaliseVolumeName(v.Name())
s.locks.Lock(name)
defer s.locks.Unlock(name)
vc, exists := s.get(name)
if !exists {
return 0
}
return vc.count
}
// List returns all the available volumes
func (s *VolumeStore) List() []volume.Volume {
s.globalLock.Lock() s.globalLock.Lock()
defer s.globalLock.Unlock() refs, exists := s.refs[v.Name()]
var ls []volume.Volume if !exists {
for _, vc := range s.vols { return
ls = append(ls, vc.Volume)
} }
return ls
for i, r := range refs {
if r == ref {
s.refs[v.Name()] = append(s.refs[v.Name()][:i], s.refs[v.Name()][i+1:]...)
}
}
s.globalLock.Unlock()
} }
// FilterByDriver returns the available volumes filtered by driver name // FilterByDriver returns the available volumes filtered by driver name
func (s *VolumeStore) FilterByDriver(name string) []volume.Volume { func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
return s.filter(byDriver(name)) vd, err := volumedrivers.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"}
}
return ls, nil
}
// FilterByUsed returns the available volumes filtered by if they are not in use
func (s *VolumeStore) FilterByUsed(vols []volume.Volume) []volume.Volume {
return s.filter(vols, func(v volume.Volume) bool {
s.locks.Lock(v.Name())
defer s.locks.Unlock(v.Name())
return len(s.refs[v.Name()]) == 0
})
} }
// filterFunc defines a function to allow filter volumes in the store // filterFunc defines a function to allow filter volumes in the store
type filterFunc func(vol volume.Volume) bool type filterFunc func(vol volume.Volume) bool
// byDriver generates a filterFunc to filter volumes by their driver name
func byDriver(name string) filterFunc {
return func(vol volume.Volume) bool {
return vol.DriverName() == name
}
}
// filter returns the available volumes filtered by a filterFunc function // filter returns the available volumes filtered by a filterFunc function
func (s *VolumeStore) filter(f filterFunc) []volume.Volume { func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume {
s.globalLock.Lock()
defer s.globalLock.Unlock()
var ls []volume.Volume var ls []volume.Volume
for _, vc := range s.vols { for _, v := range vols {
if f(vc.Volume) { if f(v) {
ls = append(ls, vc.Volume) ls = append(ls, v)
} }
} }
return ls return ls

View File

@ -2,42 +2,16 @@ package store
import ( import (
"errors" "errors"
"strings"
"testing" "testing"
"github.com/docker/docker/volume"
"github.com/docker/docker/volume/drivers" "github.com/docker/docker/volume/drivers"
vt "github.com/docker/docker/volume/testutils" vt "github.com/docker/docker/volume/testutils"
) )
func TestList(t *testing.T) {
volumedrivers.Register(vt.FakeDriver{}, "fake")
s := New()
s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
l := s.List()
if len(l) != 2 {
t.Fatalf("Expected 2 volumes in the store, got %v: %v", len(l), l)
}
}
func TestGet(t *testing.T) {
volumedrivers.Register(vt.FakeDriver{}, "fake")
s := New()
s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
v, err := s.Get("fake1")
if err != nil {
t.Fatal(err)
}
if v.Name() != "fake1" {
t.Fatalf("Expected fake1 volume, got %v", v)
}
if _, err := s.Get("fake4"); !IsNotExist(err) {
t.Fatalf("Expected IsNotExist error, got %v", err)
}
}
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
volumedrivers.Register(vt.FakeDriver{}, "fake") volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
defer volumedrivers.Unregister("fake")
s := New() s := New()
v, err := s.Create("fake1", "fake", nil) v, err := s.Create("fake1", "fake", nil)
if err != nil { if err != nil {
@ -46,7 +20,7 @@ func TestCreate(t *testing.T) {
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.List(); 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)
} }
@ -62,93 +36,90 @@ func TestCreate(t *testing.T) {
} }
func TestRemove(t *testing.T) { func TestRemove(t *testing.T) {
volumedrivers.Register(vt.FakeDriver{}, "fake") volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
defer volumedrivers.Unregister("fake")
defer volumedrivers.Unregister("noop")
s := New() s := New()
if err := s.Remove(vt.NoopVolume{}); !IsNotExist(err) {
t.Fatalf("Expected IsNotExist error, got %v", err) // doing string compare here since this error comes directly from the driver
expected := "no such volume"
if err := s.Remove(vt.NoopVolume{}); err == nil || !strings.Contains(err.Error(), expected) {
t.Fatalf("Expected error %q, got %v", expected, err)
} }
v, err := s.Create("fake1", "fake", nil)
v, err := s.CreateWithRef("fake1", "fake", "fake", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
s.Increment(v)
if err := s.Remove(v); !IsInUse(err) { if err := s.Remove(v); !IsInUse(err) {
t.Fatalf("Expected IsInUse error, got %v", err) t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
} }
s.Decrement(v) s.Dereference(v, "fake")
if err := s.Remove(v); err != nil { if err := s.Remove(v); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if l := s.List(); len(l) != 0 { if l, _, _ := s.List(); 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)
} }
} }
func TestIncrement(t *testing.T) { func TestList(t *testing.T) {
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
volumedrivers.Register(vt.NewFakeDriver("fake2"), "fake2")
defer volumedrivers.Unregister("fake")
defer volumedrivers.Unregister("fake2")
s := New() s := New()
v := vt.NewFakeVolume("fake1") if _, err := s.Create("test", "fake", nil); err != nil {
s.Increment(v) t.Fatal(err)
if l := s.List(); len(l) != 1 {
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
} }
if c := s.Count(v); c != 1 { if _, err := s.Create("test2", "fake2", nil); err != nil {
t.Fatalf("Expected 1 counter, got %v", c) t.Fatal(err)
} }
s.Increment(v) ls, _, err := s.List()
if l := s.List(); len(l) != 1 { if err != nil {
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l) t.Fatal(err)
} }
if c := s.Count(v); c != 2 { if len(ls) != 2 {
t.Fatalf("Expected 2 counter, got %v", c) t.Fatalf("expected 2 volumes, got: %d", len(ls))
} }
v2 := vt.NewFakeVolume("fake2") // and again with a new store
s.Increment(v2) s = New()
if l := s.List(); len(l) != 2 { ls, _, err = s.List()
t.Fatalf("Expected 2 volume, got %v, %v", len(l), l) if err != nil {
t.Fatal(err)
} }
} if len(ls) != 2 {
t.Fatalf("expected 2 volumes, got: %d", len(ls))
func TestDecrement(t *testing.T) {
s := New()
v := vt.NoopVolume{}
s.Decrement(v)
if c := s.Count(v); c != 0 {
t.Fatalf("Expected 0 volumes, got %v", c)
}
s.Increment(v)
s.Increment(v)
s.Decrement(v)
if c := s.Count(v); c != 1 {
t.Fatalf("Expected 1 volume, got %v", c)
}
s.Decrement(v)
if c := s.Count(v); c != 0 {
t.Fatalf("Expected 0 volumes, got %v", c)
}
// Test counter cannot be negative.
s.Decrement(v)
if c := s.Count(v); c != 0 {
t.Fatalf("Expected 0 volumes, got %v", c)
} }
} }
func TestFilterByDriver(t *testing.T) { func TestFilterByDriver(t *testing.T) {
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
defer volumedrivers.Unregister("fake")
defer volumedrivers.Unregister("noop")
s := New() s := New()
s.Increment(vt.NewFakeVolume("fake1")) if _, err := s.Create("fake1", "fake", nil); err != nil {
s.Increment(vt.NewFakeVolume("fake2")) t.Fatal(err)
s.Increment(vt.NoopVolume{}) }
if _, err := s.Create("fake2", "fake", nil); err != nil {
t.Fatal(err)
}
if _, err := s.Create("fake3", "noop", nil); err != nil {
t.Fatal(err)
}
if l := s.FilterByDriver("fake"); len(l) != 2 { if l, _ := s.FilterByDriver("fake"); len(l) != 2 {
t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l) t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
} }
if l := s.FilterByDriver("noop"); len(l) != 1 { if l, _ := s.FilterByDriver("noop"); len(l) != 1 {
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l) t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
} }
} }

View File

@ -50,19 +50,55 @@ func (FakeVolume) Mount() (string, error) { return "fake", nil }
func (FakeVolume) Unmount() error { return nil } func (FakeVolume) Unmount() error { return nil }
// FakeDriver is a driver that generates fake volumes // FakeDriver is a driver that generates fake volumes
type FakeDriver struct{} type FakeDriver struct {
name string
vols map[string]volume.Volume
}
// NewFakeDriver creates a new FakeDriver with the specified name
func NewFakeDriver(name string) volume.Driver {
return &FakeDriver{
name: name,
vols: make(map[string]volume.Volume),
}
}
// Name is the name of the driver // Name is the name of the driver
func (FakeDriver) Name() string { return "fake" } func (d *FakeDriver) Name() string { return d.name }
// Create initializes a fake volume. // Create initializes a fake volume.
// It returns an error if the options include an "error" key with a message // It returns an error if the options include an "error" key with a message
func (FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) { func (d *FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
if opts != nil && opts["error"] != "" { if opts != nil && opts["error"] != "" {
return nil, fmt.Errorf(opts["error"]) return nil, fmt.Errorf(opts["error"])
} }
return NewFakeVolume(name), nil v := NewFakeVolume(name)
d.vols[name] = v
return v, nil
} }
// Remove deletes a volume. // Remove deletes a volume.
func (FakeDriver) Remove(v volume.Volume) error { return nil } func (d *FakeDriver) Remove(v volume.Volume) error {
if _, exists := d.vols[v.Name()]; !exists {
return fmt.Errorf("no such volume")
}
delete(d.vols, v.Name())
return nil
}
// List lists the volumes
func (d *FakeDriver) List() ([]volume.Volume, error) {
var vols []volume.Volume
for _, v := range d.vols {
vols = append(vols, v)
}
return vols, nil
}
// Get gets the volume
func (d *FakeDriver) Get(name string) (volume.Volume, error) {
if v, exists := d.vols[name]; exists {
return v, nil
}
return nil, fmt.Errorf("no such volume")
}

View File

@ -21,7 +21,11 @@ type Driver interface {
// Create makes a new volume with the given id. // Create makes a new volume with the given id.
Create(name string, opts map[string]string) (Volume, error) Create(name string, opts map[string]string) (Volume, error)
// Remove deletes the volume. // Remove deletes the volume.
Remove(Volume) error Remove(vol Volume) (err error)
// List lists all the volumes the driver has
List() ([]Volume, error)
// Get retreives the volume with the requested name
Get(name string) (Volume, error)
} }
// Volume is a place to store data. It is backed by a specific driver, and can be mounted. // Volume is a place to store data. It is backed by a specific driver, and can be mounted.