Fix volume creates blocked by stale cache entries
When a conflict is found in the volume cache, check with the driver if that volume still actually exists. If the volume doesn't exist, purge it from the cache and allow the create to happen. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
64d0592d41
commit
6a0bdffc1a
5 changed files with 197 additions and 87 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,14 +343,12 @@ 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
|
||||
}
|
||||
// check exist in driver
|
||||
if driverName == "" || driverName == volume.DefaultDriverName {
|
||||
return v, nil
|
||||
}
|
||||
v, err := s.checkConflict(name, driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v != nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name
|
||||
|
@ -294,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
|
||||
}
|
||||
|
@ -435,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}
|
||||
}
|
||||
|
||||
|
@ -476,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
|
||||
|
@ -514,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…
Add table
Reference in a new issue