Browse Source

Fix N+1 calling `Path()` on `volume ls`

Implements a `CachedPath` function on the volume plugin adapter that we
call from the volume list function instead of `Path.
If a driver does not implement `CachedPath` it will just call `Path`.

Also makes sure we store the path on Mount and remove the path on
Unmount.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff 9 years ago
parent
commit
9e6b1852a7

+ 3 - 1
daemon/create.go

@@ -183,5 +183,7 @@ func (daemon *Daemon) VolumeCreate(name, driverName string, opts, labels map[str
 	}
 	}
 
 
 	daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
 	daemon.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()})
-	return volumeToAPIType(v), nil
+	apiV := volumeToAPIType(v)
+	apiV.Mountpoint = v.Path()
+	return apiV, nil
 }
 }

+ 3 - 1
daemon/inspect.go

@@ -204,7 +204,9 @@ func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	return volumeToAPIType(v), nil
+	apiV := volumeToAPIType(v)
+	apiV.Mountpoint = v.Path()
+	return apiV, nil
 }
 }
 
 
 func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings {
 func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings {

+ 9 - 1
daemon/list.go

@@ -491,7 +491,15 @@ func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error)
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
 	for _, v := range filterVolumes {
 	for _, v := range filterVolumes {
-		volumesOut = append(volumesOut, volumeToAPIType(v))
+		apiV := volumeToAPIType(v)
+		if vv, ok := v.(interface {
+			CachedPath() string
+		}); ok {
+			apiV.Mountpoint = vv.CachedPath()
+		} else {
+			apiV.Mountpoint = v.Path()
+		}
+		volumesOut = append(volumesOut, apiV)
 	}
 	}
 	return volumesOut, warnings, nil
 	return volumesOut, warnings, nil
 }
 }

+ 2 - 3
daemon/volumes.go

@@ -25,9 +25,8 @@ type mounts []container.Mount
 // volumeToAPIType converts a volume.Volume to the type used by the remote API
 // volumeToAPIType converts a volume.Volume to the type used by the remote API
 func volumeToAPIType(v volume.Volume) *types.Volume {
 func volumeToAPIType(v volume.Volume) *types.Volume {
 	tv := &types.Volume{
 	tv := &types.Volume{
-		Name:       v.Name(),
-		Driver:     v.DriverName(),
-		Mountpoint: v.Path(),
+		Name:   v.Name(),
+		Driver: v.DriverName(),
 	}
 	}
 	if v, ok := v.(interface {
 	if v, ok := v.(interface {
 		Labels() map[string]string
 		Labels() map[string]string

+ 19 - 0
integration-cli/docker_cli_start_volume_driver_unix_test.go

@@ -441,3 +441,22 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *
 	c.Assert(err, checker.NotNil, check.Commentf(out))
 	c.Assert(err, checker.NotNil, check.Commentf(out))
 	c.Assert(out, checker.Contains, "No such volume")
 	c.Assert(out, checker.Contains, "No such volume")
 }
 }
+
+// Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path`
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
+	c.Assert(s.d.Start(), checker.IsNil)
+	c.Assert(s.ec.paths, checker.Equals, 0)
+
+	out, err := s.d.Cmd("volume", "create", "--name=test", "--driver=test-external-volume-driver")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(s.ec.paths, checker.Equals, 1)
+
+	out, err = s.d.Cmd("volume", "ls")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(s.ec.paths, checker.Equals, 1)
+
+	out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
+	c.Assert(s.ec.paths, checker.Equals, 1)
+}

+ 12 - 5
volume/drivers/adapter.go

@@ -88,11 +88,14 @@ func (a *volumeAdapter) DriverName() string {
 }
 }
 
 
 func (a *volumeAdapter) Path() string {
 func (a *volumeAdapter) Path() string {
-	if len(a.eMount) > 0 {
-		return a.eMount
+	if len(a.eMount) == 0 {
+		a.eMount, _ = a.proxy.Path(a.name)
 	}
 	}
-	m, _ := a.proxy.Path(a.name)
-	return m
+	return a.eMount
+}
+
+func (a *volumeAdapter) CachedPath() string {
+	return a.eMount
 }
 }
 
 
 func (a *volumeAdapter) Mount() (string, error) {
 func (a *volumeAdapter) Mount() (string, error) {
@@ -102,5 +105,9 @@ func (a *volumeAdapter) Mount() (string, error) {
 }
 }
 
 
 func (a *volumeAdapter) Unmount() error {
 func (a *volumeAdapter) Unmount() error {
-	return a.proxy.Unmount(a.name)
+	err := a.proxy.Unmount(a.name)
+	if err == nil {
+		a.eMount = ""
+	}
+	return err
 }
 }