浏览代码

Volumes refactor and external plugin implementation.

Signed by all authors:

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
Signed-off-by: David Calavera <david.calavera@gmail.com>
Signed-off-by: Jeff Lindsay <progrium@gmail.com>
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
Signed-off-by: Luke Marsden <luke@clusterhq.com>
Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera 10 年之前
父节点
当前提交
81fa9feb0c
共有 43 个文件被更改,包括 1527 次插入1180 次删除
  1. 2 15
      api/client/cli.go
  2. 1 1
      builder/internals.go
  3. 126 43
      daemon/container.go
  4. 1 8
      daemon/container_linux.go
  5. 0 6
      daemon/container_windows.go
  6. 45 6
      daemon/create.go
  7. 49 44
      daemon/daemon.go
  8. 5 11
      daemon/delete.go
  9. 14 2
      daemon/inspect.go
  10. 185 216
      daemon/volumes.go
  11. 36 25
      daemon/volumes_linux.go
  12. 146 0
      daemon/volumes_unit_test.go
  13. 4 6
      daemon/volumes_windows.go
  14. 2 0
      docs/mkdocs.yml
  15. 1 0
      docs/sources/reference/api/README.md
  16. 7 0
      docs/sources/reference/api/docker_remote_api.md
  17. 5 2
      docs/sources/reference/api/docker_remote_api_v1.19.md
  18. 223 0
      docs/sources/reference/api/plugin_api.md
  19. 16 2
      docs/sources/reference/commandline/cli.md
  20. 8 0
      docs/sources/userguide/dockervolumes.md
  21. 6 0
      docs/sources/userguide/index.md
  22. 51 0
      docs/sources/userguide/plugins.md
  23. 1 44
      integration-cli/docker_api_containers_test.go
  24. 0 99
      integration-cli/docker_cli_daemon_test.go
  25. 10 54
      integration-cli/docker_cli_run_test.go
  26. 0 26
      integration-cli/docker_cli_start_test.go
  27. 150 0
      integration-cli/docker_cli_start_volume_driver_unix_test.go
  28. 0 1
      integration-cli/docker_test_vars.go
  29. 16 4
      pkg/plugins/client.go
  30. 43 1
      pkg/plugins/client_test.go
  31. 1 0
      runconfig/config.go
  32. 2 0
      runconfig/parse.go
  33. 22 0
      utils/tcp.go
  34. 51 0
      volume/drivers/adapter.go
  35. 20 0
      volume/drivers/api.go
  36. 61 0
      volume/drivers/extpoint.go
  37. 65 0
      volume/drivers/proxy.go
  38. 126 0
      volume/local/local.go
  39. 26 0
      volume/volume.go
  40. 0 193
      volumes/repository.go
  41. 0 164
      volumes/repository_test.go
  42. 0 152
      volumes/volume.go
  43. 0 55
      volumes/volume_test.go

+ 2 - 15
api/client/cli.go

@@ -6,19 +6,18 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"net"
 	"net/http"
 	"os"
 	"path/filepath"
 	"reflect"
 	"strings"
 	"text/template"
-	"time"
 
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/homedir"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/term"
+	"github.com/docker/docker/utils"
 )
 
 // DockerCli represents the docker command line client.
@@ -178,19 +177,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
 	}
-
-	// Why 32? See https://github.com/docker/docker/pull/8035.
-	timeout := 32 * time.Second
-	if proto == "unix" {
-		// No need for compression in local communications.
-		tr.DisableCompression = true
-		tr.Dial = func(_, _ string) (net.Conn, error) {
-			return net.DialTimeout(proto, addr, timeout)
-		}
-	} else {
-		tr.Proxy = http.ProxyFromEnvironment
-		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
-	}
+	utils.ConfigureTCPTransport(tr, proto, addr)
 
 	configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
 	if e != nil {

+ 1 - 1
builder/internals.go

@@ -773,7 +773,7 @@ func (b *Builder) clearTmp() {
 			fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %v\n", stringid.TruncateID(c), err)
 			return
 		}
-		b.Daemon.DeleteVolumes(tmp.VolumePaths())
+		b.Daemon.DeleteVolumes(tmp)
 		delete(b.TmpContainers, c)
 		fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", stringid.TruncateID(c))
 	}

+ 126 - 43
daemon/container.go

