Fix issue where out-of-band deletions and then a `docker volume create`
on the same driver caused volume to not be re-created in the driver but
return as created since it was stored in the cache.
Previous fix only worked if the driver names did not match.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit d8ce4a6e108f4f870228912f105eed8218e087e4)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
... | ... |
@@ -568,8 +568,36 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverOutOfBandDelete(c *c |
568 | 568 |
// simulate out of band volume deletion on plugin level |
569 | 569 |
delete(p.vols, "test") |
570 | 570 |
|
571 |
+ // test re-create with same driver |
|
572 |
+ out, err = s.d.Cmd("volume", "create", "-d", driverName, "--opt", "foo=bar", "--name", "test") |
|
573 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
574 |
+ out, err = s.d.Cmd("volume", "inspect", "test") |
|
575 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
576 |
+ |
|
577 |
+ var vs []types.Volume |
|
578 |
+ err = json.Unmarshal([]byte(out), &vs) |
|
579 |
+ c.Assert(err, checker.IsNil) |
|
580 |
+ c.Assert(vs, checker.HasLen, 1) |
|
581 |
+ c.Assert(vs[0].Driver, checker.Equals, driverName) |
|
582 |
+ c.Assert(vs[0].Options, checker.NotNil) |
|
583 |
+ c.Assert(vs[0].Options["foo"], checker.Equals, "bar") |
|
584 |
+ c.Assert(vs[0].Driver, checker.Equals, driverName) |
|
585 |
+ |
|
586 |
+ // simulate out of band volume deletion on plugin level |
|
587 |
+ delete(p.vols, "test") |
|
588 |
+ |
|
589 |
+ // test create with different driver |
|
571 | 590 |
out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test") |
572 | 591 |
c.Assert(err, checker.IsNil, check.Commentf(out)) |
592 |
+ |
|
593 |
+ out, err = s.d.Cmd("volume", "inspect", "test") |
|
594 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
595 |
+ vs = nil |
|
596 |
+ err = json.Unmarshal([]byte(out), &vs) |
|
597 |
+ c.Assert(err, checker.IsNil) |
|
598 |
+ c.Assert(vs, checker.HasLen, 1) |
|
599 |
+ c.Assert(vs[0].Options, checker.HasLen, 0) |
|
600 |
+ c.Assert(vs[0].Driver, checker.Equals, "local") |
|
573 | 601 |
} |
574 | 602 |
|
575 | 603 |
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnmountOnMountFail(c *check.C) { |
... | ... |
@@ -284,56 +284,64 @@ func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]st |
284 | 284 |
func (s *VolumeStore) checkConflict(name, driverName string) (volume.Volume, error) { |
285 | 285 |
// check the local cache |
286 | 286 |
v, _ := s.getNamed(name) |
287 |
- if v != nil { |
|
288 |
- vDriverName := v.DriverName() |
|
289 |
- if driverName != "" && vDriverName != driverName { |
|
290 |
- // we have what looks like a conflict |
|
291 |
- // let's see if there are existing refs to this volume, if so we don't need |
|
292 |
- // to go any further since we can assume the volume is legit. |
|
293 |
- if len(s.getRefs(name)) > 0 { |
|
294 |
- return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name) |
|
295 |
- } |
|
287 |
+ if v == nil { |
|
288 |
+ return nil, nil |
|
289 |
+ } |
|
296 | 290 |
|
297 |
- // looks like there is a conflict, but nothing is referencing it... |
|
298 |
- // let's check if the found volume ref |
|
299 |
- // is stale by checking with the driver if it still exists |
|
300 |
- vd, err := volumedrivers.GetDriver(vDriverName) |
|
301 |
- if err != nil { |
|
302 |
- // play it safe and return the error |
|
303 |
- // TODO(cpuguy83): maybe when when v2 plugins are ubiquitous, we should |
|
304 |
- // just purge this from the cache |
|
305 |
- 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) |
|
306 |
- } |
|
291 |
+ vDriverName := v.DriverName() |
|
292 |
+ var conflict bool |
|
293 |
+ if driverName != "" && vDriverName != driverName { |
|
294 |
+ conflict = true |
|
295 |
+ } |
|
307 | 296 |
|
308 |
- // now check if it still exists in the driver |
|
309 |
- v2, err := vd.Get(name) |
|
310 |
- err = errors.Cause(err) |
|
311 |
- if err != nil { |
|
312 |
- if _, ok := err.(net.Error); ok { |
|
313 |
- // got some error related to the driver connectivity |
|
314 |
- // play it safe and return the error |
|
315 |
- // TODO(cpuguy83): When when v2 plugins are ubiquitous, maybe we should |
|
316 |
- // just purge this from the cache |
|
317 |
- 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) |
|
318 |
- } |
|
319 |
- |
|
320 |
- // a driver can return whatever it wants, so let's make sure this is nil |
|
321 |
- if v2 == nil { |
|
322 |
- // purge this reference from the cache |
|
323 |
- s.Purge(name) |
|
324 |
- return nil, nil |
|
325 |
- } |
|
326 |
- } |
|
327 |
- if v2 != nil { |
|
328 |
- return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name) |
|
329 |
- } |
|
297 |
+ // let's check if the found volume ref |
|
298 |
+ // is stale by checking with the driver if it still exists |
|
299 |
+ exists, err := volumeExists(v) |
|
300 |
+ if err != nil { |
|
301 |
+ 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) |
|
302 |
+ } |
|
303 |
+ |
|
304 |
+ if exists { |
|
305 |
+ if conflict { |
|
306 |
+ return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name) |
|
330 | 307 |
} |
331 | 308 |
return v, nil |
332 | 309 |
} |
333 | 310 |
|
311 |
+ if len(s.getRefs(v.Name())) > 0 { |
|
312 |
+ // Containers are referencing this volume but it doesn't seem to exist anywhere. |
|
313 |
+ // Return a conflict error here, the user can fix this with `docker volume rm -f` |
|
314 |
+ return nil, errors.Wrapf(errNameConflict, "found references to volume '%s' in driver '%s' but the volume was not found in the driver -- you may need to remove containers referencing this volume or force remove the volume to re-create it", name, vDriverName) |
|
315 |
+ } |
|
316 |
+ |
|
317 |
+ // doesn't exist, so purge it from the cache |
|
318 |
+ s.Purge(name) |
|
334 | 319 |
return nil, nil |
335 | 320 |
} |
336 | 321 |
|
322 |
+// volumeExists returns if the volume is still present in the driver. |
|
323 |
+// An error is returned if there was an issue communicating with the driver. |
|
324 |
+func volumeExists(v volume.Volume) (bool, error) { |
|
325 |
+ vd, err := volumedrivers.GetDriver(v.DriverName()) |
|
326 |
+ if err != nil { |
|
327 |
+ return false, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", v.Name(), v.DriverName()) |
|
328 |
+ } |
|
329 |
+ exists, err := vd.Get(v.Name()) |
|
330 |
+ if err != nil { |
|
331 |
+ err = errors.Cause(err) |
|
332 |
+ if _, ok := err.(net.Error); ok { |
|
333 |
+ return false, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", v.Name(), v.DriverName()) |
|
334 |
+ } |
|
335 |
+ |
|
336 |
+ // At this point, the error could be anything from the driver, such as "no such volume" |
|
337 |
+ // Let's not check an error here, and instead check if the driver returned a volume |
|
338 |
+ } |
|
339 |
+ if exists == nil { |
|
340 |
+ return false, nil |
|
341 |
+ } |
|
342 |
+ return true, nil |
|
343 |
+} |
|
344 |
+ |
|
337 | 345 |
// create asks the given driver to create a volume with the name/opts. |
338 | 346 |
// If a volume with the name is already known, it will ask the stored driver for the volume. |
339 | 347 |
// If the passed in driver name does not match the driver name which is stored |
... | ... |
@@ -354,6 +362,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st |
354 | 354 |
if err != nil { |
355 | 355 |
return nil, err |
356 | 356 |
} |
357 |
+ |
|
357 | 358 |
if v != nil { |
358 | 359 |
return v, nil |
359 | 360 |
} |