Merge pull request #27164 from cpuguy83/carry_24205
Fix volume creates blocked by stale cache entries
This commit is contained in:
commit
cf55397e13
5 changed files with 196 additions and 83 deletions
|
@ -17,10 +17,13 @@ import (
|
|||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
const volumePluginName = "test-external-volume-driver"
|
||||
|
||||
func init() {
|
||||
check.Suite(&DockerExternalVolumeSuite{
|
||||
ds: &DockerSuite{},
|
||||
|
@ -40,10 +43,9 @@ type eventCounter struct {
|
|||
}
|
||||
|
||||
type DockerExternalVolumeSuite struct {
|
||||
server *httptest.Server
|
||||
ds *DockerSuite
|
||||
d *Daemon
|
||||
ec *eventCounter
|
||||
ds *DockerSuite
|
||||
d *Daemon
|
||||
*volumePlugin
|
||||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
|
||||
|
@ -57,8 +59,29 @@ func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
||||
s.volumePlugin = newVolumePlugin(c, volumePluginName)
|
||||
}
|
||||
|
||||
type volumePlugin struct {
|
||||
ec *eventCounter
|
||||
*httptest.Server
|
||||
vols map[string]vol
|
||||
}
|
||||
|
||||
type vol struct {
|
||||
Name string
|
||||
Mountpoint string
|
||||
Ninja bool // hack used to trigger a null volume return on `Get`
|
||||
Status map[string]interface{}
|
||||
}
|
||||
|
||||
func (p *volumePlugin) Close() {
|
||||
p.Server.Close()
|
||||
}
|
||||
|
||||
func newVolumePlugin(c *check.C, name string) *volumePlugin {
|
||||
mux := http.NewServeMux()
|
||||
s.server = httptest.NewServer(mux)
|
||||
s := &volumePlugin{Server: httptest.NewServer(mux), ec: &eventCounter{}, vols: make(map[string]vol)}
|
||||
|
||||
type pluginRequest struct {
|
||||
Name string
|
||||
|
@ -71,14 +94,6 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
Err string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type vol struct {
|
||||
Name string
|
||||
Mountpoint string
|
||||
Ninja bool // hack used to trigger a null volume return on `Get`
|
||||
Status map[string]interface{}
|
||||
}
|
||||
var volList []vol
|
||||
|
||||
read := func(b io.ReadCloser) (pluginRequest, error) {
|
||||
defer b.Close()
|
||||
var pr pluginRequest
|
||||
|
@ -115,14 +130,14 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
}
|
||||
_, isNinja := pr.Opts["ninja"]
|
||||
status := map[string]interface{}{"Hello": "world"}
|
||||
volList = append(volList, vol{Name: pr.Name, Ninja: isNinja, Status: status})
|
||||
s.vols[pr.Name] = vol{Name: pr.Name, Ninja: isNinja, Status: status}
|
||||
send(w, nil)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.ec.lists++
|
||||
vols := []vol{}
|
||||
for _, v := range volList {
|
||||
vols := make([]vol, 0, len(s.vols))
|
||||
for _, v := range s.vols {
|
||||
if v.Ninja {
|
||||
continue
|
||||
}
|
||||
|
@ -139,19 +154,19 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
return
|
||||
}
|
||||
|
||||
for _, v := range volList {
|
||||
if v.Name == pr.Name {
|
||||
if v.Ninja {
|
||||
send(w, map[string]vol{})
|
||||
return
|
||||
}
|
||||
|
||||
v.Mountpoint = hostVolumePath(pr.Name)
|
||||
send(w, map[string]vol{"Volume": v})
|
||||
return
|
||||
}
|
||||
v, exists := s.vols[pr.Name]
|
||||
if !exists {
|
||||
send(w, `{"Err": "no such volume"}`)
|
||||
}
|
||||
send(w, `{"Err": "no such volume"}`)
|
||||
|
||||
if v.Ninja {
|
||||
send(w, map[string]vol{})
|
||||
return
|
||||
}
|
||||
|
||||
v.Mountpoint = hostVolumePath(pr.Name)
|
||||
send(w, map[string]vol{"Volume": v})
|
||||
return
|
||||
})
|
||||
|
||||
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -162,16 +177,17 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
return
|
||||
}
|
||||
|
||||
for i, v := range volList {
|
||||
if v.Name == pr.Name {
|
||||
if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
|
||||
send(w, &pluginResp{Err: err.Error()})
|
||||
return
|
||||
}
|
||||
volList = append(volList[:i], volList[i+1:]...)
|
||||
break
|
||||
}
|
||||
v, ok := s.vols[pr.Name]
|
||||
if !ok {
|
||||
send(w, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
|
||||
send(w, &pluginResp{Err: err.Error()})
|
||||
return
|
||||
}
|
||||
delete(s.vols, v.Name)
|
||||
send(w, nil)
|
||||
})
|
||||
|
||||
|
@ -202,7 +218,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
|
||||
if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.Server.URL), 0644); err != nil {
|
||||
send(w, err)
|
||||
return
|
||||
}
|
||||
|
@ -242,12 +258,13 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|||
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = ioutil.WriteFile("/etc/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644)
|
||||
err = ioutil.WriteFile("/etc/docker/plugins/"+name+".spec", []byte(s.Server.URL), 0644)
|
||||
c.Assert(err, checker.IsNil)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
|
||||
s.server.Close()
|
||||
s.volumePlugin.Close()
|
||||
|
||||
err := os.RemoveAll("/etc/docker/plugins")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
@ -257,9 +274,9 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
|
|||
err := s.d.StartWithBusybox()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
||||
out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", volumePluginName, "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
c.Assert(out, checker.Contains, s.server.URL)
|
||||
c.Assert(out, checker.Contains, s.Server.URL)
|
||||
|
||||
_, err = s.d.Cmd("volume", "rm", "external-volume-test")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
@ -280,9 +297,9 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnnamed(c *check.C)
|
|||
err := s.d.StartWithBusybox()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
||||
out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "/tmp/external-volume-test", "--volume-driver", volumePluginName, "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
c.Assert(out, checker.Contains, s.server.URL)
|
||||
c.Assert(out, checker.Contains, s.Server.URL)
|
||||
|
||||
c.Assert(s.ec.activations, checker.Equals, 1)
|
||||
c.Assert(s.ec.creations, checker.Equals, 1)
|
||||
|
@ -295,7 +312,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverVolumesFrom(c *check
|
|||
err := s.d.StartWithBusybox()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
|
||||
out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", volumePluginName, "busybox:latest")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
|
||||
out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp")
|
||||
|
@ -315,7 +332,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverDeleteContainer(c *c
|
|||
err := s.d.StartWithBusybox()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
|
||||
out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", volumePluginName, "busybox:latest")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
|
||||
out, err = s.d.Cmd("rm", "-fv", "vol-test1")
|
||||
|
@ -388,7 +405,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyE
|
|||
// wait for a retry to occur, then create spec to allow plugin to register
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
// no need to check for an error here since it will get picked up by the timeout later
|
||||
ioutil.WriteFile(specPath, []byte(s.server.URL), 0644)
|
||||
ioutil.WriteFile(specPath, []byte(s.Server.URL), 0644)
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -409,7 +426,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyE
|
|||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c *check.C) {
|
||||
dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "foo")
|
||||
dockerCmd(c, "volume", "create", "-d", volumePluginName, "foo")
|
||||
dockerCmd(c, "run", "-d", "--name", "testing", "-v", "foo:/bar", "busybox", "top")
|
||||
|
||||
var mounts []struct {
|
||||
|
@ -420,18 +437,18 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c
|
|||
c.Assert(json.NewDecoder(strings.NewReader(out)).Decode(&mounts), checker.IsNil)
|
||||
c.Assert(len(mounts), checker.Equals, 1, check.Commentf(out))
|
||||
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, volumePluginName)
|
||||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverList(c *check.C) {
|
||||
dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "abc3")
|
||||
dockerCmd(c, "volume", "create", "-d", volumePluginName, "abc3")
|
||||
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[0], check.Equals, volumePluginName)
|
||||
c.Assert(vol[1], check.Equals, "abc3")
|
||||
|
||||
c.Assert(s.ec.lists, check.Equals, 1)
|
||||
|
@ -440,10 +457,10 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverList(c *check.C) {
|
|||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(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")
|
||||
c.Assert(s.ec.gets, check.Equals, 1)
|
||||
|
||||
dockerCmd(c, "volume", "create", "test", "-d", "test-external-volume-driver")
|
||||
dockerCmd(c, "volume", "create", "test", "-d", volumePluginName)
|
||||
out, _ = dockerCmd(c, "volume", "inspect", "test")
|
||||
|
||||
type vol struct {
|
||||
|
@ -458,7 +475,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemonRestart(c *check.C) {
|
||||
dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "abc1")
|
||||
dockerCmd(c, "volume", "create", "-d", volumePluginName, "abc1")
|
||||
err := s.d.Restart()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
|
@ -466,7 +483,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemonRestart(c
|
|||
var mounts []types.MountPoint
|
||||
inspectFieldAndMarshall(c, "test", "Mounts", &mounts)
|
||||
c.Assert(mounts, checker.HasLen, 1)
|
||||
c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver")
|
||||
c.Assert(mounts[0].Driver, checker.Equals, volumePluginName)
|
||||
}
|
||||
|
||||
// Ensures that the daemon handles when the plugin responds to a `Get` request with a null volume and a null error.
|
||||
|
@ -474,7 +491,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemonRestart(c
|
|||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *check.C) {
|
||||
c.Assert(s.d.Start(), checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", "abc2", "--opt", "ninja=1")
|
||||
out, err := s.d.Cmd("volume", "create", "-d", volumePluginName, "abc2", "--opt", "ninja=1")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
|
||||
out, err = s.d.Cmd("volume", "inspect", "abc2")
|
||||
|
@ -505,7 +522,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C)
|
|||
err := s.d.StartWithBusybox()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("run", "--rm", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
||||
out, err := s.d.Cmd("run", "--rm", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", volumePluginName, "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
||||
}
|
||||
|
@ -516,7 +533,7 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *chec
|
|||
c.Assert(s.ec.caps, checker.Equals, 0)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
out, err := s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", fmt.Sprintf("test%d", i))
|
||||
out, err := s.d.Cmd("volume", "create", "-d", volumePluginName, fmt.Sprintf("test%d", i))
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
c.Assert(s.ec.caps, checker.Equals, 1)
|
||||
out, err = s.d.Cmd("volume", "inspect", "--format={{.Scope}}", fmt.Sprintf("test%d", i))
|
||||
|
@ -524,3 +541,24 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *chec
|
|||
c.Assert(strings.TrimSpace(out), checker.Equals, volume.GlobalScope)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverOutOfBandDelete(c *check.C) {
|
||||
driverName := stringid.GenerateNonCryptoID()
|
||||
p := newVolumePlugin(c, driverName)
|
||||
defer p.Close()
|
||||
|
||||
c.Assert(s.d.StartWithBusybox(), checker.IsNil)
|
||||
|
||||
out, err := s.d.Cmd("volume", "create", "-d", driverName, "--name", "test")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
|
||||
out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test")
|
||||
c.Assert(err, checker.NotNil, check.Commentf(out))
|
||||
c.Assert(out, checker.Contains, "volume named test already exists")
|
||||
|
||||
// simulate out of band volume deletion on plugin level
|
||||
delete(p.vols, "test")
|
||||
|
||||
out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test")
|
||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -64,11 +65,12 @@ func IsNameConflict(err error) bool {
|
|||
}
|
||||
|
||||
func isErr(err error, expected error) bool {
|
||||
err = errors.Cause(err)
|
||||
switch pe := err.(type) {
|
||||
case nil:
|
||||
return false
|
||||
case *OpErr:
|
||||
err = pe.Err
|
||||
err = errors.Cause(pe.Err)
|
||||
}
|
||||
return err == expected
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package store
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
@ -117,6 +118,15 @@ func (s *VolumeStore) setNamed(v volume.Volume, ref string) {
|
|||
s.globalLock.Unlock()
|
||||
}
|
||||
|
||||
// getRefs gets the list of refs for a given name
|
||||
// Callers of this function are expected to hold the name lock.
|
||||
func (s *VolumeStore) getRefs(name string) []string {
|
||||
s.globalLock.Lock()
|
||||
refs := s.refs[name]
|
||||
s.globalLock.Unlock()
|
||||
return refs
|
||||
}
|
||||
|
||||
// Purge allows the cleanup of internal data on docker in case
|
||||
// the internal data is out of sync with volumes driver plugins.
|
||||
func (s *VolumeStore) Purge(name string) {
|
||||
|
@ -251,9 +261,77 @@ func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]st
|
|||
return s.CreateWithRef(name, driverName, "", opts, labels)
|
||||
}
|
||||
|
||||
// checkConflict checks the local cache for name collisions with the passed in name,
|
||||
// for existing volumes with the same name but in a different driver.
|
||||
// This is used by `Create` as a best effort to prevent name collisions for volumes.
|
||||
// If a matching volume is found that is not a conflict that is returned so the caller
|
||||
// does not need to perform an additional lookup.
|
||||
// When no matching volume is found, both returns will be nil
|
||||
//
|
||||
// Note: This does not probe all the drivers for name collisions because v1 plugins
|
||||
// are very slow, particularly if the plugin is down, and cause other issues,
|
||||
// particularly around locking the store.
|
||||
// TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially
|
||||
// use a connect timeout for this kind of check to ensure we aren't blocking for a
|
||||
// long time.
|
||||
func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, error) {
|
||||
// check the local cache
|
||||
v, _ := s.getNamed(name)
|
||||
if v != nil {
|
||||
vDriverName := v.DriverName()
|
||||
if driverName != "" && vDriverName != driverName {
|
||||
// we have what looks like a conflict
|
||||
// let's see if there are existing refs to this volume, if so we don't need
|
||||
// to go any further since we can assume the volume is legit.
|
||||
if len(s.getRefs(name)) > 0 {
|
||||
return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name)
|
||||
}
|
||||
|
||||
// looks like there is a conflict, but nothing is referencing it...
|
||||
// let's check if the found volume ref
|
||||
// is stale by checking with the driver if it still exists
|
||||
vd, err := volumedrivers.GetDriver(vDriverName)
|
||||
if err != nil {
|
||||
// play it safe and return the error
|
||||
// TODO(cpuguy83): maybe when when v2 plugins are ubiquitous, we should
|
||||
// just purge this from the cache
|
||||
return nil, errors.Wrapf(errNameConflict, "found reference to volume '%s' in driver '%s', but got an error while checking the driver: %v", name, vDriverName, err)
|
||||
}
|
||||
|
||||
// now check if it still exists in the driver
|
||||
v2, err := vd.Get(name)
|
||||
err = errors.Cause(err)
|
||||
if err != nil {
|
||||
if _, ok := err.(net.Error); ok {
|
||||
// got some error related to the driver connectivity
|
||||
// play it safe and return the error
|
||||
// TODO(cpuguy83): When when v2 plugins are ubiquitous, maybe we should
|
||||
// just purge this from the cache
|
||||
return nil, errors.Wrapf(errNameConflict, "found reference to volume '%s' in driver '%s', but got an error while checking the driver: %v", name, vDriverName, err)
|
||||
}
|
||||
|
||||
// a driver can return whatever it wants, so let's make sure this is nil
|
||||
if v2 == nil {
|
||||
// purge this reference from the cache
|
||||
s.Purge(name)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if v2 != nil {
|
||||
return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name)
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, 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.
|
||||
// If the passed in driver name does not match the driver name which is stored
|
||||
// for the given volume name, an error is returned after checking if the reference is stale.
|
||||
// If the reference is stale, it will be purged and this create can continue.
|
||||
// It is expected that callers of this function hold any necessary locks.
|
||||
func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
||||
// Validate the name in a platform-specific manner
|
||||
|
@ -265,10 +343,11 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
|
|||
return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
|
||||
}
|
||||
|
||||
if v, exists := s.getNamed(name); exists {
|
||||
if v.DriverName() != driverName && driverName != "" && driverName != volume.DefaultDriverName {
|
||||
return nil, errNameConflict
|
||||
}
|
||||
v, err := s.checkConflict(name, driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v != nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
@ -291,7 +370,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
|
|||
if v, _ := vd.Get(name); v != nil {
|
||||
return v, nil
|
||||
}
|
||||
v, err := vd.Create(name, opts)
|
||||
v, err = vd.Create(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -432,7 +511,8 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
|
|||
s.locks.Lock(name)
|
||||
defer s.locks.Unlock(name)
|
||||
|
||||
if refs, exists := s.refs[name]; exists && len(refs) > 0 {
|
||||
refs := s.getRefs(name)
|
||||
if len(refs) > 0 {
|
||||
return &OpErr{Err: errVolumeInUse, Name: v.Name(), Op: "remove", Refs: refs}
|
||||
}
|
||||
|
||||
|
@ -473,13 +553,7 @@ func (s *VolumeStore) Refs(v volume.Volume) []string {
|
|||
s.locks.Lock(v.Name())
|
||||
defer s.locks.Unlock(v.Name())
|
||||
|
||||
s.globalLock.Lock()
|
||||
defer s.globalLock.Unlock()
|
||||
refs, exists := s.refs[v.Name()]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
refs := s.getRefs(v.Name())
|
||||
refsOut := make([]string, len(refs))
|
||||
copy(refsOut, refs)
|
||||
return refsOut
|
||||
|
@ -511,7 +585,7 @@ func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) {
|
|||
func (s *VolumeStore) FilterByUsed(vols []volume.Volume, used bool) []volume.Volume {
|
||||
return s.filter(vols, func(v volume.Volume) bool {
|
||||
s.locks.Lock(v.Name())
|
||||
l := len(s.refs[v.Name()])
|
||||
l := len(s.getRefs(v.Name()))
|
||||
s.locks.Unlock(v.Name())
|
||||
if (used && l > 0) || (!used && l == 0) {
|
||||
return true
|
||||
|
|
|
@ -270,7 +270,6 @@ func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*Moun
|
|||
}
|
||||
mp.CopyData = DefaultCopyMode
|
||||
|
||||
mp.Driver = DefaultDriverName
|
||||
if cfg.VolumeOptions != nil {
|
||||
if cfg.VolumeOptions.DriverConfig != nil {
|
||||
mp.Driver = cfg.VolumeOptions.DriverConfig.Name
|
||||
|
|
|
@ -163,8 +163,8 @@ func TestParseMountRawSplit(t *testing.T) {
|
|||
{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
|
||||
{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
|
||||
// TODO Windows post TP5 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
|
||||
{`name:c:`, "", ``, ``, ``, DefaultDriverName, true, true},
|
||||
{`driver/name:c:`, "", ``, ``, ``, DefaultDriverName, true, true},
|
||||
{`name:c:`, "", ``, ``, ``, "", true, true},
|
||||
{`driver/name:c:`, "", ``, ``, ``, "", true, true},
|
||||
}
|
||||
} else {
|
||||
cases = []testParseMountRaw{
|
||||
|
@ -172,10 +172,10 @@ func TestParseMountRawSplit(t *testing.T) {
|
|||
{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
|
||||
{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
|
||||
{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
|
||||
{"name:/named1", "", "/named1", "", "name", DefaultDriverName, true, false},
|
||||
{"name:/named1", "", "/named1", "", "name", "", true, false},
|
||||
{"name:/named2", "external", "/named2", "", "name", "external", true, false},
|
||||
{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
|
||||
{"local/name:/tmp:rw", "", "/tmp", "", "local/name", DefaultDriverName, true, false},
|
||||
{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "", true, false},
|
||||
{"/tmp:tmp", "", "", "", "", "", true, true},
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ func TestParseMountRawSplit(t *testing.T) {
|
|||
t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
|
||||
}
|
||||
|
||||
if (m.Driver != c.expDriver) || (m.Driver == DefaultDriverName && c.expDriver == "") {
|
||||
if m.Driver != c.expDriver {
|
||||
t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
|
||||
}
|
||||
|
||||
|
@ -233,8 +233,8 @@ func TestParseMountSpec(t *testing.T) {
|
|||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true}},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
|
||||
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, Driver: DefaultDriverName, CopyData: DefaultCopyMode}},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, Driver: DefaultDriverName, CopyData: DefaultCopyMode}},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
|
||||
{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
|
|
Loading…
Reference in a new issue