@@ -26,9 +26,11 @@ import (
 	"github.com/docker/docker/pkg/broadcastwriter"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/jsonlog"
+	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/runconfig"
+	"github.com/docker/docker/volume"
 )
 
 var (
@@ -48,46 +50,37 @@ type StreamConfig struct {
 // CommonContainer holds the settings for a container which are applicable
 // across all platforms supported by the daemon.
 type CommonContainer struct {
+	StreamConfig
+
 	*State `json:"State"` // Needed for remote api version <= 1.11
 	root   string         // Path to the "home" of the container, including metadata.
 	basefs string         // Path to the graphdriver mountpoint
 
-	ID string
-
-	Created time.Time
-
-	Path string
-	Args []string
-
-	Config  *runconfig.Config
-	ImageID string `json:"Image"`
-
-	NetworkSettings *network.Settings
-
-	ResolvConfPath string
-	HostnamePath   string
-	HostsPath      string
-	LogPath        string
-	Name           string
-	Driver         string
-	ExecDriver     string
-
-	command *execdriver.Command
-	StreamConfig
-
-	daemon                   *Daemon
+	ID                       string
+	Created                  time.Time
+	Path                     string
+	Args                     []string
+	Config                   *runconfig.Config
+	ImageID                  string `json:"Image"`
+	NetworkSettings          *network.Settings
+	ResolvConfPath           string
+	HostnamePath             string
+	HostsPath                string
+	LogPath                  string
+	Name                     string
+	Driver                   string
+	ExecDriver               string
 	MountLabel, ProcessLabel string
 	RestartCount             int
 	UpdateDns                bool
+	MountPoints              map[string]*mountPoint
 
-	// Maps container paths to volume paths.  The key in this is the path to which
-	// the volume is being mounted inside the container.  Value is the path of the
-	// volume on disk
-	Volumes    map[string]string
 	hostConfig *runconfig.HostConfig
+	command    *execdriver.Command
 
 	monitor      *containerMonitor
 	execCommands *execStore
+	daemon       *Daemon
 	// logDriver for closing
 	logDriver logger.Logger
 	logCopier *logger.Copier
@@ -259,9 +252,6 @@ func (container *Container) Start() (err error) {
 		return err
 	}
 	container.verifyDaemonSettings()
-	if err := container.prepareVolumes(); err != nil {
-		return err
-	}
 	linkedEnv, err := container.setupLinkedContainers()
 	if err != nil {
 		return err
@@ -273,10 +263,13 @@ func (container *Container) Start() (err error) {
 	if err := populateCommand(container, env); err != nil {
 		return err
 	}
-	if err := container.setupMounts(); err != nil {
+
+	mounts, err := container.setupMounts()
+	if err != nil {
 		return err
 	}
 
+	container.command.Mounts = mounts
 	return container.waitForStart()
 }
 
@@ -571,27 +564,38 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
 	if err := container.Mount(); err != nil {
 		return nil, err
 	}
+	var paths []string
+	unmount := func() {
+		for _, p := range paths {
+			syscall.Unmount(p, 0)
+		}
+	}
 	defer func() {
 		if err != nil {
+			// unmount any volumes
+			unmount()
+			// unmount the container's rootfs
 			container.Unmount()
 		}
 	}()
-
-	if err = container.mountVolumes(); err != nil {
-		container.unmountVolumes()
+	mounts, err := container.setupMounts()
+	if err != nil {
 		return nil, err
 	}
-	defer func() {
+	for _, m := range mounts {
+		dest, err := container.GetResourcePath(m.Destination)
 		if err != nil {
-			container.unmountVolumes()
+			return nil, err
 		}
-	}()
-
+		paths = append(paths, dest)
+		if err := mount.Mount(m.Source, dest, "bind", "rbind,ro"); err != nil {
+			return nil, err
+		}
+	}
 	basePath, err := container.GetResourcePath(resource)
 	if err != nil {
 		return nil, err
 	}
-
 	stat, err := os.Stat(basePath)
 	if err != nil {
 		return nil, err
@@ -605,7 +609,6 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
 		filter = []string{filepath.Base(basePath)}
 		basePath = filepath.Dir(basePath)
 	}
-
 	archive, err := archive.TarWithOptions(basePath, &archive.TarOptions{
 		Compression:  archive.Uncompressed,
 		IncludeFiles: filter,
@@ -613,10 +616,9 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
 	if err != nil {
 		return nil, err
 	}
-
 	return ioutils.NewReadCloserWrapper(archive, func() error {
 			err := archive.Close()
-			container.unmountVolumes()
+			unmount()
 			container.Unmount()
 			return err
 		}),
@@ -1007,3 +1009,84 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
 	}
 	return written, err
 }
+
+func (container *Container) networkMounts() []execdriver.Mount {
+	var mounts []execdriver.Mount
+	if container.ResolvConfPath != "" {
+		mounts = append(mounts, execdriver.Mount{
+			Source:      container.ResolvConfPath,
+			Destination: "/etc/resolv.conf",
+			Writable:    !container.hostConfig.ReadonlyRootfs,
+			Private:     true,
+		})
+	}
+	if container.HostnamePath != "" {
+		mounts = append(mounts, execdriver.Mount{
+			Source:      container.HostnamePath,
+			Destination: "/etc/hostname",
+			Writable:    !container.hostConfig.ReadonlyRootfs,
+			Private:     true,
+		})
+	}
+	if container.HostsPath != "" {
+		mounts = append(mounts, execdriver.Mount{
+			Source:      container.HostsPath,
+			Destination: "/etc/hosts",
+			Writable:    !container.hostConfig.ReadonlyRootfs,
+			Private:     true,
+		})
+	}
+	return mounts
+}
+
+func (container *Container) AddLocalMountPoint(name, destination string, rw bool) {
+	container.MountPoints[destination] = &mountPoint{
+		Name:        name,
+		Driver:      volume.DefaultDriverName,
+		Destination: destination,
+		RW:          rw,
+	}
+}
+
+func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
+	container.MountPoints[destination] = &mountPoint{
+		Name:        vol.Name(),
+		Driver:      vol.DriverName(),
+		Destination: destination,
+		RW:          rw,
+		Volume:      vol,
+	}
+}
+
+func (container *Container) IsDestinationMounted(destination string) bool {
+	return container.MountPoints[destination] != nil
+}
+
+func (container *Container) PrepareMountPoints() error {
+	for _, config := range container.MountPoints {
+		if len(config.Driver) > 0 {
+			v, err := createVolume(config.Name, config.Driver)
+			if err != nil {
+				return err
+			}
+			config.Volume = v
+		}
+	}
+	return nil
+}
+
+func (container *Container) RemoveMountPoints() error {
+	for _, m := range container.MountPoints {
+		if m.Volume != nil {
+			if err := removeVolume(m.Volume); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (container *Container) ShouldRestart() bool {
+	return container.hostConfig.RestartPolicy.Name == "always" ||
+		(container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0)
+}

+ 1 - 8
daemon/container_linux.go

@@ -42,14 +42,7 @@ type Container struct {
 	// Fields below here are platform specific.
 
 	AppArmorProfile string
-
-	// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
-	// Easier than migrating older container configs :)
-	VolumesRW map[string]bool
-
-	AppliedVolumesFrom map[string]struct{}
-
-	activeLinks map[string]*links.Link
+	activeLinks     map[string]*links.Link
 }
 
 func killProcessDirectly(container *Container) error {

+ 0 - 6
daemon/container_windows.go

@@ -27,12 +27,6 @@ type Container struct {
 	// removed in subsequent PRs.
 
 	AppArmorProfile string
-
-	// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
-	// Easier than migrating older container configs :)
-	VolumesRW map[string]bool
-
-	AppliedVolumesFrom map[string]struct{}
 	// ---- END OF TEMPORARY DECLARATION ----
 
 }

+ 45 - 6
daemon/create.go

@@ -2,11 +2,15 @@ package daemon
 
 import (
 	"fmt"
+	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/docker/docker/graph"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/parsers"
+	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/libcontainer/label"
 )
@@ -87,17 +91,52 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 	if err := daemon.createRootfs(container); err != nil {
 		return nil, nil, err
 	}
-	if hostConfig != nil {
-		if err := daemon.setHostConfig(container, hostConfig); err != nil {
-			return nil, nil, err
-		}
+	if err := daemon.setHostConfig(container, hostConfig); err != nil {
+		return nil, nil, err
 	}
 	if err := container.Mount(); err != nil {
 		return nil, nil, err
 	}
 	defer container.Unmount()
-	if err := container.prepareVolumes(); err != nil {
-		return nil, nil, err
+
+	for spec := range config.Volumes {
+		var (
+			name, destination string
+			parts             = strings.Split(spec, ":")
+		)
+		switch len(parts) {
+		case 2:
+			name, destination = parts[0], filepath.Clean(parts[1])
+		default:
+			name = stringid.GenerateRandomID()
+			destination = filepath.Clean(parts[0])
+		}
+		// Skip volumes for which we already have something mounted on that
+		// destination because of a --volume-from.
+		if container.IsDestinationMounted(destination) {
+			continue
+		}
+		path, err := container.GetResourcePath(destination)
+		if err != nil {
+			return nil, nil, err
+		}
+		if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
+			return nil, nil, fmt.Errorf("cannot mount volume over existing file, file exists %s", path)
+		}
+		v, err := createVolume(name, config.VolumeDriver)
+		if err != nil {
+			return nil, nil, err
+		}
+		rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
+		if err != nil {
+			return nil, nil, err
+		}
+		if path, err = v.Mount(); err != nil {
+			return nil, nil, err
+		}
+		copyExistingContents(rootfs, path)
+
+		container.AddMountPointWithVolume(destination, v, true)
 	}
 	if err := container.ToDisk(); err != nil {
 		return nil, nil, err

+ 49 - 44
daemon/daemon.go

@@ -46,9 +46,12 @@ import (
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/trust"
 	"github.com/docker/docker/utils"
-	"github.com/docker/docker/volumes"
+	volumedrivers "github.com/docker/docker/volume/drivers"
+	"github.com/docker/docker/volume/local"
 )
 
+const defaultVolumesPathName = "volumes"
+
 var (
 	validContainerNameChars   = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
 	validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
@@ -99,7 +102,6 @@ type Daemon struct {
 	repositories     *graph.TagStore
 	idIndex          *truncindex.TruncIndex
 	sysInfo          *sysinfo.SysInfo
-	volumes          *volumes.Repository
 	config           *Config
 	containerGraph   *graphdb.Database
 	driver           graphdriver.Driver
@@ -109,6 +111,7 @@ type Daemon struct {
 	RegistryService  *registry.Service
 	EventsService    *events.Events
 	netController    libnetwork.NetworkController
+	root             string
 }
 
 // Get looks for a container using the provided information, which could be
@@ -209,7 +212,13 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
 	// we'll waste time if we update it for every container
 	daemon.idIndex.Add(container.ID)
 
-	container.registerVolumes()
+	if err := daemon.verifyOldVolumesInfo(container); err != nil {
+		return err
+	}
+
+	if err := container.PrepareMountPoints(); err != nil {
+		return err
+	}
 
 	if container.IsRunning() {
 		logrus.Debugf("killing old running container %s", container.ID)
@@ -249,10 +258,15 @@ func (daemon *Daemon) ensureName(container *Container) error {
 }
 
 func (daemon *Daemon) restore() error {
+	type cr struct {
+		container  *Container
+		registered bool
+	}
+
 	var (
 		debug         = (os.Getenv("DEBUG") != "" || os.Getenv("TEST") != "")
-		containers    = make(map[string]*Container)
 		currentDriver = daemon.driver.String()
+		containers    = make(map[string]*cr)
 	)
 
 	if !debug {
@@ -278,14 +292,12 @@ func (daemon *Daemon) restore() error {
 		if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver {
 			logrus.Debugf("Loaded container %v", container.ID)
 
-			containers[container.ID] = container
+			containers[container.ID] = &cr{container: container}
 		} else {
 			logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID)
 		}
 	}
 
-	registeredContainers := []*Container{}
-
 	if entities := daemon.containerGraph.List("/", -1); entities != nil {
 		for _, p := range entities.Paths() {
 			if !debug && logrus.GetLevel() == logrus.InfoLevel {
@@ -294,50 +306,43 @@ func (daemon *Daemon) restore() error {
 
 			e := entities[p]
 
-			if container, ok := containers[e.ID()]; ok {
-				if err := daemon.register(container, false); err != nil {
-					logrus.Debugf("Failed to register container %s: %s", container.ID, err)
-				}
-
-				registeredContainers = append(registeredContainers, container)
-
-				// delete from the map so that a new name is not automatically generated
-				delete(containers, e.ID())
+			if c, ok := containers[e.ID()]; ok {
+				c.registered = true
 			}
 		}
 	}
 
-	// Any containers that are left over do not exist in the graph
-	for _, container := range containers {
-		// Try to set the default name for a container if it exists prior to links
-		container.Name, err = daemon.generateNewName(container.ID)
-		if err != nil {
-			logrus.Debugf("Setting default id - %s", err)
-		}
+	group := sync.WaitGroup{}
+	for _, c := range containers {
+		group.Add(1)
 
-		if err := daemon.register(container, false); err != nil {
-			logrus.Debugf("Failed to register container %s: %s", container.ID, err)
-		}
+		go func(container *Container, registered bool) {
+			defer group.Done()
 
-		registeredContainers = append(registeredContainers, container)
-	}
+			if !registered {
+				// Try to set the default name for a container if it exists prior to links
+				container.Name, err = daemon.generateNewName(container.ID)
+				if err != nil {
+					logrus.Debugf("Setting default id - %s", err)
+				}
+			}
 
-	// check the restart policy on the containers and restart any container with
-	// the restart policy of "always"
-	if daemon.config.AutoRestart {
-		logrus.Debug("Restarting containers...")
+			if err := daemon.register(container, false); err != nil {
+				logrus.Debugf("Failed to register container %s: %s", container.ID, err)
+			}
 
-		for _, container := range registeredContainers {
-			if container.hostConfig.RestartPolicy.IsAlways() ||
-				(container.hostConfig.RestartPolicy.IsOnFailure() && container.ExitCode != 0) {
+			// check the restart policy on the containers and restart any container with
+			// the restart policy of "always"
+			if daemon.config.AutoRestart && container.ShouldRestart() {
 				logrus.Debugf("Starting container %s", container.ID)
 
 				if err := container.Start(); err != nil {
 					logrus.Debugf("Failed to start container %s: %s", container.ID, err)
 				}
 			}
-		}
+		}(c.container, c.registered)
 	}
+	group.Wait()
 
 	if !debug {
 		if logrus.GetLevel() == logrus.InfoLevel {
@@ -535,6 +540,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID
 			ExecDriver:      daemon.execDriver.Name(),
 			State:           NewState(),
 			execCommands:    newExecStore(),
+			MountPoints:     map[string]*mountPoint{},
 		},
 	}
 	container.root = daemon.containerRoot(container.ID)
@@ -785,15 +791,11 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
 		return nil, err
 	}
 
-	volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
-	if err != nil {
-		return nil, err
-	}
-
-	volumes, err := volumes.NewRepository(filepath.Join(config.Root, "volumes"), volumesDriver)
+	volumesDriver, err := local.New(filepath.Join(config.Root, defaultVolumesPathName))
 	if err != nil {
 		return nil, err
 	}
+	volumedrivers.Register(volumesDriver, volumesDriver.Name())
 
 	trustKey, err := api.LoadOrCreateTrustKey(config.TrustKeyPath)
 	if err != nil {
@@ -872,7 +874,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
 	d.repositories = repositories
 	d.idIndex = truncindex.NewTruncIndex([]string{})
 	d.sysInfo = sysInfo
-	d.volumes = volumes
 	d.config = config
 	d.sysInitPath = sysInitPath
 	d.execDriver = ed
@@ -880,6 +881,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
 	d.defaultLogConfig = config.LogConfig
 	d.RegistryService = registryService
 	d.EventsService = eventsService
+	d.root = config.Root
 
 	if err := d.restore(); err != nil {
 		return nil, err
@@ -1218,6 +1220,10 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri
 }
 
 func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error {
+	if err := daemon.registerMountPoints(container, hostConfig); err != nil {
+		return err
+	}
+
 	container.Lock()
 	defer container.Unlock()
 	if err := parseSecurityOpt(container, hostConfig); err != nil {
@@ -1231,6 +1237,5 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.
 
 	container.hostConfig = hostConfig
 	container.toDisk()
-
 	return nil
 }

+ 5 - 11
daemon/delete.go

@@ -71,21 +71,12 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error
 		}
 		container.LogEvent("destroy")
 		if config.RemoveVolume {
-			daemon.DeleteVolumes(container.VolumePaths())
+			container.RemoveMountPoints()
 		}
 	}
 	return nil
 }
 
-func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) {
-	for id := range volumeIDs {
-		if err := daemon.volumes.Delete(id); err != nil {
-			logrus.Infof("%s", err)
-			continue
-		}
-	}
-}
-
 func (daemon *Daemon) Rm(container *Container) (err error) {
 	return daemon.commonRm(container, false)
 }
@@ -134,7 +125,6 @@ func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err erro
 		}
 	}()
 
-	container.derefVolumes()
 	if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
 		logrus.Debugf("Unable to remove container from link graph: %s", err)
 	}
@@ -162,3 +152,7 @@ func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err erro
 
 	return nil
 }
+
+func (daemon *Daemon) DeleteVolumes(c *Container) error {
+	return c.RemoveMountPoints()
+}

+ 14 - 2
daemon/inspect.go

@@ -10,6 +10,10 @@ import (
 type ContainerJSONRaw struct {
 	*Container
 	HostConfig *runconfig.HostConfig
+
+	// Unused fields for backward compatibility with API versions < 1.12.
+	Volumes   map[string]string
+	VolumesRW map[string]bool
 }
 
 func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error) {
@@ -48,6 +52,14 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
 		FinishedAt: container.State.FinishedAt,
 	}
 
+	volumes := make(map[string]string)
+	volumesRW := make(map[string]bool)
+
+	for _, m := range container.MountPoints {
+		volumes[m.Destination] = m.Path()
+		volumesRW[m.Destination] = m.RW
+	}
+
 	contJSON := &types.ContainerJSON{
 		Id:              container.ID,
 		Created:         container.Created,
@@ -67,8 +79,8 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
 		ExecDriver:      container.ExecDriver,
 		MountLabel:      container.MountLabel,
 		ProcessLabel:    container.ProcessLabel,
-		Volumes:         container.Volumes,
-		VolumesRW:       container.VolumesRW,
+		Volumes:         volumes,
+		VolumesRW:       volumesRW,
 		AppArmorProfile: container.AppArmorProfile,
 		ExecIDs:         container.GetExecIDs(),
 		HostConfig:      &hostConfig,

+ 185 - 216
daemon/volumes.go

@@ -1,213 +1,116 @@
 package daemon
 
 import (
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"sort"
 	"strings"
 
-	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/pkg/chrootarchive"
-	"github.com/docker/docker/pkg/mount"
-	"github.com/docker/docker/pkg/symlink"
+	"github.com/docker/docker/runconfig"
+	"github.com/docker/docker/volume"
+	volumedrivers "github.com/docker/docker/volume/drivers"
 )
 
-type volumeMount struct {
-	containerPath string
-	hostPath      string
-	writable      bool
-	copyData      bool
-	from          string
-}
-
-func (container *Container) createVolumes() error {
-	mounts := make(map[string]*volumeMount)
-
-	// get the normal volumes
-	for path := range container.Config.Volumes {
-		path = filepath.Clean(path)
-		// skip if there is already a volume for this container path
-		if _, exists := container.Volumes[path]; exists {
-			continue
-		}
-
-		realPath, err := container.GetResourcePath(path)
-		if err != nil {
-			return err
-		}
-		if stat, err := os.Stat(realPath); err == nil {
-			if !stat.IsDir() {
-				return fmt.Errorf("can't mount to container path, file exists - %s", path)
-			}
-		}
-
-		mnt := &volumeMount{
-			containerPath: path,
-			writable:      true,
-			copyData:      true,
-		}
-		mounts[mnt.containerPath] = mnt
-	}
-
-	// Get all the bind mounts
-	// track bind paths separately due to #10618
-	bindPaths := make(map[string]struct{})
-	for _, spec := range container.hostConfig.Binds {
-		mnt, err := parseBindMountSpec(spec)
-		if err != nil {
-			return err
-		}
-
-		// #10618
-		if _, exists := bindPaths[mnt.containerPath]; exists {
-			return fmt.Errorf("Duplicate volume mount %s", mnt.containerPath)
-		}
-
-		bindPaths[mnt.containerPath] = struct{}{}
-		mounts[mnt.containerPath] = mnt
-	}
-
-	// Get volumes from
-	for _, from := range container.hostConfig.VolumesFrom {
-		cID, mode, err := parseVolumesFromSpec(from)
-		if err != nil {
-			return err
-		}
-		if _, exists := container.AppliedVolumesFrom[cID]; exists {
-			// skip since it's already been applied
-			continue
-		}
+var localMountErr = fmt.Errorf("Invalid driver: %s driver doesn't support named volumes", volume.DefaultDriverName)
 
-		c, err := container.daemon.Get(cID)
-		if err != nil {
-			return fmt.Errorf("container %s not found, impossible to mount its volumes", cID)
-		}
+type mountPoint struct {
+	Name        string
+	Destination string
+	Driver      string
+	RW          bool
+	Volume      volume.Volume `json:"-"`
+	Source      string
+}
 
-		for _, mnt := range c.volumeMounts() {
-			mnt.writable = mnt.writable && (mode == "rw")
-			mnt.from = cID
-			mounts[mnt.containerPath] = mnt
-		}
+func (m *mountPoint) Setup() (string, error) {
+	if m.Volume != nil {
+		return m.Volume.Mount()
 	}
 
-	for _, mnt := range mounts {
-		containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, mnt.containerPath), container.basefs)
-		if err != nil {
-			return err
-		}
-
-		// Create the actual volume
-		v, err := container.daemon.volumes.FindOrCreateVolume(mnt.hostPath, mnt.writable)
-		if err != nil {
-			return err
-		}
-
-		container.VolumesRW[mnt.containerPath] = mnt.writable
-		container.Volumes[mnt.containerPath] = v.Path
-		v.AddContainer(container.ID)
-		if mnt.from != "" {
-			container.AppliedVolumesFrom[mnt.from] = struct{}{}
-		}
-
-		if mnt.writable && mnt.copyData {
-			// Copy whatever is in the container at the containerPath to the volume
-			copyExistingContents(containerMntPath, v.Path)
+	if len(m.Source) > 0 {
+		if _, err := os.Stat(m.Source); err != nil {
+			if !os.IsNotExist(err) {
+				return "", err
+			}
+			if err := os.MkdirAll(m.Source, 0755); err != nil {
+				return "", err
+			}
 		}
+		return m.Source, nil
 	}
 
-	return nil
+	return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
 }
 
-// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
-func (container *Container) sortedVolumeMounts() []string {
-	var mountPaths []string
-	for path := range container.Volumes {
-		mountPaths = append(mountPaths, path)
+func (m *mountPoint) Path() string {
+	if m.Volume != nil {
+		return m.Volume.Path()
 	}
 
-	sort.Strings(mountPaths)
-	return mountPaths
+	return m.Source
 }
 
-func (container *Container) VolumePaths() map[string]struct{} {
-	var paths = make(map[string]struct{})
-	for _, path := range container.Volumes {
-		paths[path] = struct{}{}
+func parseBindMount(spec string, config *runconfig.Config) (*mountPoint, error) {
+	bind := &mountPoint{
+		RW: true,
 	}
-	return paths
-}
-
-func (container *Container) registerVolumes() {
-	for path := range container.VolumePaths() {
-		if v := container.daemon.volumes.Get(path); v != nil {
-			v.AddContainer(container.ID)
-			continue
-		}
+	arr := strings.Split(spec, ":")
 
-		// if container was created with an old daemon, this volume may not be registered so we need to make sure it gets registered
-		writable := true
-		if rw, exists := container.VolumesRW[path]; exists {
-			writable = rw
-		}
-		v, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
-		if err != nil {
-			logrus.Debugf("error registering volume %s: %v", path, err)
-			continue
+	switch len(arr) {
+	case 2:
+		bind.Destination = arr[1]
+	case 3:
+		bind.Destination = arr[1]
+		if !validMountMode(arr[2]) {
+			return nil, fmt.Errorf("invalid mode for volumes-from: %s", arr[2])
 		}
-		v.AddContainer(container.ID)
+		bind.RW = arr[2] == "rw"
+	default:
+		return nil, fmt.Errorf("Invalid volume specification: %s", spec)
 	}
-}
 
-func (container *Container) derefVolumes() {
-	for path := range container.VolumePaths() {
-		vol := container.daemon.volumes.Get(path)
-		if vol == nil {
-			logrus.Debugf("Volume %s was not found and could not be dereferenced", path)
-			continue
+	if !filepath.IsAbs(arr[0]) {
+		bind.Driver, bind.Name = parseNamedVolumeInfo(arr[0], config)
+		if bind.Driver == volume.DefaultDriverName {
+			return nil, localMountErr
 		}
-		vol.RemoveContainer(container.ID)
+	} else {
+		bind.Source = filepath.Clean(arr[0])
 	}
-}
 
-func parseBindMountSpec(spec string) (*volumeMount, error) {
-	arr := strings.Split(spec, ":")
+	bind.Destination = filepath.Clean(bind.Destination)
+	return bind, nil
+}
 
-	mnt := &volumeMount{}
-	switch len(arr) {
+func parseNamedVolumeInfo(info string, config *runconfig.Config) (driver string, name string) {
+	p := strings.SplitN(info, "/", 2)
+	switch len(p) {
 	case 2:
-		mnt.hostPath = arr[0]
-		mnt.containerPath = arr[1]
-		mnt.writable = true
-	case 3:
-		mnt.hostPath = arr[0]
-		mnt.containerPath = arr[1]
-		mnt.writable = validMountMode(arr[2]) && arr[2] == "rw"
+		driver = p[0]
+		name = p[1]
 	default:
-		return nil, fmt.Errorf("Invalid volume specification: %s", spec)
-	}
-
-	if !filepath.IsAbs(mnt.hostPath) {
-		return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath)
+		if driver = config.VolumeDriver; len(driver) == 0 {
+			driver = volume.DefaultDriverName
+		}
+		name = p[0]
 	}
 
-	mnt.hostPath = filepath.Clean(mnt.hostPath)
-	mnt.containerPath = filepath.Clean(mnt.containerPath)
-	return mnt, nil
+	return
 }
 
-func parseVolumesFromSpec(spec string) (string, string, error) {
-	specParts := strings.SplitN(spec, ":", 2)
-	if len(specParts) == 0 {
+func parseVolumesFrom(spec string) (string, string, error) {
+	if len(spec) == 0 {
 		return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec)
 	}
 
-	var (
-		id   = specParts[0]
-		mode = "rw"
-	)
+	specParts := strings.SplitN(spec, ":", 2)
+	id := specParts[0]
+	mode := "rw"
+
 	if len(specParts) == 2 {
 		mode = specParts[1]
 		if !validMountMode(mode) {
@@ -222,7 +125,6 @@ func validMountMode(mode string) bool {
 		"rw": true,
 		"ro": true,
 	}
-
 	return validModes[mode]
 }
 
@@ -240,34 +142,16 @@ func (container *Container) specialMounts() []execdriver.Mount {
 	return mounts
 }
 
-func (container *Container) volumeMounts() map[string]*volumeMount {
-	mounts := make(map[string]*volumeMount)
-
-	for containerPath, path := range container.Volumes {
-		v := container.daemon.volumes.Get(path)
-		if v == nil {
-			// This should never happen
-			logrus.Debugf("reference by container %s to non-existent volume path %s", container.ID, path)
-			continue
-		}
-		mounts[containerPath] = &volumeMount{hostPath: path, containerPath: containerPath, writable: container.VolumesRW[containerPath]}
-	}
-
-	return mounts
-}
-
 func copyExistingContents(source, destination string) error {
 	volList, err := ioutil.ReadDir(source)
 	if err != nil {
 		return err
 	}
-
 	if len(volList) > 0 {
 		srcList, err := ioutil.ReadDir(destination)
 		if err != nil {
 			return err
 		}
-
 		if len(srcList) == 0 {
 			// If the source volume is empty copy files from the root into the volume
 			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
@@ -275,60 +159,145 @@ func copyExistingContents(source, destination string) error {
 			}
 		}
 	}
-
 	return copyOwnership(source, destination)
 }
 
-func (container *Container) mountVolumes() error {
-	for dest, source := range container.Volumes {
-		v := container.daemon.volumes.Get(source)
-		if v == nil {
-			return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest)
+// registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
+// It follows the next sequence to decide what to mount in each final destination:
+//
+// 1. Select the previously configured mount points for the containers, if any.
+// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
+// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
+func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
+	binds := map[string]bool{}
+	mountPoints := map[string]*mountPoint{}
+
+	// 1. Read already configured mount points.
+	for name, point := range container.MountPoints {
+		mountPoints[name] = point
+	}
+
+	// 2. Read volumes from other containers.
+	for _, v := range hostConfig.VolumesFrom {
+		containerID, mode, err := parseVolumesFrom(v)
+		if err != nil {
+			return err
 		}
 
-		destPath, err := container.GetResourcePath(dest)
+		c, err := daemon.Get(containerID)
 		if err != nil {
 			return err
 		}
 
-		if err := mount.Mount(source, destPath, "bind", "rbind,rw"); err != nil {
-			return fmt.Errorf("error while mounting volume %s: %v", source, err)
+		for _, m := range c.MountPoints {
+			cp := m
+			cp.RW = m.RW && mode != "ro"
+
+			if len(m.Source) == 0 {
+				v, err := createVolume(m.Name, m.Driver)
+				if err != nil {
+					return err
+				}
+				cp.Volume = v
+			}
+
+			mountPoints[cp.Destination] = cp
 		}
 	}
 
-	for _, mnt := range container.specialMounts() {
-		destPath, err := container.GetResourcePath(mnt.Destination)
+	// 3. Read bind mounts
+	for _, b := range hostConfig.Binds {
+		// #10618
+		bind, err := parseBindMount(b, container.Config)
 		if err != nil {
 			return err
 		}
-		if err := mount.Mount(mnt.Source, destPath, "bind", "bind,rw"); err != nil {
-			return fmt.Errorf("error while mounting volume %s: %v", mnt.Source, err)
+
+		if binds[bind.Destination] {
+			return fmt.Errorf("Duplicate bind mount %s", bind.Destination)
 		}
+
+		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
+			v, err := createVolume(bind.Name, bind.Driver)
+			if err != nil {
+				return err
+			}
+			bind.Volume = v
+		}
+
+		binds[bind.Destination] = true
+		mountPoints[bind.Destination] = bind
 	}
+
+	container.MountPoints = mountPoints
+
 	return nil
 }
 
-func (container *Container) unmountVolumes() {
-	for dest := range container.Volumes {
-		destPath, err := container.GetResourcePath(dest)
-		if err != nil {
-			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
-			continue
-		}
-		if err := mount.ForceUnmount(destPath); err != nil {
-			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
-			continue
+// verifyOldVolumesInfo ports volumes configured for the containers pre docker 1.7.
+// It reads the container configuration and creates valid mount points for the old volumes.
+func (daemon *Daemon) verifyOldVolumesInfo(container *Container) error {
+	jsonPath, err := container.jsonPath()
+	if err != nil {
+		return err
+	}
+	f, err := os.Open(jsonPath)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil
 		}
+		return err
 	}
 
-	for _, mnt := range container.specialMounts() {
-		destPath, err := container.GetResourcePath(mnt.Destination)
-		if err != nil {
-			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
-			continue
-		}
-		if err := mount.ForceUnmount(destPath); err != nil {
-			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
+	type oldContVolCfg struct {
+		Volumes   map[string]string
+		VolumesRW map[string]bool
+	}
+
+	vols := oldContVolCfg{
+		Volumes:   make(map[string]string),
+		VolumesRW: make(map[string]bool),
+	}
+	if err := json.NewDecoder(f).Decode(&vols); err != nil {
+		return err
+	}
+
+	for destination, hostPath := range vols.Volumes {
+		vfsPath := filepath.Join(daemon.root, "vfs", "dir")
+
+		if strings.HasPrefix(hostPath, vfsPath) {
+			id := filepath.Base(hostPath)
+
+			container.AddLocalMountPoint(id, destination, vols.VolumesRW[destination])
 		}
 	}
+
+	return container.ToDisk()
+}
+
+func createVolume(name, driverName string) (volume.Volume, error) {
+	vd, err := getVolumeDriver(driverName)
+	if err != nil {
+		return nil, err
+	}
+	return vd.Create(name)
+}
+
+func removeVolume(v volume.Volume) error {
+	vd, err := getVolumeDriver(v.DriverName())
+	if err != nil {
+		return nil
+	}
+	return vd.Remove(v)
+}
+
+func getVolumeDriver(name string) (volume.Driver, error) {
+	if name == "" {
+		name = volume.DefaultDriverName
+	}
+	vd := volumedrivers.Lookup(name)
+	if vd == nil {
+		return nil, fmt.Errorf("Volumes Driver %s isn't registered", name)
+	}
+	return vd, nil
 }

+ 36 - 25
daemon/volumes_linux.go

@@ -4,6 +4,9 @@ package daemon
 
 import (
 	"os"
+	"path/filepath"
+	"sort"
+	"strings"
 
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/pkg/system"
@@ -24,36 +27,44 @@ func copyOwnership(source, destination string) error {
 	return os.Chmod(destination, os.FileMode(stat.Mode()))
 }
 
-func (container *Container) prepareVolumes() error {
-	if container.Volumes == nil || len(container.Volumes) == 0 {
-		container.Volumes = make(map[string]string)
-		container.VolumesRW = make(map[string]bool)
-	}
+func (container *Container) setupMounts() ([]execdriver.Mount, error) {
+	var mounts []execdriver.Mount
+	for _, m := range container.MountPoints {
+		path, err := m.Setup()
+		if err != nil {
+			return nil, err
+		}
 
-	if len(container.hostConfig.VolumesFrom) > 0 && container.AppliedVolumesFrom == nil {
-		container.AppliedVolumesFrom = make(map[string]struct{})
+		mounts = append(mounts, execdriver.Mount{
+			Source:      path,
+			Destination: m.Destination,
+			Writable:    m.RW,
+		})
 	}
-	return container.createVolumes()
+
+	mounts = sortMounts(mounts)
+	return append(mounts, container.networkMounts()...), nil
 }
 
-func (container *Container) setupMounts() error {
-	mounts := []execdriver.Mount{}
+func sortMounts(m []execdriver.Mount) []execdriver.Mount {
+	sort.Sort(mounts(m))
+	return m
+}
 
-	// Mount user specified volumes
-	// Note, these are not private because you may want propagation of (un)mounts from host
-	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
-	// want this new mount in the container
-	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
-	for _, path := range container.sortedVolumeMounts() {
-		mounts = append(mounts, execdriver.Mount{
-			Source:      container.Volumes[path],
-			Destination: path,
-			Writable:    container.VolumesRW[path],
-		})
-	}
+type mounts []execdriver.Mount
+
+func (m mounts) Len() int {
+	return len(m)
+}
 
-	mounts = append(mounts, container.specialMounts()...)
+func (m mounts) Less(i, j int) bool {
+	return m.parts(i) < m.parts(j)
+}
+
+func (m mounts) Swap(i, j int) {
+	m[i], m[j] = m[j], m[i]
+}
 
-	container.command.Mounts = mounts
-	return nil
+func (m mounts) parts(i int) int {
+	return len(strings.Split(filepath.Clean(m[i].Destination), string(os.PathSeparator)))
 }

+ 146 - 0
daemon/volumes_unit_test.go

@@ -0,0 +1,146 @@
+package daemon
+
+import (
+	"testing"
+
+	"github.com/docker/docker/runconfig"
+	"github.com/docker/docker/volume"
+	volumedrivers "github.com/docker/docker/volume/drivers"
+)
+
+func TestParseNamedVolumeInfo(t *testing.T) {
+	cases := []struct {
+		driver    string
+		name      string
+		expDriver string
+		expName   string
+	}{
+		{"", "name", "local", "name"},
+		{"external", "name", "external", "name"},
+		{"", "external/name", "external", "name"},
+		{"ignored", "external/name", "external", "name"},
+	}
+
+	for _, c := range cases {
+		conf := &runconfig.Config{VolumeDriver: c.driver}
+		driver, name := parseNamedVolumeInfo(c.name, conf)
+
+		if driver != c.expDriver {
+			t.Fatalf("Expected %s, was %s\n", c.expDriver, driver)
+		}
+
+		if name != c.expName {
+			t.Fatalf("Expected %s, was %s\n", c.expName, name)
+		}
+	}
+}
+
+func TestParseBindMount(t *testing.T) {
+	cases := []struct {
+		bind      string
+		driver    string
+		expDest   string
+		expSource string
+		expName   string
+		expDriver string
+		expRW     bool
+		fail      bool
+	}{
+		{"/tmp:/tmp", "", "/tmp", "/tmp", "", "", true, false},
+		{"/tmp:/tmp:ro", "", "/tmp", "/tmp", "", "", false, false},
+		{"/tmp:/tmp:rw", "", "/tmp", "/tmp", "", "", true, false},
+		{"/tmp:/tmp:foo", "", "/tmp", "/tmp", "", "", false, true},
+		{"name:/tmp", "", "", "", "", "", false, true},
+		{"name:/tmp", "external", "/tmp", "", "name", "external", true, false},
+		{"external/name:/tmp:rw", "", "/tmp", "", "name", "external", true, false},
+		{"external/name:/tmp:ro", "", "/tmp", "", "name", "external", false, false},
+		{"external/name:/tmp:foo", "", "/tmp", "", "name", "external", false, true},
+		{"name:/tmp", "local", "", "", "", "", false, true},
+		{"local/name:/tmp:rw", "", "", "", "", "", true, true},
+	}
+
+	for _, c := range cases {
+		conf := &runconfig.Config{VolumeDriver: c.driver}
+		m, err := parseBindMount(c.bind, conf)
+		if c.fail {
+			if err == nil {
+				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
+			}
+			continue
+		}
+
+		if m.Destination != c.expDest {
+			t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
+		}
+
+		if m.Source != c.expSource {
+			t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
+		}
+
+		if m.Name != c.expName {
+			t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
+		}
+
+		if m.Driver != c.expDriver {
+			t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
+		}
+
+		if m.RW != c.expRW {
+			t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
+		}
+	}
+}
+
+func TestParseVolumeFrom(t *testing.T) {
+	cases := []struct {
+		spec    string
+		expId   string
+		expMode string
+		fail    bool
+	}{
+		{"", "", "", true},
+		{"foobar", "foobar", "rw", false},
+		{"foobar:rw", "foobar", "rw", false},
+		{"foobar:ro", "foobar", "ro", false},
+		{"foobar:baz", "", "", true},
+	}
+
+	for _, c := range cases {
+		id, mode, err := parseVolumesFrom(c.spec)
+		if c.fail {
+			if err == nil {
+				t.Fatalf("Expected error, was nil, for spec %s\n", c.spec)
+			}
+			continue
+		}
+
+		if id != c.expId {
+			t.Fatalf("Expected id %s, was %s, for spec %s\n", c.expId, id, c.spec)
+		}
+		if mode != c.expMode {
+			t.Fatalf("Expected mode %s, was %s for spec %s\n", c.expMode, mode, c.spec)
+		}
+	}
+}
+
+type fakeDriver struct{}
+
+func (fakeDriver) Name() string                              { return "fake" }
+func (fakeDriver) Create(name string) (volume.Volume, error) { return nil, nil }
+func (fakeDriver) Remove(v volume.Volume) error              { return nil }
+
+func TestGetVolumeDriver(t *testing.T) {
+	_, err := getVolumeDriver("missing")
+	if err == nil {
+		t.Fatal("Expected error, was nil")
+	}
+
+	volumedrivers.Register(fakeDriver{}, "fake")
+	d, err := getVolumeDriver("fake")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if d.Name() != "fake" {
+		t.Fatalf("Expected fake driver, got %s\n", d.Name())
+	}
+}

+ 4 - 6
daemon/volumes_windows.go

@@ -2,15 +2,13 @@
 
 package daemon
 
+import "github.com/docker/docker/daemon/execdriver"
+
 // Not supported on Windows
 func copyOwnership(source, destination string) error {
-	return nil
-}
-
-func (container *Container) prepareVolumes() error {
-	return nil
+	return nil, nil
 }
 
-func (container *Container) setupMounts() error {
+func (container *Container) setupMounts() ([]execdriver.Mount, error) {
 	return nil
 }

+ 2 - 0
docs/mkdocs.yml

@@ -73,6 +73,7 @@ pages:
 - ['machine/index.md', 'User Guide', 'Docker Machine' ]
 - ['swarm/index.md', 'User Guide', 'Docker Swarm' ]
 - ['kitematic/userguide.md', 'User Guide', 'Kitematic']
+- ['userguide/plugins.md', 'User Guide', 'Docker Plugins']
 
 # Docker Hub docs:
 - ['docker-hub/index.md', 'Docker Hub', 'Docker Hub' ]
@@ -185,6 +186,7 @@ pages:
 - ['reference/api/docker_remote_api_v1.0.md', '**HIDDEN**']
 - ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API client libraries']
 - ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub accounts API']
+- ['reference/api/plugin_api.md', 'Reference', 'Docker Plugin API']
 - ['kitematic/faq.md', 'Reference', 'Kitematic: FAQ']
 - ['kitematic/known-issues.md', 'Reference', 'Kitematic: Known issues']
 

+ 1 - 0
docs/sources/reference/api/README.md

@@ -7,3 +7,4 @@ This directory holds the authoritative specifications of APIs defined and implem
    index for images to download
  * The docker.io OAuth and accounts API which 3rd party services can
    use to access account information
+ * The plugin API for Docker Plugins

+ 7 - 0
docs/sources/reference/api/docker_remote_api.md

@@ -73,6 +73,13 @@ are now returned as boolean instead of as an int.
 In addition, the end point now returns the new boolean fields
 `CpuCfsPeriod`, `CpuCfsQuota`, and `OomKillDisable`.
 
+**New!**
+
+You can now specify a volume plugin in `/v1.19/containers/create`, for example
+`"HostConfig": {"Binds": ["flocker/name:/data"]}` where `flocker` is the name
+of the plugin, `name` is the user-facing name of the volume (passed to the
+volume plugin) and `/data` is the mountpoint inside the container.
+
 ## v1.18
 
 ### Full documentation

+ 5 - 2
docs/sources/reference/api/docker_remote_api_v1.19.md

@@ -226,8 +226,11 @@ Json Parameters:
     -   **Binds** – A list of volume bindings for this container. Each volume
             binding is a string of the form `container_path` (to create a new
             volume for the container), `host_path:container_path` (to bind-mount
-            a host path into the container), or `host_path:container_path:ro`
-            (to make the bind-mount read-only inside the container).
+            a host path into the container), `host_path:container_path:ro`
+            (to make the bind-mount read-only inside the container), or
+            `volume_plugin/volume_name:container_path` (to provision a
+            volume named `volume_name` from a [volume plugin](/userguide/plugins)
+            named `volume_plugin`).
     -   **Links** - A list of links for the container. Each link entry should be
           in the form of `container_name:alias`.
     -   **LxcConf** - LXC specific configurations. These configurations will only

+ 223 - 0
docs/sources/reference/api/plugin_api.md

@@ -0,0 +1,223 @@
+page_title: Plugin API documentation
+page_description: Documentation for writing a Docker plugin.
+page_keywords: docker, plugins, api, extensions
+
+# Docker Plugin API
+
+Docker plugins are out-of-process extensions which add capabilities to the
+Docker Engine.
+
+This page is intended for people who want to develop their own Docker plugin.
+If you just want to learn about or use Docker plugins, look
+[here](/userguide/plugins).
+
+## What plugins are
+
+A plugin is a process running on the same docker host as the docker daemon,
+which registers itself by placing a file in `/usr/share/docker/plugins` (the
+"plugin directory").
+
+Plugins have human-readable names, which are short, lowercase strings. For
+example, `flocker` or `weave`.
+
+Plugins can run inside or outside containers. Currently running them outside
+containers is recommended.
+
+## Plugin discovery
+
+Docker discovers plugins by looking for them in the plugin directory whenever a
+user or container tries to use one by name.
+
+There are two types of files which can be put in the plugin directory.
+
+* `.sock` files are UNIX domain sockets.
+* `.spec` files are text files containing a URL, such as `unix:///other.sock`.
+
+The name of the file (excluding the extension) determines the plugin name.
+
+For example, the `flocker` plugin might create a UNIX socket at
+`/usr/share/docker/plugins/flocker.sock`.
+
+Plugins must be run locally on the same machine as the Docker daemon.  UNIX
+domain sockets are strongly encouraged for security reasons.
+
+## Plugin lifecycle
+
+Plugins should be started before Docker, and stopped after Docker.  For
+example, when packaging a plugin for a platform which supports `systemd`, you
+might use [`systemd` dependencies](
+http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=) to
+manage startup and shutdown order.
+
+When upgrading a plugin, you should first stop the Docker daemon, upgrade the
+plugin, then start Docker again.
+
+If a plugin is packaged as a container, this may cause issues. Plugins as
+containers are currently considered experimental due to these shutdown/startup
+ordering issues. These issues are mitigated by plugin retries (see below).
+
+## Plugin activation
+
+When a plugin is first referred to -- either by a user referring to it by name
+(e.g.  `docker run --volume-driver=foo`) or a container already configured to
+use a plugin being started -- Docker looks for the named plugin in the plugin
+directory and activates it with a handshake. See Handshake API below.
+
+Plugins are *not* activated automatically at Docker daemon startup. Rather,
+they are activated only lazily, or on-demand, when they are needed.
+
+## API design
+
+The Plugin API is RPC-style JSON over HTTP, much like webhooks.
+
+Requests flow *from* the Docker daemon *to* the plugin.  So the plugin needs to
+implement an HTTP server and bind this to the UNIX socket mentioned in the
+"plugin discovery" section.
+
+All requests are HTTP `POST` requests.
+
+The API is versioned via an Accept header, which currently is always set to
+`application/vnd.docker.plugins.v1+json`.
+
+## Handshake API
+
+Plugins are activated via the following "handshake" API call.
+
+### /Plugin.Activate
+
+**Request:** empty body
+
+**Response:**
+```
+{
+    "Implements": ["VolumeDriver"]
+}
+```
+
+Responds with a list of Docker subsystems which this plugin implements.
+After activation, the plugin will then be sent events from this subsystem.
+
+## Volume API
+
+If a plugin registers itself as a `VolumeDriver` (see above) then it is
+expected to provide writeable paths on the host filesystem for the Docker
+daemon to provide to containers to consume.
+
+The Docker daemon handles bind-mounting the provided paths into user
+containers.
+
+### /VolumeDriver.Create
+
+**Request**:
+```
+{
+    "Name": "volume_name"
+}
+```
+
+Instruct the plugin that the user wants to create a volume, given a user
+specified volume name.  The plugin does not need to actually manifest the
+volume on the filesystem yet (until Mount is called).
+
+**Response**:
+```
+{
+    "Err": null
+}
+```
+
+Respond with a string error if an error occurred.
+
+### /VolumeDriver.Remove
+
+**Request**:
+```
+{
+    "Name": "volume_name"
+}
+```
+
+Create a volume, given a user specified volume name.
+
+**Response**:
+```
+{
+    "Err": null
+}
+```
+
+Respond with a string error if an error occurred.
+
+### /VolumeDriver.Mount
+
+**Request**:
+```
+{
+    "Name": "volume_name"
+}
+```
+
+Docker requires the plugin to provide a volume, given a user specified volume
+name. This is called once per container start.
+
+**Response**:
+```
+{
+    "Mountpoint": "/path/to/directory/on/host",
+    "Err": null
+}
+```
+
+Respond with the path on the host filesystem where the volume has been made
+available, and/or a string error if an error occurred.
+
+### /VolumeDriver.Path
+
+**Request**:
+```
+{
+    "Name": "volume_name"
+}
+```
+
+Docker needs reminding of the path to the volume on the host.
+
+**Response**:
+```
+{
+    "Mountpoint": "/path/to/directory/on/host",
+    "Err": null
+}
+```
+
+Respond with the path on the host filesystem where the volume has been made
+available, and/or a string error if an error occurred.
+
+### /VolumeDriver.Unmount
+
+**Request**:
+```
+{
+    "Name": "volume_name"
+}
+```
+
+Indication that Docker no longer is using the named volume. This is called once
+per container stop.  Plugin may deduce that it is safe to deprovision it at
+this point.
+
+**Response**:
+```
+{
+    "Err": null
+}
+```
+
+Respond with a string error if an error occurred.
+
+## Plugin retries
+
+Attempts to call a method on a plugin are retried with an exponential backoff
+for up to 30 seconds. This may help when packaging plugins as containers, since
+it gives plugin containers a chance to start up before failing any user
+containers which depend on them.

+ 16 - 2
docs/sources/reference/commandline/cli.md

@@ -1000,7 +1000,8 @@ Creates a new container.
       --security-opt=[]          Security options
       -t, --tty=false            Allocate a pseudo-TTY
       -u, --user=""              Username or UID
-      -v, --volume=[]            Bind mount a volume
+      -v, --volume=[]            Bind mount a volume, or specify name for volume plugin
+      --volume-driver=           Optional volume driver (plugin name) for the container
       --volumes-from=[]          Mount volumes from the specified container(s)
       -w, --workdir=""           Working directory inside the container
 
@@ -1970,7 +1971,8 @@ To remove an image using its digest:
       --sig-proxy=true           Proxy received signals to the process
       -t, --tty=false            Allocate a pseudo-TTY
       -u, --user=""              Username or UID (format: <name|uid>[:<group|gid>])
-      -v, --volume=[]            Bind mount a volume
+      -v, --volume=[]            Bind mount a volume, or specify name for volume plugin
+      --volume-driver=           Optional volume driver (plugin name) for the container
       --volumes-from=[]          Mount volumes from the specified container(s)
       -w, --workdir=""           Working directory inside the container
 
@@ -2066,6 +2068,18 @@ binary (such as that provided by [https://get.docker.com](
 https://get.docker.com)), you give the container the full access to create and
 manipulate the host's Docker daemon.
 
+    $ docker run -ti -v volumename:/data --volume-driver=flocker busybox sh
+
+By specifying a volume name in conjunction with a volume driver, volume plugins
+such as [Flocker](https://clusterhq.com/docker-plugin/), once installed, can be
+used to manage volumes external to a single host, such as those on EBS. In this
+example, "volumename" is passed through to the volume plugin as a user-given
+name for the volume which allows the plugin to associate it with an external
+volume beyond the lifetime of a single container or container host. This can be
+used, for example, to move a stateful container from one server to another.
+
+The `volumename` must not begin with a `/`.
+
     $ docker run -p 127.0.0.1:80:8080 ubuntu bash
 
 This binds port `8080` of the container to port `80` on `127.0.0.1` of

+ 8 - 0
docs/sources/userguide/dockervolumes.md

@@ -210,6 +210,14 @@ Then un-tar the backup file in the new container's data volume.
 You can use the techniques above to automate backup, migration and
 restore testing using your preferred tools.
 
+## Integrating Docker with external storage systems
+
+Docker volume plugins such as [Flocker](https://clusterhq.com/docker-plugin/)
+enable Docker deployments to be integrated with external storage systems, such
+as Amazon EBS, and enable data volumes to persist beyond the lifetime of a
+single Docker host. See the [plugin section of the user
+guide](/userguide/plugins) for more information.
+
 # Next steps
 
 Now we've learned a bit more about how to use Docker we're going to see how to

+ 6 - 0
docs/sources/userguide/index.md

@@ -105,6 +105,12 @@ works with Docker can now transparently scale up to multiple hosts.
 
 Go to [Docker Swarm user guide](/swarm/).
 
+## Docker Plugins
+
+Docker plugins allow you to extend the capabilities of the Docker Engine.
+
+Go to [Docker Plugins](/userguide/plugins).
+
 ## Getting help
 
 * [Docker homepage](http://www.docker.com/)

+ 51 - 0
docs/sources/userguide/plugins.md

@@ -0,0 +1,51 @@
+page_title: Docker Plugins
+page_description: Learn what Docker Plugins are and how to use them.
+page_keywords: plugins, extensions, extensibility
+
+# Understanding Docker Plugins
+
+You can extend the capabilities of the Docker Engine by loading third-party
+plugins.
+
+## Types of plugins
+
+Plugins extend Docker's functionality.  They come in specific types.  For
+example, a **volume plugin** might enable Docker volumes to persist across
+multiple Docker hosts.
+
+Currently Docker supports **volume plugins**. In the future it will support
+additional plugin types.
+
+## Installing a plugin
+
+Follow the instructions in the plugin's documentation.
+
+## Finding a plugin
+
+The following plugins exist:
+
+* The [Flocker plugin](https://clusterhq.com/docker-plugin/) is a volume plugin
+  which provides multi-host portable volumes for Docker, enabling you to run
+  databases and other stateful containers and move them around across a cluster
+  of machines.
+
+## Using a plugin
+
+Depending on the plugin type, there are additional arguments to `docker` CLI
+commands.
+
+* For example `docker run` has a [`--volume-driver` argument](
+  /reference/commandline/cli/#run).
+
+You can also use plugins via the [Docker Remote API](
+/reference/api/docker_remote_api/).
+
+## Troubleshooting a plugin
+
+If you are having problems with Docker after loading a plugin, ask the authors
+of the plugin for help. The Docker team may not be able to assist you.
+
+## Writing a plugin
+
+If you are interested in writing a plugin for Docker, or seeing how they work
+under the hood, see the [docker plugins reference](/reference/api/plugin_api).

+ 1 - 44
integration-cli/docker_api_containers_test.go

@@ -166,7 +166,7 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) {
 	c.Assert(status, check.Equals, http.StatusInternalServerError)
 	c.Assert(err, check.IsNil)
 
-	if !strings.Contains(string(body), "Duplicate volume") {
+	if !strings.Contains(string(body), "Duplicate bind") {
 		c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err)
 	}
 }
@@ -210,49 +210,6 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
 	}
 }
 
-// Ensure that volumes-from has priority over binds/anything else
-// This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start
-func (s *DockerSuite) TestVolumesFromHasPriority(c *check.C) {
-	volName := "voltst2"
-	volPath := "/tmp"
-
-	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
-		c.Fatal(out, err)
-	}
-
-	name := "testing"
-	config := map[string]interface{}{
-		"Image":   "busybox",
-		"Volumes": map[string]struct{}{volPath: {}},
-	}
-
-	status, _, err := sockRequest("POST", "/containers/create?name="+name, config)
-	c.Assert(status, check.Equals, http.StatusCreated)
-	c.Assert(err, check.IsNil)
-
-	bindPath := randomUnixTmpDirPath("test")
-	config = map[string]interface{}{
-		"VolumesFrom": []string{volName},
-		"Binds":       []string{bindPath + ":/tmp"},
-	}
-	status, _, err = sockRequest("POST", "/containers/"+name+"/start", config)
-	c.Assert(status, check.Equals, http.StatusNoContent)
-	c.Assert(err, check.IsNil)
-
-	pth, err := inspectFieldMap(name, "Volumes", volPath)
-	if err != nil {
-		c.Fatal(err)
-	}
-	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
-	if err != nil {
-		c.Fatal(err)
-	}
-
-	if pth != pth2 {
-		c.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
-	}
-}
-
 func (s *DockerSuite) TestGetContainerStats(c *check.C) {
 	var (
 		name   = "statscontainer"

+ 0 - 99
integration-cli/docker_cli_daemon_test.go

@@ -284,35 +284,6 @@ func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *check.C) {
 	}
 }
 
-// #9629
-func (s *DockerDaemonSuite) TestDaemonVolumesBindsRefs(c *check.C) {
-	if err := s.d.StartWithBusybox(); err != nil {
-		c.Fatal(err)
-	}
-
-	tmp, err := ioutil.TempDir(os.TempDir(), "")
-	if err != nil {
-		c.Fatal(err)
-	}
-	defer os.RemoveAll(tmp)
-
-	if err := ioutil.WriteFile(tmp+"/test", []byte("testing"), 0655); err != nil {
-		c.Fatal(err)
-	}
-
-	if out, err := s.d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil {
-		c.Fatal(err, out)
-	}
-
-	if err := s.d.Restart(); err != nil {
-		c.Fatal(err)
-	}
-
-	if out, err := s.d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil {
-		c.Fatal(err, out)
-	}
-}
-
 func (s *DockerDaemonSuite) TestDaemonKeyGeneration(c *check.C) {
 	// TODO: skip or update for Windows daemon
 	os.Remove("/etc/docker/key.json")
@@ -360,76 +331,6 @@ func (s *DockerDaemonSuite) TestDaemonKeyMigration(c *check.C) {
 	}
 }
 
-// Simulate an older daemon (pre 1.3) coming up with volumes specified in containers
-//	without corresponding volume json
-func (s *DockerDaemonSuite) TestDaemonUpgradeWithVolumes(c *check.C) {
-	graphDir := filepath.Join(os.TempDir(), "docker-test")
-	defer os.RemoveAll(graphDir)
-	if err := s.d.StartWithBusybox("-g", graphDir); err != nil {
-		c.Fatal(err)
-	}
-
-	tmpDir := filepath.Join(os.TempDir(), "test")
-	defer os.RemoveAll(tmpDir)
-
-	if out, err := s.d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil {
-		c.Fatal(err, out)
-	}
-
-	if err := s.d.Stop(); err != nil {
-		c.Fatal(err)
-	}
-
-	// Remove this since we're expecting the daemon to re-create it too
-	if err := os.RemoveAll(tmpDir); err != nil {
-		c.Fatal(err)
-	}
-
-	configDir := filepath.Join(graphDir, "volumes")
-
-	if err := os.RemoveAll(configDir); err != nil {
-		c.Fatal(err)
-	}
-
-	if err := s.d.Start("-g", graphDir); err != nil {
-		c.Fatal(err)
-	}
-
-	if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
-		c.Fatalf("expected volume path %s to exist but it does not", tmpDir)
-	}
-
-	dir, err := ioutil.ReadDir(configDir)
-	if err != nil {
-		c.Fatal(err)
-	}
-	if len(dir) == 0 {
-		c.Fatalf("expected volumes config dir to contain data for new volume")
-	}
-
-	// Now with just removing the volume config and not the volume data
-	if err := s.d.Stop(); err != nil {
-		c.Fatal(err)
-	}
-
-	if err := os.RemoveAll(configDir); err != nil {
-		c.Fatal(err)
-	}
-
-	if err := s.d.Start("-g", graphDir); err != nil {
-		c.Fatal(err)
-	}
-
-	dir, err = ioutil.ReadDir(configDir)
-	if err != nil {
-		c.Fatal(err)
-	}
-
-	if len(dir) == 0 {
-		c.Fatalf("expected volumes config dir to contain data for new volume")
-	}
-}
-
 // GH#11320 - verify that the daemon exits on failure properly
 // Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means
 // to get a daemon init failure; no other tests for -b/--bip conflict are therefore required

+ 10 - 54
integration-cli/docker_cli_run_test.go

@@ -395,21 +395,6 @@ func (s *DockerSuite) TestRunModeNetContainerHostname(c *check.C) {
 	}
 }
 
-// Regression test for #4741
-func (s *DockerSuite) TestRunWithVolumesAsFiles(c *check.C) {
-	runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/etc/hosts:/target-file", "busybox", "true")
-	out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
-	if err != nil && exitCode != 0 {
-		c.Fatal("1", out, stderr, err)
-	}
-
-	runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/target-file")
-	out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd)
-	if err != nil && exitCode != 0 {
-		c.Fatal("2", out, stderr, err)
-	}
-}
-
 // Regression test for #4979
 func (s *DockerSuite) TestRunWithVolumesFromExited(c *check.C) {
 	runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/some/dir", "busybox", "touch", "/some/dir/file")
@@ -536,7 +521,7 @@ func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) {
 	if out, _, err := runCommandWithOutput(cmd); err == nil {
 		c.Fatal("Expected error about duplicate volume definitions")
 	} else {
-		if !strings.Contains(out, "Duplicate volume") {
+		if !strings.Contains(out, "Duplicate bind mount") {
 			c.Fatalf("Expected 'duplicate volume' error, got %v", err)
 		}
 	}
@@ -2333,7 +2318,13 @@ func (s *DockerSuite) TestRunMountOrdering(c *check.C) {
 		c.Fatal(err)
 	}
 
-	cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp", tmpDir), "-v", fmt.Sprintf("%s:/tmp/foo", fooDir), "-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2), "-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir), "busybox:latest", "sh", "-c", "ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me")
+	cmd := exec.Command(dockerBinary, "run",
+		"-v", fmt.Sprintf("%s:/tmp", tmpDir),
+		"-v", fmt.Sprintf("%s:/tmp/foo", fooDir),
+		"-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2),
+		"-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir),
+		"busybox:latest", "sh", "-c",
+		"ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me")
 	out, _, err := runCommandWithOutput(cmd)
 	if err != nil {
 		c.Fatal(out, err)
@@ -2427,41 +2418,6 @@ func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) {
 	}
 }
 
-func (s *DockerSuite) TestRunVolumesNotRecreatedOnStart(c *check.C) {
-	testRequires(c, SameHostDaemon)
-
-	// Clear out any remnants from other tests
-	info, err := ioutil.ReadDir(volumesConfigPath)
-	if err != nil {
-		c.Fatal(err)
-	}
-	if len(info) > 0 {
-		for _, f := range info {
-			if err := os.RemoveAll(volumesConfigPath + "/" + f.Name()); err != nil {
-				c.Fatal(err)
-			}
-		}
-	}
-
-	cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "--name", "lone_starr", "busybox")
-	if _, err := runCommand(cmd); err != nil {
-		c.Fatal(err)
-	}
-
-	cmd = exec.Command(dockerBinary, "start", "lone_starr")
-	if _, err := runCommand(cmd); err != nil {
-		c.Fatal(err)
-	}
-
-	info, err = ioutil.ReadDir(volumesConfigPath)
-	if err != nil {
-		c.Fatal(err)
-	}
-	if len(info) != 1 {
-		c.Fatalf("Expected only 1 volume have %v", len(info))
-	}
-}
-
 func (s *DockerSuite) TestRunNoOutputFromPullInStdout(c *check.C) {
 	// just run with unknown image
 	cmd := exec.Command(dockerBinary, "run", "asdfsg")
@@ -2496,7 +2452,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
 
 	out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo")
 	c.Assert(err, check.IsNil)
-	if !strings.Contains(out, volumesStoragePath) {
+	if !strings.Contains(out, volumesConfigPath) {
 		c.Fatalf("Volume was not defined for /foo\n%q", out)
 	}
 
@@ -2507,7 +2463,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
 	}
 	out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar")
 	c.Assert(err, check.IsNil)
-	if !strings.Contains(out, volumesStoragePath) {
+	if !strings.Contains(out, volumesConfigPath) {
 		c.Fatalf("Volume was not defined for /bar\n%q", out)
 	}
 }

+ 0 - 26
integration-cli/docker_cli_start_test.go

@@ -126,32 +126,6 @@ func (s *DockerSuite) TestStartRecordError(c *check.C) {
 
 }
 
-// gh#8726: a failed Start() breaks --volumes-from on subsequent Start()'s
-func (s *DockerSuite) TestStartVolumesFromFailsCleanly(c *check.C) {
-
-	// Create the first data volume
-	dockerCmd(c, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox")
-
-	// Expect this to fail because the data test after contaienr doesn't exist yet
-	if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "consumer", "--volumes-from", "data_before", "--volumes-from", "data_after", "busybox")); err == nil {
-		c.Fatal("Expected error but got none")
-	}
-
-	// Create the second data volume
-	dockerCmd(c, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox")
-
-	// Now, all the volumes should be there
-	dockerCmd(c, "start", "consumer")
-
-	// Check that we have the volumes we want
-	out, _ := dockerCmd(c, "inspect", "--format='{{ len .Volumes }}'", "consumer")
-	nVolumes := strings.Trim(out, " \r\n'")
-	if nVolumes != "2" {
-		c.Fatalf("Missing volumes: expected 2, got %s", nVolumes)
-	}
-
-}
-
 func (s *DockerSuite) TestStartPausedContainer(c *check.C) {
 	defer unpauseAllContainers()
 

+ 150 - 0
integration-cli/docker_cli_start_volume_driver_unix_test.go

@@ -0,0 +1,150 @@
+// +build !windows
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"github.com/go-check/check"
+)
+
+func init() {
+	check.Suite(&ExternalVolumeSuite{
+		ds: &DockerSuite{},
+	})
+}
+
+type ExternalVolumeSuite struct {
+	server *httptest.Server
+	ds     *DockerSuite
+}
+
+func (s *ExternalVolumeSuite) SetUpTest(c *check.C) {
+	s.ds.SetUpTest(c)
+}
+
+func (s *ExternalVolumeSuite) TearDownTest(c *check.C) {
+	s.ds.TearDownTest(c)
+}
+
+func (s *ExternalVolumeSuite) SetUpSuite(c *check.C) {
+	mux := http.NewServeMux()
+	s.server = httptest.NewServer(mux)
+
+	type pluginRequest struct {
+		name string
+	}
+
+	hostVolumePath := func(name string) string {
+		return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
+	}
+
+	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+		fmt.Fprintln(w, `{"Implements": ["VolumeDriver"]}`)
+	})
+
+	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+		fmt.Fprintln(w, `{}`)
+	})
+
+	mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+		fmt.Fprintln(w, `{}`)
+	})
+
+	mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
+		var pr pluginRequest
+		if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
+
+		p := hostVolumePath(pr.name)
+
+		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+		fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
+	})
+
+	mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
+		var pr pluginRequest
+		if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
+
+		p := hostVolumePath(pr.name)
+		if err := os.MkdirAll(p, 0755); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
+
+		if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
+
+		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+		fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
+	})
+
+	mux.HandleFunc("/VolumeDriver.Umount", func(w http.ResponseWriter, r *http.Request) {
+		var pr pluginRequest
+		if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
+
+		p := hostVolumePath(pr.name)
+		if err := os.RemoveAll(p); err != nil {
+			http.Error(w, err.Error(), 500)
+		}
+
+		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+		fmt.Fprintln(w, `{}`)
+	})
+
+	if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+		c.Fatal(err)
+	}
+
+	if err := ioutil.WriteFile("/usr/share/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644); err != nil {
+		c.Fatal(err)
+	}
+}
+
+func (s *ExternalVolumeSuite) TearDownSuite(c *check.C) {
+	s.server.Close()
+
+	if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+		c.Fatal(err)
+	}
+}
+
+func (s *ExternalVolumeSuite) TestStartExternalVolumeDriver(c *check.C) {
+	runCmd := exec.Command(dockerBinary, "run", "--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, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil && exitCode != 0 {
+		c.Fatal(out, stderr, err)
+	}
+
+	if !strings.Contains(out, s.server.URL) {
+		c.Fatalf("External volume mount failed. Output: %s\n", out)
+	}
+}
+
+func (s *ExternalVolumeSuite) TestStartExternalVolumeNamedDriver(c *check.C) {
+	runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "-v", "test-external-volume-driver/volume-1:/tmp/external-volume-test", "busybox:latest", "cat", "/tmp/external-volume-test/test")
+	out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil && exitCode != 0 {
+		c.Fatal(out, stderr, err)
+	}
+
+	if !strings.Contains(out, s.server.URL) {
+		c.Fatalf("External volume mount failed. Output: %s\n", out)
+	}
+}

+ 0 - 1
integration-cli/docker_test_vars.go

@@ -18,7 +18,6 @@ var (
 
 	dockerBasePath       = "/var/lib/docker"
 	volumesConfigPath    = dockerBasePath + "/volumes"
-	volumesStoragePath   = dockerBasePath + "/vfs/dir"
 	containerStoragePath = dockerBasePath + "/containers"
 
 	runtimePath    = "/var/run/docker"

+ 16 - 4
pkg/plugins/client.go

@@ -31,6 +31,10 @@ type Client struct {
 }
 
 func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
+	return c.callWithRetry(serviceMethod, args, ret, true)
+}
+
+func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error {
 	var buf bytes.Buffer
 	if err := json.NewEncoder(&buf).Encode(args); err != nil {
 		return err
@@ -50,12 +54,16 @@ func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) e
 	for {
 		resp, err := c.http.Do(req)
 		if err != nil {
+			if !retry {
+				return err
+			}
+
 			timeOff := backoff(retries)
-			if timeOff+time.Since(start) > defaultTimeOut {
+			if abort(start, timeOff) {
 				return err
 			}
 			retries++
-			logrus.Warn("Unable to connect to plugin: %s, retrying in %ds\n", c.addr, timeOff)
+			logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
 			time.Sleep(timeOff)
 			continue
 		}
@@ -73,7 +81,7 @@ func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) e
 }
 
 func backoff(retries int) time.Duration {
-	b, max := float64(1), float64(defaultTimeOut)
+	b, max := 1, defaultTimeOut
 	for b < max && retries > 0 {
 		b *= 2
 		retries--
@@ -81,7 +89,11 @@ func backoff(retries int) time.Duration {
 	if b > max {
 		b = max
 	}
-	return time.Duration(b)
+	return time.Duration(b) * time.Second
+}
+
+func abort(start time.Time, timeOff time.Duration) bool {
+	return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second
 }
 
 func configureTCPTransport(tr *http.Transport, proto, addr string) {

+ 43 - 1
pkg/plugins/client_test.go

@@ -6,6 +6,7 @@ import (
 	"net/http/httptest"
 	"reflect"
 	"testing"
+	"time"
 )
 
 var (
@@ -27,7 +28,7 @@ func teardownRemotePluginServer() {
 
 func TestFailedConnection(t *testing.T) {
 	c := NewClient("tcp://127.0.0.1:1")
-	err := c.Call("Service.Method", nil, nil)
+	err := c.callWithRetry("Service.Method", nil, nil, false)
 	if err == nil {
 		t.Fatal("Unexpected successful connection")
 	}
@@ -61,3 +62,44 @@ func TestEchoInputOutput(t *testing.T) {
 		t.Fatalf("Expected %v, was %v\n", m, output)
 	}
 }
+
+func TestBackoff(t *testing.T) {
+	cases := []struct {
+		retries    int
+		expTimeOff time.Duration
+	}{
+		{0, time.Duration(1)},
+		{1, time.Duration(2)},
+		{2, time.Duration(4)},
+		{4, time.Duration(16)},
+		{6, time.Duration(30)},
+		{10, time.Duration(30)},
+	}
+
+	for _, c := range cases {
+		s := c.expTimeOff * time.Second
+		if d := backoff(c.retries); d != s {
+			t.Fatalf("Retry %v, expected %v, was %v\n", c.retries, s, d)
+		}
+	}
+}
+
+func TestAbortRetry(t *testing.T) {
+	cases := []struct {
+		timeOff  time.Duration
+		expAbort bool
+	}{
+		{time.Duration(1), false},
+		{time.Duration(2), false},
+		{time.Duration(10), false},
+		{time.Duration(30), true},
+		{time.Duration(40), true},
+	}
+
+	for _, c := range cases {
+		s := c.timeOff * time.Second
+		if a := abort(time.Now(), s); a != c.expAbort {
+			t.Fatalf("Duration %v, expected %v, was %v\n", c.timeOff, s, a)
+		}
+	}
+}

+ 1 - 0
runconfig/config.go

@@ -122,6 +122,7 @@ type Config struct {
 	Cmd             *Command
 	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
 	Volumes         map[string]struct{}
+	VolumeDriver    string
 	WorkingDir      string
 	Entrypoint      *Entrypoint
 	NetworkDisabled bool

+ 2 - 0
runconfig/parse.go

@@ -77,6 +77,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flReadonlyRootfs  = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
 		flLoggingDriver   = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
 		flCgroupParent    = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
+		flVolumeDriver    = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
 	)
 
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
@@ -317,6 +318,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		Entrypoint:      entrypoint,
 		WorkingDir:      *flWorkingDir,
 		Labels:          convertKVStringsToMap(labels),
+		VolumeDriver:    *flVolumeDriver,
 	}
 
 	hostConfig := &HostConfig{

+ 22 - 0
utils/tcp.go

@@ -0,0 +1,22 @@
+package utils
+
+import (
+	"net"
+	"net/http"
+	"time"
+)
+
+func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
+	// Why 32? See https://github.com/docker/docker/pull/8035.
+	timeout := 32 * time.Second
+	if proto == "unix" {
+		// No need for compression in local communications.
+		tr.DisableCompression = true
+		tr.Dial = func(_, _ string) (net.Conn, error) {
+			return net.DialTimeout(proto, addr, timeout)
+		}
+	} else {
+		tr.Proxy = http.ProxyFromEnvironment
+		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+	}
+}

+ 51 - 0
volume/drivers/adapter.go

@@ -0,0 +1,51 @@
+package volumedrivers
+
+import "github.com/docker/docker/volume"
+
+type volumeDriverAdapter struct {
+	name  string
+	proxy *volumeDriverProxy
+}
+
+func (a *volumeDriverAdapter) Name() string {
+	return a.name
+}
+
+func (a *volumeDriverAdapter) Create(name string) (volume.Volume, error) {
+	err := a.proxy.Create(name)
+	if err != nil {
+		return nil, err
+	}
+	return &volumeAdapter{a.proxy, name, a.name}, nil
+}
+
+func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
+	return a.proxy.Remove(v.Name())
+}
+
+type volumeAdapter struct {
+	proxy      *volumeDriverProxy
+	name       string
+	driverName string
+}
+
+func (a *volumeAdapter) Name() string {
+	return a.name
+}
+
+func (a *volumeAdapter) DriverName() string {
+	return a.driverName
+}
+
+func (a *volumeAdapter) Path() string {
+	m, _ := a.proxy.Path(a.name)
+	return m
+}
+
+func (a *volumeAdapter) Mount() (string, error) {
+	return a.proxy.Mount(a.name)
+}
+
+func (a *volumeAdapter) Unmount() error {
+	return a.proxy.Unmount(a.name)
+}

+ 20 - 0
volume/drivers/api.go

@@ -0,0 +1,20 @@
+package volumedrivers
+
+import "github.com/docker/docker/volume"
+
+type client interface {
+	Call(string, interface{}, interface{}) error
+}
+
+func NewVolumeDriver(name string, c client) volume.Driver {
+	proxy := &volumeDriverProxy{c}
+	return &volumeDriverAdapter{name, proxy}
+}
+
+type VolumeDriver interface {
+	Create(name string) (err error)
+	Remove(name string) (err error)
+	Path(name string) (mountpoint string, err error)
+	Mount(name string) (mountpoint string, err error)
+	Unmount(name string) (err error)
+}

+ 61 - 0
volume/drivers/extpoint.go

@@ -0,0 +1,61 @@
+package volumedrivers
+
+import (
+	"sync"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/volume"
+)
+
+// currently created by hand. generation tool would generate this like:
+// $ extpoint-gen Driver > volume/extpoint.go
+
+var drivers = &driverExtpoint{extensions: make(map[string]volume.Driver)}
+
+type driverExtpoint struct {
+	extensions map[string]volume.Driver
+	sync.Mutex
+}
+
+func Register(extension volume.Driver, name string) bool {
+	drivers.Lock()
+	defer drivers.Unlock()
+	if name == "" {
+		return false
+	}
+	_, exists := drivers.extensions[name]
+	if exists {
+		return false
+	}
+	drivers.extensions[name] = extension
+	return true
+}
+
+func Unregister(name string) bool {
+	drivers.Lock()
+	defer drivers.Unlock()
+	_, exists := drivers.extensions[name]
+	if !exists {
+		return false
+	}
+	delete(drivers.extensions, name)
+	return true
+}
+
+func Lookup(name string) volume.Driver {
+	drivers.Lock()
+	defer drivers.Unlock()
+	ext, ok := drivers.extensions[name]
+	if ok {
+		return ext
+	}
+	pl, err := plugins.Get(name, "VolumeDriver")
+	if err != nil {
+		logrus.Errorf("Error: %v", err)
+		return nil
+	}
+	d := NewVolumeDriver(name, pl.Client)
+	drivers.extensions[name] = d
+	return d
+}

+ 65 - 0
volume/drivers/proxy.go

@@ -0,0 +1,65 @@
+package volumedrivers
+
+// currently created by hand. generation tool would generate this like:
+// $ rpc-gen volume/drivers/api.go VolumeDriver > volume/drivers/proxy.go
+
+type volumeDriverRequest struct {
+	Name string
+}
+
+type volumeDriverResponse struct {
+	Mountpoint string `json:",ommitempty"`
+	Err        error  `json:",ommitempty"`
+}
+
+type volumeDriverProxy struct {
+	c client
+}
+
+func (pp *volumeDriverProxy) Create(name string) error {
+	args := volumeDriverRequest{name}
+	var ret volumeDriverResponse
+	err := pp.c.Call("VolumeDriver.Create", args, &ret)
+	if err != nil {
+		return err
+	}
+	return ret.Err
+}
+
+func (pp *volumeDriverProxy) Remove(name string) error {
+	args := volumeDriverRequest{name}
+	var ret volumeDriverResponse
+	err := pp.c.Call("VolumeDriver.Remove", args, &ret)
+	if err != nil {
+		return err
+	}
+	return ret.Err
+}
+
+func (pp *volumeDriverProxy) Path(name string) (string, error) {
+	args := volumeDriverRequest{name}
+	var ret volumeDriverResponse
+	if err := pp.c.Call("VolumeDriver.Path", args, &ret); err != nil {
+		return "", err
+	}
+	return ret.Mountpoint, ret.Err
+}
+
+func (pp *volumeDriverProxy) Mount(name string) (string, error) {
+	args := volumeDriverRequest{name}
+	var ret volumeDriverResponse
+	if err := pp.c.Call("VolumeDriver.Mount", args, &ret); err != nil {
+		return "", err
+	}
+	return ret.Mountpoint, ret.Err
+}
+
+func (pp *volumeDriverProxy) Unmount(name string) error {
+	args := volumeDriverRequest{name}
+	var ret volumeDriverResponse
+	err := pp.c.Call("VolumeDriver.Unmount", args, &ret)
+	if err != nil {
+		return err
+	}
+	return ret.Err
+}

+ 126 - 0
volume/local/local.go

@@ -0,0 +1,126 @@
+package local
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"github.com/docker/docker/volume"
+)
+
+func New(rootDirectory string) (*Root, error) {
+	if err := os.MkdirAll(rootDirectory, 0700); err != nil {
+		return nil, err
+	}
+	r := &Root{
+		path:    rootDirectory,
+		volumes: make(map[string]*Volume),
+	}
+	dirs, err := ioutil.ReadDir(rootDirectory)
+	if err != nil {
+		return nil, err
+	}
+	for _, d := range dirs {
+		name := filepath.Base(d.Name())
+		r.volumes[name] = &Volume{
+			driverName: r.Name(),
+			name:       name,
+			path:       filepath.Join(rootDirectory, name),
+		}
+	}
+	return r, nil
+}
+
+type Root struct {
+	m       sync.Mutex
+	path    string
+	volumes map[string]*Volume
+}
+
+func (r *Root) Name() string {
+	return "local"
+}
+
+func (r *Root) Create(name string) (volume.Volume, error) {
+	r.m.Lock()
+	defer r.m.Unlock()
+	v, exists := r.volumes[name]
+	if !exists {
+		path := filepath.Join(r.path, name)
+		if err := os.Mkdir(path, 0755); err != nil {
+			if os.IsExist(err) {
+				return nil, fmt.Errorf("volume already exists under %s", path)
+			}
+			return nil, err
+		}
+		v = &Volume{
+			driverName: r.Name(),
+			name:       name,
+			path:       path,
+		}
+		r.volumes[name] = v
+	}
+	v.use()
+	return v, nil
+}
+
+func (r *Root) Remove(v volume.Volume) error {
+	r.m.Lock()
+	defer r.m.Unlock()
+	lv, ok := v.(*Volume)
+	if !ok {
+		return errors.New("unknown volume type")
+	}
+	lv.release()
+	if lv.usedCount == 0 {
+		delete(r.volumes, lv.name)
+		return os.RemoveAll(lv.path)
+	}
+	return nil
+}
+
+type Volume struct {
+	m         sync.Mutex
+	usedCount int
+	// unique name of the volume
+	name string
+	// path is the path on the host where the data lives
+	path string
+	// driverName is the name of the driver that created the volume.
+	driverName string
+}
+
+func (v *Volume) Name() string {
+	return v.name
+}
+
+func (v *Volume) DriverName() string {
+	return v.driverName
+}
+
+func (v *Volume) Path() string {
+	return v.path
+}
+
+func (v *Volume) Mount() (string, error) {
+	return v.path, nil
+}
+
+func (v *Volume) Unmount() error {
+	return nil
+}
+
+func (v *Volume) use() {
+	v.m.Lock()
+	v.usedCount++
+	v.m.Unlock()
+}
+
+func (v *Volume) release() {
+	v.m.Lock()
+	v.usedCount--
+	v.m.Unlock()
+}

+ 26 - 0
volume/volume.go

@@ -0,0 +1,26 @@
+package volume
+
+const DefaultDriverName = "local"
+
+type Driver interface {
+	// Name returns the name of the volume driver.
+	Name() string
+	// Create makes a new volume with the given id.
+	Create(string) (Volume, error)
+	// Remove deletes the volume.
+	Remove(Volume) error
+}
+
+type Volume interface {
+	// Name returns the name of the volume
+	Name() string
+	// DriverName returns the name of the driver which owns this volume.
+	DriverName() string
+	// Path returns the absolute path to the volume.
+	Path() string
+	// Mount mounts the volume and returns the absolute path to
+	// where it can be consumed.
+	Mount() (string, error)
+	// Unmount unmounts the volume when it is no longer in use.
+	Unmount() error
+}

+ 0 - 193
volumes/repository.go

@@ -1,193 +0,0 @@
-package volumes
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"sync"
-
-	"github.com/Sirupsen/logrus"
-	"github.com/docker/docker/daemon/graphdriver"
-	"github.com/docker/docker/pkg/stringid"
-)
-
-type Repository struct {
-	configPath string
-	driver     graphdriver.Driver
-	volumes    map[string]*Volume
-	lock       sync.Mutex
-}
-
-func NewRepository(configPath string, driver graphdriver.Driver) (*Repository, error) {
-	abspath, err := filepath.Abs(configPath)
-	if err != nil {
-		return nil, err
-	}
-
-	// Create the config path
-	if err := os.MkdirAll(abspath, 0700); err != nil && !os.IsExist(err) {
-		return nil, err
-	}
-
-	repo := &Repository{
-		driver:     driver,
-		configPath: abspath,
-		volumes:    make(map[string]*Volume),
-	}
-
-	return repo, repo.restore()
-}
-
-func (r *Repository) newVolume(path string, writable bool) (*Volume, error) {
-	var (
-		isBindMount bool
-		err         error
-		id          = stringid.GenerateRandomID()
-	)
-	if path != "" {
-		isBindMount = true
-	}
-
-	if path == "" {
-		path, err = r.createNewVolumePath(id)
-		if err != nil {
-			return nil, err
-		}
-	}
-	path = filepath.Clean(path)
-
-	// Ignore the error here since the path may not exist
-	// Really just want to make sure the path we are using is real(or nonexistent)
-	if cleanPath, err := filepath.EvalSymlinks(path); err == nil {
-		path = cleanPath
-	}
-
-	v := &Volume{
-		ID:          id,
-		Path:        path,
-		repository:  r,
-		Writable:    writable,
-		containers:  make(map[string]struct{}),
-		configPath:  r.configPath + "/" + id,
-		IsBindMount: isBindMount,
-	}
-
-	if err := v.initialize(); err != nil {
-		return nil, err
-	}
-
-	r.add(v)
-	return v, nil
-}
-
-func (r *Repository) restore() error {
-	dir, err := ioutil.ReadDir(r.configPath)
-	if err != nil {
-		return err
-	}
-
-	for _, v := range dir {
-		id := v.Name()
-		vol := &Volume{
-			ID:         id,
-			configPath: r.configPath + "/" + id,
-			containers: make(map[string]struct{}),
-		}
-		if err := vol.FromDisk(); err != nil {
-			if !os.IsNotExist(err) {
-				logrus.Debugf("Error restoring volume: %v", err)
-				continue
-			}
-			if err := vol.initialize(); err != nil {
-				logrus.Debugf("%s", err)
-				continue
-			}
-		}
-		r.add(vol)
-	}
-	return nil
-}
-
-func (r *Repository) Get(path string) *Volume {
-	r.lock.Lock()
-	vol := r.get(path)
-	r.lock.Unlock()
-	return vol
-}
-
-func (r *Repository) get(path string) *Volume {
-	path, err := filepath.EvalSymlinks(path)
-	if err != nil {
-		return nil
-	}
-	return r.volumes[filepath.Clean(path)]
-}
-
-func (r *Repository) add(volume *Volume) {
-	if vol := r.get(volume.Path); vol != nil {
-		return
-	}
-	r.volumes[volume.Path] = volume
-}
-
-func (r *Repository) Delete(path string) error {
-	r.lock.Lock()
-	defer r.lock.Unlock()
-	path, err := filepath.EvalSymlinks(path)
-	if err != nil {
-		return err
-	}
-	volume := r.get(filepath.Clean(path))
-	if volume == nil {
-		return fmt.Errorf("Volume %s does not exist", path)
-	}
-
-	containers := volume.Containers()
-	if len(containers) > 0 {
-		return fmt.Errorf("Volume %s is being used and cannot be removed: used by containers %s", volume.Path, containers)
-	}
-
-	if err := os.RemoveAll(volume.configPath); err != nil {
-		return err
-	}
-
-	if !volume.IsBindMount {
-		if err := r.driver.Remove(volume.ID); err != nil {
-			if !os.IsNotExist(err) {
-				return err
-			}
-		}
-	}
-
-	delete(r.volumes, volume.Path)
-	return nil
-}
-
-func (r *Repository) createNewVolumePath(id string) (string, error) {
-	if err := r.driver.Create(id, ""); err != nil {
-		return "", err
-	}
-
-	path, err := r.driver.Get(id, "")
-	if err != nil {
-		return "", fmt.Errorf("Driver %s failed to get volume rootfs %s: %v", r.driver, id, err)
-	}
-
-	return path, nil
-}
-
-func (r *Repository) FindOrCreateVolume(path string, writable bool) (*Volume, error) {
-	r.lock.Lock()
-	defer r.lock.Unlock()
-
-	if path == "" {
-		return r.newVolume(path, writable)
-	}
-
-	if v := r.get(path); v != nil {
-		return v, nil
-	}
-
-	return r.newVolume(path, writable)
-}

+ 0 - 164
volumes/repository_test.go

@@ -1,164 +0,0 @@
-package volumes
-
-import (
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"testing"
-
-	"github.com/docker/docker/daemon/graphdriver"
-	_ "github.com/docker/docker/daemon/graphdriver/vfs"
-)
-
-func TestRepositoryFindOrCreate(t *testing.T) {
-	root, err := ioutil.TempDir(os.TempDir(), "volumes")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(root)
-	repo, err := newRepo(root)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// no path
-	v, err := repo.FindOrCreateVolume("", true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// FIXME: volumes are heavily dependent on the vfs driver, but this should not be so!
-	expected := filepath.Join(root, "repo-graph", "vfs", "dir", v.ID)
-	if v.Path != expected {
-		t.Fatalf("expected new path to be created in %s, got %s", expected, v.Path)
-	}
-
-	// with a non-existant path
-	dir := filepath.Join(root, "doesntexist")
-	v, err = repo.FindOrCreateVolume(dir, true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if v.Path != dir {
-		t.Fatalf("expected new path to be created in %s, got %s", dir, v.Path)
-	}
-
-	if _, err := os.Stat(v.Path); err != nil {
-		t.Fatal(err)
-	}
-
-	// with a pre-existing path
-	// can just use the same path from above since it now exists
-	v, err = repo.FindOrCreateVolume(dir, true)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if v.Path != dir {
-		t.Fatalf("expected new path to be created in %s, got %s", dir, v.Path)
-	}
-
-}
-
-func TestRepositoryGet(t *testing.T) {
-	root, err := ioutil.TempDir(os.TempDir(), "volumes")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(root)
-	repo, err := newRepo(root)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	v, err := repo.FindOrCreateVolume("", true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	v2 := repo.Get(v.Path)
-	if v2 == nil {
-		t.Fatalf("expected to find volume but didn't")
-	}
-	if v2 != v {
-		t.Fatalf("expected get to return same volume")
-	}
-}
-
-func TestRepositoryDelete(t *testing.T) {
-	root, err := ioutil.TempDir(os.TempDir(), "volumes")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(root)
-	repo, err := newRepo(root)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// with a normal volume
-	v, err := repo.FindOrCreateVolume("", true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := repo.Delete(v.Path); err != nil {
-		t.Fatal(err)
-	}
-
-	if v := repo.Get(v.Path); v != nil {
-		t.Fatalf("expected volume to not exist")
-	}
-
-	if _, err := os.Stat(v.Path); err == nil {
-		t.Fatalf("expected volume files to be removed")
-	}
-
-	// with a bind mount
-	dir := filepath.Join(root, "test")
-	v, err = repo.FindOrCreateVolume(dir, true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := repo.Delete(v.Path); err != nil {
-		t.Fatal(err)
-	}
-
-	if v := repo.Get(v.Path); v != nil {
-		t.Fatalf("expected volume to not exist")
-	}
-
-	if _, err := os.Stat(v.Path); err != nil && os.IsNotExist(err) {
-		t.Fatalf("expected bind volume data to persist after destroying volume")
-	}
-
-	// with container refs
-	dir = filepath.Join(root, "test")
-	v, err = repo.FindOrCreateVolume(dir, true)
-	if err != nil {
-		t.Fatal(err)
-	}
-	v.AddContainer("1234")
-
-	if err := repo.Delete(v.Path); err == nil {
-		t.Fatalf("expected volume delete to fail due to container refs")
-	}
-
-	v.RemoveContainer("1234")
-	if err := repo.Delete(v.Path); err != nil {
-		t.Fatal(err)
-	}
-
-}
-
-func newRepo(root string) (*Repository, error) {
-	configPath := filepath.Join(root, "repo-config")
-	graphDir := filepath.Join(root, "repo-graph")
-
-	driver, err := graphdriver.GetDriver("vfs", graphDir, []string{})
-	if err != nil {
-		return nil, err
-	}
-	return NewRepository(configPath, driver)
-}

+ 0 - 152
volumes/volume.go

@@ -1,152 +0,0 @@
-package volumes
-
-import (
-	"encoding/json"
-	"os"
-	"path/filepath"
-	"sync"
-
-	"github.com/docker/docker/pkg/symlink"
-)
-
-type Volume struct {
-	ID          string
-	Path        string
-	IsBindMount bool
-	Writable    bool
-	containers  map[string]struct{}
-	configPath  string
-	repository  *Repository
-	lock        sync.Mutex
-}
-
-func (v *Volume) IsDir() (bool, error) {
-	stat, err := os.Stat(v.Path)
-	if err != nil {
-		return false, err
-	}
-
-	return stat.IsDir(), nil
-}
-
-func (v *Volume) Containers() []string {
-	v.lock.Lock()
-
-	var containers []string
-	for c := range v.containers {
-		containers = append(containers, c)
-	}
-
-	v.lock.Unlock()
-	return containers
-}
-
-func (v *Volume) RemoveContainer(containerId string) {
-	v.lock.Lock()
-	delete(v.containers, containerId)
-	v.lock.Unlock()
-}
-
-func (v *Volume) AddContainer(containerId string) {
-	v.lock.Lock()
-	v.containers[containerId] = struct{}{}
-	v.lock.Unlock()
-}
-
-func (v *Volume) initialize() error {
-	v.lock.Lock()
-	defer v.lock.Unlock()
-
-	if _, err := os.Stat(v.Path); err != nil {
-		if !os.IsNotExist(err) {
-			return err
-		}
-		if err := os.MkdirAll(v.Path, 0755); err != nil {
-			return err
-		}
-	}
-
-	if err := os.MkdirAll(v.configPath, 0755); err != nil {
-		return err
-	}
-
-	return v.toDisk()
-}
-
-func (v *Volume) ToDisk() error {
-	v.lock.Lock()
-	defer v.lock.Unlock()
-	return v.toDisk()
-}
-
-func (v *Volume) toDisk() error {
-	jsonPath, err := v.jsonPath()
-	if err != nil {
-		return err
-	}
-	f, err := os.OpenFile(jsonPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
-	if err != nil {
-		return err
-	}
-	if err := json.NewEncoder(f).Encode(v); err != nil {
-		f.Close()
-		return err
-	}
-	return f.Close()
-}
-
-func (v *Volume) FromDisk() error {
-	v.lock.Lock()
-	defer v.lock.Unlock()
-	pth, err := v.jsonPath()
-	if err != nil {
-		return err
-	}
-
-	jsonSource, err := os.Open(pth)
-	if err != nil {
-		return err
-	}
-	defer jsonSource.Close()
-
-	dec := json.NewDecoder(jsonSource)
-
-	return dec.Decode(v)
-}
-
-func (v *Volume) jsonPath() (string, error) {
-	return v.GetRootResourcePath("config.json")
-}
-
-// Evalutes `path` in the scope of the volume's root path, with proper path
-// sanitisation. Symlinks are all scoped to the root of the volume, as
-// though the volume's root was `/`.
-//
-// The volume's root path is the host-facing path of the root of the volume's
-// mountpoint inside a container.
-//
-// NOTE: The returned path is *only* safely scoped inside the volume's root
-//       if no component of the returned path changes (such as a component
-//       symlinking to a different path) between using this method and using the
-//       path. See symlink.FollowSymlinkInScope for more details.
-func (v *Volume) GetResourcePath(path string) (string, error) {
-	cleanPath := filepath.Join("/", path)
-	return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path)
-}
-
-// Evalutes `path` in the scope of the volume's config path, with proper path
-// sanitisation. Symlinks are all scoped to the root of the config path, as
-// though the config path was `/`.
-//
-// The config path of a volume is not exposed to the container and is just used
-// to store volume configuration options and other internal information. If in
-// doubt, you probably want to just use v.GetResourcePath.
-//
-// NOTE: The returned path is *only* safely scoped inside the volume's config
-//       path if no component of the returned path changes (such as a component
-//       symlinking to a different path) between using this method and using the
-//       path. See symlink.FollowSymlinkInScope for more details.
-func (v *Volume) GetRootResourcePath(path string) (string, error) {
-	cleanPath := filepath.Join("/", path)
-	return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath)
-}

+ 0 - 55
volumes/volume_test.go

@@ -1,55 +0,0 @@
-package volumes
-
-import (
-	"os"
-	"testing"
-
-	"github.com/docker/docker/pkg/stringutils"
-)
-
-func TestContainers(t *testing.T) {
-	v := &Volume{containers: make(map[string]struct{})}
-	id := "1234"
-
-	v.AddContainer(id)
-
-	if v.Containers()[0] != id {
-		t.Fatalf("adding a container ref failed")
-	}
-
-	v.RemoveContainer(id)
-	if len(v.Containers()) != 0 {
-		t.Fatalf("removing container failed")
-	}
-}
-
-// os.Stat(v.Path) is returning ErrNotExist, initialize catch it and try to
-// mkdir v.Path but it dies and correctly returns the error
-func TestInitializeCannotMkdirOnNonExistentPath(t *testing.T) {
-	v := &Volume{Path: "nonexistentpath"}
-
-	err := v.initialize()
-	if err == nil {
-		t.Fatal("Expected not to initialize volume with a non existent path")
-	}
-
-	if !os.IsNotExist(err) {
-		t.Fatalf("Expected to get ErrNotExist error, got %s", err)
-	}
-}
-
-// os.Stat(v.Path) is NOT returning ErrNotExist so skip and return error from
-// initialize
-func TestInitializeCannotStatPathFileNameTooLong(t *testing.T) {
-	// ENAMETOOLONG
-	v := &Volume{Path: stringutils.GenerateRandomAlphaOnlyString(300)}
-
-	err := v.initialize()
-	if err == nil {
-		t.Fatal("Expected not to initialize volume with a non existent path")
-	}
-
-	if os.IsNotExist(err) {
-		t.Fatal("Expected to not get ErrNotExist")
-	}
-}