Sfoglia il codice sorgente

plugins: container-rootfs-relative paths

Legacy plugins expect host-relative paths (such as for Volume.Mount).
However, a containerized plugin cannot respond with a host-relative
path. Therefore, this commit modifies new volume plugins' paths in Mount
and List to prepend the container's rootfs path.

This introduces a new PropagatedMount field in the Plugin Config.
When it is set for volume plugins, RootfsPropagation is set to rshared
and the path specified by PropagatedMount is bind-mounted with rshared
prior to launching the container. This is so that the daemon code can
access the paths returned by the plugin from the host mount namespace.

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit c54b717caf1a55e525ce180bfcb42addd59c6633)
Signed-off-by: Victor Vieux <vieux@docker.com>
Tibor Vass 8 anni fa
parent
commit
3a571b72fd

+ 4 - 0
api/swagger.yaml

@@ -1382,6 +1382,7 @@ definitions:
           - Workdir
           - Workdir
           - Network
           - Network
           - Linux
           - Linux
+          - PropagatedMount
           - Mounts
           - Mounts
           - Env
           - Env
           - Args
           - Args
@@ -1446,6 +1447,9 @@ definitions:
                 type: "array"
                 type: "array"
                 items:
                 items:
                   $ref: "#/definitions/PluginDevice"
                   $ref: "#/definitions/PluginDevice"
+          PropagatedMount:
+            type: "string"
+            x-nullable: false
           Mounts:
           Mounts:
             type: "array"
             type: "array"
             items:
             items:

+ 4 - 0
api/types/plugin.go

@@ -71,6 +71,10 @@ type PluginConfig struct {
 	// Required: true
 	// Required: true
 	Network PluginConfigNetwork `json:"Network"`
 	Network PluginConfigNetwork `json:"Network"`
 
 
+	// propagated mount
+	// Required: true
+	PropagatedMount string `json:"PropagatedMount"`
+
 	// user
 	// user
 	User PluginConfigUser `json:"User,omitempty"`
 	User PluginConfigUser `json:"User,omitempty"`
 
 

+ 4 - 0
docs/extend/config.md

@@ -111,6 +111,10 @@ Config provides the base accessible fields for working with V0 plugin format
 
 
 	  options of the mount.
 	  options of the mount.
 
 
+- **`propagatedMount`** *string*
+
+   path to be mounted as rshared, so that mounts under that path are visible to docker. This is useful for volume plugins.
+
 - **`env`** *PluginEnv array*
 - **`env`** *PluginEnv array*
 
 
    env of the plugin, struct consisting of the following fields
    env of the plugin, struct consisting of the following fields

+ 4 - 0
docs/extend/plugins_volume.md

@@ -22,6 +22,10 @@ beyond the lifetime of a single Engine host. See the
 
 
 ## Changelog
 ## Changelog
 
 
+### 1.13.0
+
+- If used as part of the v2 plugin architecture, mountpoints that are part of paths returned by plugin have to be mounted under the directory specified by PropagatedMount in the plugin configuration [#26398](https://github.com/docker/docker/pull/26398)
+
 ### 1.12.0
 ### 1.12.0
 
 
 - Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#))
 - Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#))

+ 5 - 0
integration-cli/daemon.go

@@ -88,6 +88,11 @@ func NewDaemon(c *check.C) *Daemon {
 	}
 	}
 }
 }
 
 
+// RootDir returns the root directory of the daemon.
+func (d *Daemon) RootDir() string {
+	return d.root
+}
+
 func (d *Daemon) getClientConfig() (*clientConfig, error) {
 func (d *Daemon) getClientConfig() (*clientConfig, error) {
 	var (
 	var (
 		transport *http.Transport
 		transport *http.Transport

+ 30 - 5
integration-cli/docker_cli_daemon_plugins_test.go

@@ -10,6 +10,7 @@ import (
 	"syscall"
 	"syscall"
 
 
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/integration/checker"
+	"github.com/docker/docker/pkg/mount"
 	"github.com/go-check/check"
 	"github.com/go-check/check"
 )
 )
 
 
@@ -186,7 +187,6 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
 	testRequires(c, Network, IsAmd64)
 	testRequires(c, Network, IsAmd64)
 
 
 	volName := "plugin-volume"
 	volName := "plugin-volume"
-	volRoot := "/data"
 	destDir := "/tmp/data/"
 	destDir := "/tmp/data/"
 	destFile := "foo"
 	destFile := "foo"
 
 
@@ -197,13 +197,25 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
 	if err != nil {
 	if err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 	}
+	pluginID, err := s.d.Cmd("plugin", "inspect", "-f", "{{.Id}}", pName)
+	pluginID = strings.TrimSpace(pluginID)
+	if err != nil {
+		c.Fatalf("Could not retrieve plugin ID: %v %s", err, pluginID)
+	}
+	mountpointPrefix := filepath.Join(s.d.RootDir(), "plugins", pluginID, "rootfs")
 	defer func() {
 	defer func() {
 		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 		}
 		}
+
 		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 		}
+
+		exists, err := existsMountpointWithPrefix(mountpointPrefix)
+		c.Assert(err, checker.IsNil)
+		c.Assert(exists, checker.Equals, false)
+
 	}()
 	}()
 
 
 	out, err = s.d.Cmd("volume", "create", "-d", pName, volName)
 	out, err = s.d.Cmd("volume", "create", "-d", pName, volName)
@@ -231,11 +243,24 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
 
 
 	out, err = s.d.Cmd("run", "--rm", "-v", volName+":"+destDir, "busybox", "touch", destDir+destFile)
 	out, err = s.d.Cmd("run", "--rm", "-v", volName+":"+destDir, "busybox", "touch", destDir+destFile)
 	c.Assert(err, checker.IsNil, check.Commentf(out))
 	c.Assert(err, checker.IsNil, check.Commentf(out))
-	path := filepath.Join(mountPoint, destFile)
+	path := filepath.Join(s.d.RootDir(), "plugins", pluginID, "rootfs", mountPoint, destFile)
 	_, err = os.Lstat(path)
 	_, err = os.Lstat(path)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 
 
-	// tiborvass/no-remove is a volume plugin that persists data on disk at /data,
-	// even after the volume is removed. So perform an explicit filesystem cleanup.
-	os.RemoveAll(volRoot)
+	exists, err := existsMountpointWithPrefix(mountpointPrefix)
+	c.Assert(err, checker.IsNil)
+	c.Assert(exists, checker.Equals, true)
+}
+
+func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
+	mounts, err := mount.GetMounts()
+	if err != nil {
+		return false, err
+	}
+	for _, mnt := range mounts {
+		if strings.HasPrefix(mnt.Mountpoint, mountpointPrefix) {
+			return true, nil
+		}
+	}
+	return false, nil
 }
 }

+ 3 - 2
integration-cli/docker_cli_plugins_test.go

@@ -11,8 +11,8 @@ import (
 )
 )
 
 
 var (
 var (
-	pluginProcessName = "no-remove"
-	pName             = "tiborvass/no-remove"
+	pluginProcessName = "sample-volume-plugin"
+	pName             = "tiborvass/sample-volume-plugin"
 	pTag              = "latest"
 	pTag              = "latest"
 	pNameWithTag      = pName + ":" + pTag
 	pNameWithTag      = pName + ":" + pTag
 )
 )
@@ -33,6 +33,7 @@ func (s *DockerSuite) TestPluginBasicOps(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 
 
 	out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
 	out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
+	c.Assert(err, checker.NotNil)
 	c.Assert(out, checker.Contains, "is enabled")
 	c.Assert(out, checker.Contains, "is enabled")
 
 
 	_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
 	_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)

+ 1 - 0
pkg/plugingetter/getter.go

@@ -15,6 +15,7 @@ const (
 type CompatPlugin interface {
 type CompatPlugin interface {
 	Client() *plugins.Client
 	Client() *plugins.Client
 	Name() string
 	Name() string
+	BasePath() string
 	IsV1() bool
 	IsV1() bool
 }
 }
 
 

+ 6 - 0
pkg/plugins/plugins.go

@@ -78,6 +78,12 @@ type Plugin struct {
 	activateWait *sync.Cond
 	activateWait *sync.Cond
 }
 }
 
 
+// BasePath returns the path to which all paths returned by the plugin are relative to.
+// For v1 plugins, this always returns the host's root directory.
+func (p *Plugin) BasePath() string {
+	return "/"
+}
+
 // Name returns the name of the plugin.
 // Name returns the name of the plugin.
 func (p *Plugin) Name() string {
 func (p *Plugin) Name() string {
 	return p.name
 	return p.name

+ 7 - 3
plugin/backend_linux.go

@@ -255,7 +255,7 @@ func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.A
 		return err
 		return err
 	}
 	}
 
 
-	rootfs, err := archive.Tar(filepath.Join(dest, "rootfs"), archive.Gzip)
+	rootfs, err := archive.Tar(p.Rootfs, archive.Gzip)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -293,9 +293,13 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
 		}
 		}
 	}
 	}
 
 
+	id := p.GetID()
 	pm.pluginStore.Remove(p)
 	pm.pluginStore.Remove(p)
-	os.RemoveAll(filepath.Join(pm.libRoot, p.GetID()))
-	pm.pluginEventLogger(p.GetID(), name, "remove")
+	pluginDir := filepath.Join(pm.libRoot, id)
+	if err := os.RemoveAll(pluginDir); err != nil {
+		logrus.Warnf("unable to remove %q from plugin remove: %v", pluginDir, err)
+	}
+	pm.pluginEventLogger(id, name, "remove")
 	return nil
 	return nil
 }
 }
 
 

+ 2 - 1
plugin/distribution/pull.go

@@ -177,7 +177,8 @@ func WritePullData(pd PullData, dest string, extract bool) error {
 	if err := json.Unmarshal(config, &p); err != nil {
 	if err := json.Unmarshal(config, &p); err != nil {
 		return err
 		return err
 	}
 	}
-	logrus.Debugf("%#v", p)
+	logrus.Debugf("plugin: %#v", p)
+
 	if err := os.MkdirAll(dest, 0700); err != nil {
 	if err := os.MkdirAll(dest, 0700); err != nil {
 		return err
 		return err
 	}
 	}

+ 28 - 1
plugin/manager.go

@@ -5,10 +5,12 @@ import (
 	"io"
 	"io"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"strings"
 	"sync"
 	"sync"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/plugin/store"
 	"github.com/docker/docker/plugin/store"
 	"github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
@@ -63,7 +65,7 @@ func Init(root string, ps *store.Store, remote libcontainerd.Remote, rs registry
 	root = filepath.Join(root, "plugins")
 	root = filepath.Join(root, "plugins")
 	manager = &Manager{
 	manager = &Manager{
 		libRoot:           root,
 		libRoot:           root,
-		runRoot:           "/run/docker",
+		runRoot:           "/run/docker/plugins",
 		pluginStore:       ps,
 		pluginStore:       ps,
 		registryService:   rs,
 		registryService:   rs,
 		liveRestore:       liveRestore,
 		liveRestore:       liveRestore,
@@ -104,6 +106,13 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
 		pm.mu.RUnlock()
 		pm.mu.RUnlock()
 
 
 		p.RemoveFromDisk()
 		p.RemoveFromDisk()
+
+		if p.PropagatedMount != "" {
+			if err := mount.Unmount(p.PropagatedMount); err != nil {
+				logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
+			}
+		}
+
 		if restart {
 		if restart {
 			pm.enable(p, c, true)
 			pm.enable(p, c, true)
 		}
 		}
@@ -141,6 +150,24 @@ func (pm *Manager) reload() error {
 				return
 				return
 			}
 			}
 
 
+			if p.Rootfs != "" {
+				p.Rootfs = filepath.Join(pm.libRoot, p.PluginObj.ID, "rootfs")
+			}
+
+			// We should only enable rootfs propagation for certain plugin types that need it.
+			for _, typ := range p.PluginObj.Config.Interface.Types {
+				if typ.Capability == "volumedriver" && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
+					if p.PluginObj.Config.PropagatedMount != "" {
+						// TODO: sanitize PropagatedMount and prevent breakout
+						p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
+						if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
+							logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err)
+							return
+						}
+					}
+				}
+			}
+
 			pm.pluginStore.Update(p)
 			pm.pluginStore.Update(p)
 			requiresManualRestore := !pm.liveRestore && p.IsEnabled()
 			requiresManualRestore := !pm.liveRestore && p.IsEnabled()
 
 

+ 9 - 1
plugin/manager_linux.go

@@ -11,16 +11,18 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/oci"
 	"github.com/docker/docker/oci"
+	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/plugins"
 	"github.com/docker/docker/pkg/plugins"
 	"github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/plugin/v2"
 	specs "github.com/opencontainers/runtime-spec/specs-go"
 	specs "github.com/opencontainers/runtime-spec/specs-go"
 )
 )
 
 
 func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
 func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
+	p.Rootfs = filepath.Join(pm.libRoot, p.PluginObj.ID, "rootfs")
 	if p.IsEnabled() && !force {
 	if p.IsEnabled() && !force {
 		return fmt.Errorf("plugin %s is already enabled", p.Name())
 		return fmt.Errorf("plugin %s is already enabled", p.Name())
 	}
 	}
-	spec, err := p.InitSpec(oci.DefaultSpec(), pm.libRoot)
+	spec, err := p.InitSpec(oci.DefaultSpec())
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -32,6 +34,12 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
 	pm.cMap[p] = c
 	pm.cMap[p] = c
 	pm.mu.Unlock()
 	pm.mu.Unlock()
 
 
+	if p.PropagatedMount != "" {
+		if err := mount.MakeRShared(p.PropagatedMount); err != nil {
+			return err
+		}
+	}
+
 	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
 	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
 		return err
 		return err
 	}
 	}

+ 36 - 31
plugin/v2/plugin.go

@@ -2,6 +2,7 @@ package v2
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -22,7 +23,9 @@ type Plugin struct {
 	pClient           *plugins.Client
 	pClient           *plugins.Client
 	runtimeSourcePath string
 	runtimeSourcePath string
 	refCount          int
 	refCount          int
-	libRoot           string
+	LibRoot           string // TODO: make private
+	PropagatedMount   string // TODO: make private
+	Rootfs            string // TODO: make private
 }
 }
 
 
 const defaultPluginRuntimeDestination = "/run/docker/plugins"
 const defaultPluginRuntimeDestination = "/run/docker/plugins"
@@ -45,7 +48,7 @@ func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
 	return &Plugin{
 	return &Plugin{
 		PluginObj:         newPluginObj(name, id, tag),
 		PluginObj:         newPluginObj(name, id, tag),
 		runtimeSourcePath: filepath.Join(runRoot, id),
 		runtimeSourcePath: filepath.Join(runRoot, id),
-		libRoot:           libRoot,
+		LibRoot:           libRoot,
 	}
 	}
 }
 }
 
 
@@ -63,6 +66,12 @@ func (p *Plugin) GetRuntimeSourcePath() string {
 	return p.runtimeSourcePath
 	return p.runtimeSourcePath
 }
 }
 
 
+// BasePath returns the path to which all paths returned by the plugin are relative to.
+// For Plugin objects this returns the host path of the plugin container's rootfs.
+func (p *Plugin) BasePath() string {
+	return p.Rootfs
+}
+
 // Client returns the plugin client.
 // Client returns the plugin client.
 func (p *Plugin) Client() *plugins.Client {
 func (p *Plugin) Client() *plugins.Client {
 	p.mu.RLock()
 	p.mu.RLock()
@@ -112,7 +121,7 @@ func (p *Plugin) RemoveFromDisk() error {
 
 
 // InitPlugin populates the plugin object from the plugin config file.
 // InitPlugin populates the plugin object from the plugin config file.
 func (p *Plugin) InitPlugin() error {
 func (p *Plugin) InitPlugin() error {
-	dt, err := os.Open(filepath.Join(p.libRoot, p.PluginObj.ID, "config.json"))
+	dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json"))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -123,9 +132,7 @@ func (p *Plugin) InitPlugin() error {
 	}
 	}
 
 
 	p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
 	p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
-	for i, mount := range p.PluginObj.Config.Mounts {
-		p.PluginObj.Settings.Mounts[i] = mount
-	}
+	copy(p.PluginObj.Settings.Mounts, p.PluginObj.Config.Mounts)
 	p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
 	p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
 	p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
 	p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
 	copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
 	copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
@@ -134,13 +141,14 @@ func (p *Plugin) InitPlugin() error {
 			p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
 			p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
 		}
 		}
 	}
 	}
+	p.PluginObj.Settings.Args = make([]string, len(p.PluginObj.Config.Args.Value))
 	copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
 	copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
 
 
 	return p.writeSettings()
 	return p.writeSettings()
 }
 }
 
 
 func (p *Plugin) writeSettings() error {
 func (p *Plugin) writeSettings() error {
-	f, err := os.Create(filepath.Join(p.libRoot, p.PluginObj.ID, "plugin-settings.json"))
+	f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json"))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -287,18 +295,21 @@ func (p *Plugin) SetRefCount(count int) {
 }
 }
 
 
 // InitSpec creates an OCI spec from the plugin's config.
 // InitSpec creates an OCI spec from the plugin's config.
-func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
-	rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
+func (p *Plugin) InitSpec(s specs.Spec) (*specs.Spec, error) {
 	s.Root = specs.Root{
 	s.Root = specs.Root{
-		Path:     rootfs,
+		Path:     p.Rootfs,
 		Readonly: false, // TODO: all plugins should be readonly? settable in config?
 		Readonly: false, // TODO: all plugins should be readonly? settable in config?
 	}
 	}
 
 
-	userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
-	for _, m := range p.PluginObj.Config.Mounts {
+	userMounts := make(map[string]struct{}, len(p.PluginObj.Settings.Mounts))
+	for _, m := range p.PluginObj.Settings.Mounts {
 		userMounts[m.Destination] = struct{}{}
 		userMounts[m.Destination] = struct{}{}
 	}
 	}
 
 
+	if err := os.MkdirAll(p.runtimeSourcePath, 0755); err != nil {
+		return nil, err
+	}
+
 	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
 	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
 		Source:      &p.runtimeSourcePath,
 		Source:      &p.runtimeSourcePath,
 		Destination: defaultPluginRuntimeDestination,
 		Destination: defaultPluginRuntimeDestination,
@@ -328,27 +339,16 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
 			})
 			})
 	}
 	}
 
 
-	for _, mount := range mounts {
+	for _, mnt := range mounts {
 		m := specs.Mount{
 		m := specs.Mount{
-			Destination: mount.Destination,
-			Type:        mount.Type,
-			Options:     mount.Options,
+			Destination: mnt.Destination,
+			Type:        mnt.Type,
+			Options:     mnt.Options,
 		}
 		}
-		// TODO: if nil, then it's required and user didn't set it
-		if mount.Source != nil {
-			m.Source = *mount.Source
-		}
-		if m.Source != "" && m.Type == "bind" {
-			fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks
-			if err != nil {
-				return nil, err
-			}
-			if fi.IsDir() {
-				if err := os.MkdirAll(m.Source, 0700); err != nil {
-					return nil, err
-				}
-			}
+		if mnt.Source == nil {
+			return nil, errors.New("mount source is not specified")
 		}
 		}
+		m.Source = *mnt.Source
 		s.Mounts = append(s.Mounts, m)
 		s.Mounts = append(s.Mounts, m)
 	}
 	}
 
 
@@ -360,11 +360,16 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
 		}
 		}
 	}
 	}
 
 
+	if p.PluginObj.Config.PropagatedMount != "" {
+		p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
+		s.Linux.RootfsPropagation = "rshared"
+	}
+
 	if p.PluginObj.Config.Linux.DeviceCreation {
 	if p.PluginObj.Config.Linux.DeviceCreation {
 		rwm := "rwm"
 		rwm := "rwm"
 		s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
 		s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
 	}
 	}
-	for _, dev := range p.PluginObj.Config.Linux.Devices {
+	for _, dev := range p.PluginObj.Settings.Devices {
 		path := *dev.Path
 		path := *dev.Path
 		d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
 		d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
 		if err != nil {
 		if err != nil {

+ 34 - 20
volume/drivers/adapter.go

@@ -2,6 +2,7 @@ package volumedrivers
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"path/filepath"
 	"strings"
 	"strings"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -14,6 +15,7 @@ var (
 
 
 type volumeDriverAdapter struct {
 type volumeDriverAdapter struct {
 	name         string
 	name         string
+	baseHostPath string
 	capabilities *volume.Capability
 	capabilities *volume.Capability
 	proxy        *volumeDriverProxy
 	proxy        *volumeDriverProxy
 }
 }
@@ -27,9 +29,10 @@ func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volum
 		return nil, err
 		return nil, err
 	}
 	}
 	return &volumeAdapter{
 	return &volumeAdapter{
-		proxy:      a.proxy,
-		name:       name,
-		driverName: a.name,
+		proxy:        a.proxy,
+		name:         name,
+		driverName:   a.name,
+		baseHostPath: a.baseHostPath,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -37,6 +40,13 @@ func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
 	return a.proxy.Remove(v.Name())
 	return a.proxy.Remove(v.Name())
 }
 }
 
 
+func hostPath(baseHostPath, path string) string {
+	if baseHostPath != "" {
+		path = filepath.Join(baseHostPath, path)
+	}
+	return path
+}
+
 func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
 func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
 	ls, err := a.proxy.List()
 	ls, err := a.proxy.List()
 	if err != nil {
 	if err != nil {
@@ -46,10 +56,11 @@ func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
 	var out []volume.Volume
 	var out []volume.Volume
 	for _, vp := range ls {
 	for _, vp := range ls {
 		out = append(out, &volumeAdapter{
 		out = append(out, &volumeAdapter{
-			proxy:      a.proxy,
-			name:       vp.Name,
-			driverName: a.name,
-			eMount:     vp.Mountpoint,
+			proxy:        a.proxy,
+			name:         vp.Name,
+			baseHostPath: a.baseHostPath,
+			driverName:   a.name,
+			eMount:       hostPath(a.baseHostPath, vp.Mountpoint),
 		})
 		})
 	}
 	}
 	return out, nil
 	return out, nil
@@ -67,11 +78,12 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
 	}
 	}
 
 
 	return &volumeAdapter{
 	return &volumeAdapter{
-		proxy:      a.proxy,
-		name:       v.Name,
-		driverName: a.Name(),
-		eMount:     v.Mountpoint,
-		status:     v.Status,
+		proxy:        a.proxy,
+		name:         v.Name,
+		driverName:   a.Name(),
+		eMount:       v.Mountpoint,
+		status:       v.Status,
+		baseHostPath: a.baseHostPath,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -108,11 +120,12 @@ func (a *volumeDriverAdapter) getCapabilities() volume.Capability {
 }
 }
 
 
 type volumeAdapter struct {
 type volumeAdapter struct {
-	proxy      *volumeDriverProxy
-	name       string
-	driverName string
-	eMount     string // ephemeral host volume path
-	status     map[string]interface{}
+	proxy        *volumeDriverProxy
+	name         string
+	baseHostPath string
+	driverName   string
+	eMount       string // ephemeral host volume path
+	status       map[string]interface{}
 }
 }
 
 
 type proxyVolume struct {
 type proxyVolume struct {
@@ -131,7 +144,8 @@ func (a *volumeAdapter) DriverName() string {
 
 
 func (a *volumeAdapter) Path() string {
 func (a *volumeAdapter) Path() string {
 	if len(a.eMount) == 0 {
 	if len(a.eMount) == 0 {
-		a.eMount, _ = a.proxy.Path(a.name)
+		mountpoint, _ := a.proxy.Path(a.name)
+		a.eMount = hostPath(a.baseHostPath, mountpoint)
 	}
 	}
 	return a.eMount
 	return a.eMount
 }
 }
@@ -141,8 +155,8 @@ func (a *volumeAdapter) CachedPath() string {
 }
 }
 
 
 func (a *volumeAdapter) Mount(id string) (string, error) {
 func (a *volumeAdapter) Mount(id string) (string, error) {
-	var err error
-	a.eMount, err = a.proxy.Mount(a.name, id)
+	mountpoint, err := a.proxy.Mount(a.name, id)
+	a.eMount = hostPath(a.baseHostPath, mountpoint)
 	return a.eMount, err
 	return a.eMount, err
 }
 }
 
 

+ 4 - 4
volume/drivers/extpoint.go

@@ -22,9 +22,9 @@ var drivers = &driverExtpoint{
 const extName = "VolumeDriver"
 const extName = "VolumeDriver"
 
 
 // NewVolumeDriver returns a driver has the given name mapped on the given client.
 // NewVolumeDriver returns a driver has the given name mapped on the given client.
-func NewVolumeDriver(name string, c client) volume.Driver {
+func NewVolumeDriver(name string, baseHostPath string, c client) volume.Driver {
 	proxy := &volumeDriverProxy{c}
 	proxy := &volumeDriverProxy{c}
-	return &volumeDriverAdapter{name: name, proxy: proxy}
+	return &volumeDriverAdapter{name: name, baseHostPath: baseHostPath, proxy: proxy}
 }
 }
 
 
 // volumeDriver defines the available functions that volume plugins must implement.
 // volumeDriver defines the available functions that volume plugins must implement.
@@ -117,7 +117,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
 		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
 		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
 	}
 	}
 
 
-	d := NewVolumeDriver(p.Name(), p.Client())
+	d := NewVolumeDriver(p.Name(), p.BasePath(), p.Client())
 	if err := validateDriver(d); err != nil {
 	if err := validateDriver(d); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -199,7 +199,7 @@ func GetAllDrivers() ([]volume.Driver, error) {
 			continue
 			continue
 		}
 		}
 
 
-		ext = NewVolumeDriver(name, p.Client())
+		ext = NewVolumeDriver(name, p.BasePath(), p.Client())
 		if p.IsV1() {
 		if p.IsV1() {
 			drivers.extensions[name] = ext
 			drivers.extensions[name] = ext
 		}
 		}

+ 1 - 0
volume/drivers/proxy.go

@@ -4,6 +4,7 @@ package volumedrivers
 
 
 import (
 import (
 	"errors"
 	"errors"
+
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 )
 )