mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									23e8dff9e7
								
							
						
					
					
						commit
						81fa9feb0c
					
				
					 43 changed files with 1538 additions and 1191 deletions
				
			
		| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,22 +50,19 @@ 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
 | 
			
		||||
| 
						 | 
				
			
			@ -71,23 +70,17 @@ type CommonContainer struct {
 | 
			
		|||
	Name                     string
 | 
			
		||||
	Driver                   string
 | 
			
		||||
	ExecDriver               string
 | 
			
		||||
 | 
			
		||||
	command *execdriver.Command
 | 
			
		||||
	StreamConfig
 | 
			
		||||
 | 
			
		||||
	daemon                   *Daemon
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,13 +42,6 @@ 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 ----
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,18 +91,53 @@ 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 := container.Mount(); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer container.Unmount()
 | 
			
		||||
	if err := container.prepareVolumes(); err != nil {
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
	group := sync.WaitGroup{}
 | 
			
		||||
	for _, c := range containers {
 | 
			
		||||
		group.Add(1)
 | 
			
		||||
 | 
			
		||||
		go func(container *Container, registered bool) {
 | 
			
		||||
			defer group.Done()
 | 
			
		||||
 | 
			
		||||
			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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := daemon.register(container, false); err != nil {
 | 
			
		||||
				logrus.Debugf("Failed to register container %s: %s", container.ID, err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		registeredContainers = append(registeredContainers, container)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			// 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...")
 | 
			
		||||
 | 
			
		||||
		for _, container := range registeredContainers {
 | 
			
		||||
			if container.hostConfig.RestartPolicy.IsAlways() ||
 | 
			
		||||
				(container.hostConfig.RestartPolicy.IsOnFailure() && container.ExitCode != 0) {
 | 
			
		||||
			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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
var localMountErr = fmt.Errorf("Invalid driver: %s driver doesn't support named volumes", volume.DefaultDriverName)
 | 
			
		||||
 | 
			
		||||
type mountPoint struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Destination string
 | 
			
		||||
	Driver      string
 | 
			
		||||
	RW          bool
 | 
			
		||||
	Volume      volume.Volume `json:"-"`
 | 
			
		||||
	Source      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
 | 
			
		||||
func (m *mountPoint) Setup() (string, error) {
 | 
			
		||||
	if m.Volume != nil {
 | 
			
		||||
		return m.Volume.Mount()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		realPath, err := container.GetResourcePath(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
	if len(m.Source) > 0 {
 | 
			
		||||
		if _, err := os.Stat(m.Source); err != nil {
 | 
			
		||||
			if !os.IsNotExist(err) {
 | 
			
		||||
				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)
 | 
			
		||||
			if err := os.MkdirAll(m.Source, 0755); err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return m.Source, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		mnt := &volumeMount{
 | 
			
		||||
			containerPath: path,
 | 
			
		||||
			writable:      true,
 | 
			
		||||
			copyData:      true,
 | 
			
		||||
		}
 | 
			
		||||
		mounts[mnt.containerPath] = mnt
 | 
			
		||||
	return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
func (m *mountPoint) Path() string {
 | 
			
		||||
	if m.Volume != nil {
 | 
			
		||||
		return m.Volume.Path()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		// #10618
 | 
			
		||||
		if _, exists := bindPaths[mnt.containerPath]; exists {
 | 
			
		||||
			return fmt.Errorf("Duplicate volume mount %s", mnt.containerPath)
 | 
			
		||||
	return m.Source
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		bindPaths[mnt.containerPath] = struct{}{}
 | 
			
		||||
		mounts[mnt.containerPath] = mnt
 | 
			
		||||
func parseBindMount(spec string, config *runconfig.Config) (*mountPoint, error) {
 | 
			
		||||
	bind := &mountPoint{
 | 
			
		||||
		RW: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c, err := container.daemon.Get(cID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("container %s not found, impossible to mount its volumes", cID)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, mnt := range c.volumeMounts() {
 | 
			
		||||
			mnt.writable = mnt.writable && (mode == "rw")
 | 
			
		||||
			mnt.from = cID
 | 
			
		||||
			mounts[mnt.containerPath] = mnt
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(mountPaths)
 | 
			
		||||
	return mountPaths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) VolumePaths() map[string]struct{} {
 | 
			
		||||
	var paths = make(map[string]struct{})
 | 
			
		||||
	for _, path := range container.Volumes {
 | 
			
		||||
		paths[path] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 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
 | 
			
		||||
		}
 | 
			
		||||
		v.AddContainer(container.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
		}
 | 
			
		||||
		vol.RemoveContainer(container.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseBindMountSpec(spec string) (*volumeMount, error) {
 | 
			
		||||
	arr := strings.Split(spec, ":")
 | 
			
		||||
 | 
			
		||||
	mnt := &volumeMount{}
 | 
			
		||||
	switch len(arr) {
 | 
			
		||||
	case 2:
 | 
			
		||||
		mnt.hostPath = arr[0]
 | 
			
		||||
		mnt.containerPath = arr[1]
 | 
			
		||||
		mnt.writable = true
 | 
			
		||||
		bind.Destination = arr[1]
 | 
			
		||||
	case 3:
 | 
			
		||||
		mnt.hostPath = arr[0]
 | 
			
		||||
		mnt.containerPath = arr[1]
 | 
			
		||||
		mnt.writable = validMountMode(arr[2]) && arr[2] == "rw"
 | 
			
		||||
		bind.Destination = arr[1]
 | 
			
		||||
		if !validMountMode(arr[2]) {
 | 
			
		||||
			return nil, fmt.Errorf("invalid mode for volumes-from: %s", arr[2])
 | 
			
		||||
		}
 | 
			
		||||
		bind.RW = arr[2] == "rw"
 | 
			
		||||
	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 !filepath.IsAbs(arr[0]) {
 | 
			
		||||
		bind.Driver, bind.Name = parseNamedVolumeInfo(arr[0], config)
 | 
			
		||||
		if bind.Driver == volume.DefaultDriverName {
 | 
			
		||||
			return nil, localMountErr
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		bind.Source = filepath.Clean(arr[0])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mnt.hostPath = filepath.Clean(mnt.hostPath)
 | 
			
		||||
	mnt.containerPath = filepath.Clean(mnt.containerPath)
 | 
			
		||||
	return mnt, nil
 | 
			
		||||
	bind.Destination = filepath.Clean(bind.Destination)
 | 
			
		||||
	return bind, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseVolumesFromSpec(spec string) (string, string, error) {
 | 
			
		||||
	specParts := strings.SplitN(spec, ":", 2)
 | 
			
		||||
	if len(specParts) == 0 {
 | 
			
		||||
func parseNamedVolumeInfo(info string, config *runconfig.Config) (driver string, name string) {
 | 
			
		||||
	p := strings.SplitN(info, "/", 2)
 | 
			
		||||
	switch len(p) {
 | 
			
		||||
	case 2:
 | 
			
		||||
		driver = p[0]
 | 
			
		||||
		name = p[1]
 | 
			
		||||
	default:
 | 
			
		||||
		if driver = config.VolumeDriver; len(driver) == 0 {
 | 
			
		||||
			driver = volume.DefaultDriverName
 | 
			
		||||
		}
 | 
			
		||||
		name = p[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		destPath, err := container.GetResourcePath(dest)
 | 
			
		||||
	// 2. Read volumes from other containers.
 | 
			
		||||
	for _, v := range hostConfig.VolumesFrom {
 | 
			
		||||
		containerID, mode, err := parseVolumesFrom(v)
 | 
			
		||||
		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 _, mnt := range container.specialMounts() {
 | 
			
		||||
		destPath, err := container.GetResourcePath(mnt.Destination)
 | 
			
		||||
		c, err := daemon.Get(containerID)
 | 
			
		||||
		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)
 | 
			
		||||
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3. Read bind mounts
 | 
			
		||||
	for _, b := range hostConfig.Binds {
 | 
			
		||||
		// #10618
 | 
			
		||||
		bind, err := parseBindMount(b, container.Config)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 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)
 | 
			
		||||
// 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 {
 | 
			
		||||
			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
		if err := mount.ForceUnmount(destPath); err != nil {
 | 
			
		||||
			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
	f, err := os.Open(jsonPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return 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])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, mnt := range container.specialMounts() {
 | 
			
		||||
		destPath, err := container.GetResourcePath(mnt.Destination)
 | 
			
		||||
	return container.ToDisk()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createVolume(name, driverName string) (volume.Volume, error) {
 | 
			
		||||
	vd, err := getVolumeDriver(driverName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
		if err := mount.ForceUnmount(destPath); err != nil {
 | 
			
		||||
			logrus.Errorf("error while unmounting volumes %s: %v", destPath, 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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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{})
 | 
			
		||||
	}
 | 
			
		||||
	return container.createVolumes()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) setupMounts() error {
 | 
			
		||||
	mounts := []execdriver.Mount{}
 | 
			
		||||
 | 
			
		||||
	// 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],
 | 
			
		||||
			Source:      path,
 | 
			
		||||
			Destination: m.Destination,
 | 
			
		||||
			Writable:    m.RW,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mounts = append(mounts, container.specialMounts()...)
 | 
			
		||||
 | 
			
		||||
	container.command.Mounts = mounts
 | 
			
		||||
	return nil
 | 
			
		||||
	mounts = sortMounts(mounts)
 | 
			
		||||
	return append(mounts, container.networkMounts()...), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sortMounts(m []execdriver.Mount) []execdriver.Mount {
 | 
			
		||||
	sort.Sort(mounts(m))
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mounts []execdriver.Mount
 | 
			
		||||
 | 
			
		||||
func (m mounts) Len() int {
 | 
			
		||||
	return len(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m mounts) parts(i int) int {
 | 
			
		||||
	return len(strings.Split(filepath.Clean(m[i].Destination), string(os.PathSeparator)))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										146
									
								
								daemon/volumes_unit_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								daemon/volumes_unit_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) prepareVolumes() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) setupMounts() error {
 | 
			
		||||
func (container *Container) setupMounts() ([]execdriver.Mount, error) {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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']
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								docs/sources/reference/api/plugin_api.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								docs/sources/reference/api/plugin_api.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								docs/sources/userguide/plugins.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								docs/sources/userguide/plugins.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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).
 | 
			
		||||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								integration-cli/docker_cli_start_volume_driver_unix_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								integration-cli/docker_cli_start_volume_driver_unix_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ var (
 | 
			
		|||
 | 
			
		||||
	dockerBasePath       = "/var/lib/docker"
 | 
			
		||||
	volumesConfigPath    = dockerBasePath + "/volumes"
 | 
			
		||||
	volumesStoragePath   = dockerBasePath + "/vfs/dir"
 | 
			
		||||
	containerStoragePath = dockerBasePath + "/containers"
 | 
			
		||||
 | 
			
		||||
	runtimePath    = "/var/run/docker"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								utils/tcp.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								utils/tcp.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								volume/drivers/adapter.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								volume/drivers/adapter.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								volume/drivers/api.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								volume/drivers/api.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								volume/drivers/extpoint.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								volume/drivers/extpoint.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								volume/drivers/proxy.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								volume/drivers/proxy.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								volume/local/local.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								volume/local/local.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								volume/volume.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								volume/volume.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue