浏览代码

Reorganize plugin package into sub packages.

Split plugin package into `store` and `v2/plugin`. Now the functionality
is clearly delineated:
- Manager: Manages the global state of the plugin sub-system.
- PluginStore: Manages a collection of plugins (in memory and on-disk)
- Plugin: Manages the single plugin unit.

This also facilitates splitting the global PluginManager lock into:
- PluginManager lock to protect global states.
- PluginStore lock to protect store states.
- Plugin lock to protect individual plugin states.

Importing "github.com/docker/docker/plugin/store" will provide access
to plugins and has lesser dependencies when compared to importing the
original monolithic `plugin package`.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>
Anusha Ragunathan 8 年之前
父节点
当前提交
27a55fba28
共有 10 个文件被更改,包括 595 次插入447 次删除
  1. 32 25
      plugin/backend.go
  2. 0 10
      plugin/interface.go
  3. 19 295
      plugin/manager.go
  4. 34 105
      plugin/manager_linux.go
  5. 5 4
      plugin/manager_windows.go
  6. 10 0
      plugin/store/interface.go
  7. 7 5
      plugin/store/legacy.go
  8. 224 0
      plugin/store/store.go
  9. 261 0
      plugin/v2/plugin.go
  10. 3 3
      volume/drivers/extpoint.go

+ 32 - 25
plugin/backend.go

@@ -15,39 +15,40 @@ import (
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/plugin/distribution"
+	"github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/reference"
 	"github.com/docker/engine-api/types"
 )
 
 // Disable deactivates a plugin, which implies that they cannot be used by containers.
 func (pm *Manager) Disable(name string) error {
-	p, err := pm.get(name)
+	p, err := pm.pluginStore.GetByName(name)
 	if err != nil {
 		return err
 	}
 	if err := pm.disable(p); err != nil {
 		return err
 	}
-	pm.pluginEventLogger(p.PluginObj.ID, name, "disable")
+	pm.pluginEventLogger(p.GetID(), name, "disable")
 	return nil
 }
 
 // Enable activates a plugin, which implies that they are ready to be used by containers.
 func (pm *Manager) Enable(name string) error {
-	p, err := pm.get(name)
+	p, err := pm.pluginStore.GetByName(name)
 	if err != nil {
 		return err
 	}
 	if err := pm.enable(p, false); err != nil {
 		return err
 	}
-	pm.pluginEventLogger(p.PluginObj.ID, name, "enable")
+	pm.pluginEventLogger(p.GetID(), name, "enable")
 	return nil
 }
 
 // Inspect examines a plugin manifest
 func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
-	p, err := pm.get(name)
+	p, err := pm.pluginStore.GetByName(name)
 	if err != nil {
 		return tp, err
 	}
@@ -63,7 +64,7 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
 	}
 	name = ref.String()
 
-	if p, _ := pm.get(name); p != nil {
+	if p, _ := pm.pluginStore.GetByName(name); p != nil {
 		logrus.Debugf("plugin already exists")
 		return nil, fmt.Errorf("%s exists", name)
 	}
@@ -86,25 +87,25 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
 		return nil, err
 	}
 
-	p := pm.newPlugin(ref, pluginID)
-	if err := pm.initPlugin(p); err != nil {
+	var tag string
+	if ref, ok := ref.(reference.NamedTagged); ok {
+		tag = ref.Tag()
+	}
+	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, tag)
+	if err := p.InitPlugin(pm.libRoot); err != nil {
 		return nil, err
 	}
-
-	pm.Lock()
-	pm.plugins[pluginID] = p
-	pm.nameToID[name] = pluginID
-	pm.save()
-	pm.Unlock()
+	pm.pluginStore.Add(p)
 
 	pm.pluginEventLogger(pluginID, name, "pull")
-	return computePrivileges(&p.PluginObj.Manifest), nil
+	return p.ComputePrivileges(), nil
 }
 
 // List displays the list of plugins and associated metadata.
 func (pm *Manager) List() ([]types.Plugin, error) {
-	out := make([]types.Plugin, 0, len(pm.plugins))
-	for _, p := range pm.plugins {
+	plugins := pm.pluginStore.GetAll()
+	out := make([]types.Plugin, 0, len(plugins))
+	for _, p := range plugins {
 		out = append(out, p.PluginObj)
 	}
 	return out, nil
@@ -112,11 +113,11 @@ func (pm *Manager) List() ([]types.Plugin, error) {
 
 // Push pushes a plugin to the store.
 func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.AuthConfig) error {
-	p, err := pm.get(name)
+	p, err := pm.pluginStore.GetByName(name)
 	if err != nil {
 		return err
 	}
-	dest := filepath.Join(pm.libRoot, p.PluginObj.ID)
+	dest := filepath.Join(pm.libRoot, p.GetID())
 	config, err := ioutil.ReadFile(filepath.Join(dest, "manifest.json"))
 	if err != nil {
 		return err
@@ -142,22 +143,28 @@ func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.A
 
 // Remove deletes plugin's root directory.
 func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
-	p, err := pm.get(name)
+	p, err := pm.pluginStore.GetByName(name)
 	if err != nil {
 		return err
 	}
-	if err := pm.remove(p, config.ForceRemove); err != nil {
-		return err
+	if p.IsEnabled() {
+		if !config.ForceRemove {
+			return fmt.Errorf("plugin %s is enabled", p.Name())
+		}
+		if err := pm.disable(p); err != nil {
+			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
+		}
 	}
-	pm.pluginEventLogger(p.PluginObj.ID, name, "remove")
+	pm.pluginStore.Remove(p)
+	pm.pluginEventLogger(p.GetID(), name, "remove")
 	return nil
 }
 
 // Set sets plugin args
 func (pm *Manager) Set(name string, args []string) error {
-	p, err := pm.get(name)
+	p, err := pm.pluginStore.GetByName(name)
 	if err != nil {
 		return err
 	}
-	return pm.set(p, args)
+	return p.Set(args)
 }

+ 0 - 10
plugin/interface.go

@@ -1,10 +0,0 @@
-package plugin
-
-import "github.com/docker/docker/pkg/plugins"
-
-// Plugin represents a plugin. It is used to abstract from an older plugin architecture (in pkg/plugins).
-type Plugin interface {
-	Client() *plugins.Client
-	Name() string
-	IsLegacy() bool
-}

+ 19 - 295
plugin/manager.go

@@ -4,7 +4,6 @@ package plugin
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -14,16 +13,12 @@ import (
 
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/libcontainerd"
-	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/plugins"
-	"github.com/docker/docker/reference"
+	"github.com/docker/docker/plugin/store"
+	"github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/registry"
-	"github.com/docker/docker/restartmanager"
-	"github.com/docker/engine-api/types"
 )
 
-const defaultPluginRuntimeDestination = "/run/docker/plugins"
-
 var (
 	manager *Manager
 
@@ -34,71 +29,14 @@ var (
 	allowV1PluginsFallback = true
 )
 
-// ErrNotFound indicates that a plugin was not found locally.
-type ErrNotFound string
-
-func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
-
-// ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
-type ErrInadequateCapability struct {
-	name       string
-	capability string
-}
-
-func (e ErrInadequateCapability) Error() string {
-	return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
-}
-
-type plugin struct {
-	//sync.RWMutex TODO
-	PluginObj         types.Plugin `json:"plugin"`
-	client            *plugins.Client
-	restartManager    restartmanager.RestartManager
-	runtimeSourcePath string
-	exitChan          chan bool
-}
-
-func (p *plugin) Client() *plugins.Client {
-	return p.client
-}
-
-// IsLegacy returns true for legacy plugins and false otherwise.
-func (p *plugin) IsLegacy() bool {
-	return false
-}
-
-func (p *plugin) Name() string {
-	name := p.PluginObj.Name
-	if len(p.PluginObj.Tag) > 0 {
-		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
-		name += ":" + p.PluginObj.Tag
-	}
-	return name
-}
-
-func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
-	p := &plugin{
-		PluginObj: types.Plugin{
-			Name: ref.Name(),
-			ID:   id,
-		},
-		runtimeSourcePath: filepath.Join(pm.runRoot, id),
-	}
-	if ref, ok := ref.(reference.NamedTagged); ok {
-		p.PluginObj.Tag = ref.Tag()
-	}
-	return p
-}
-
-func (pm *Manager) restorePlugin(p *plugin) error {
-	p.runtimeSourcePath = filepath.Join(pm.runRoot, p.PluginObj.ID)
-	if p.PluginObj.Enabled {
+func (pm *Manager) restorePlugin(p *v2.Plugin) error {
+	p.RuntimeSourcePath = filepath.Join(pm.runRoot, p.GetID())
+	if p.IsEnabled() {
 		return pm.restore(p)
 	}
 	return nil
 }
 
-type pluginMap map[string]*plugin
 type eventLogger func(id, name, action string)
 
 // Manager controls the plugin subsystem.
@@ -106,8 +44,7 @@ type Manager struct {
 	sync.RWMutex
 	libRoot           string
 	runRoot           string
-	plugins           pluginMap // TODO: figure out why save() doesn't json encode *plugin object
-	nameToID          map[string]string
+	pluginStore       *store.PluginStore
 	handlers          map[string]func(string, *plugins.Client)
 	containerdClient  libcontainerd.Client
 	registryService   registry.Service
@@ -132,8 +69,7 @@ func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRes
 	manager = &Manager{
 		libRoot:           root,
 		runRoot:           "/run/docker",
-		plugins:           make(map[string]*plugin),
-		nameToID:          make(map[string]string),
+		pluginStore:       store.NewPluginStore(root),
 		handlers:          make(map[string]func(string, *plugins.Client)),
 		registryService:   rs,
 		liveRestore:       liveRestore,
@@ -162,105 +98,6 @@ func Handle(capability string, callback func(string, *plugins.Client)) {
 	}
 }
 
-func (pm *Manager) get(name string) (*plugin, error) {
-	pm.RLock()
-	defer pm.RUnlock()
-
-	id, nameOk := pm.nameToID[name]
-	if !nameOk {
-		return nil, ErrNotFound(name)
-	}
-
-	p, idOk := pm.plugins[id]
-	if !idOk {
-		return nil, ErrNotFound(name)
-	}
-
-	return p, nil
-}
-
-// FindWithCapability returns a list of plugins matching the given capability.
-func FindWithCapability(capability string) ([]Plugin, error) {
-	result := make([]Plugin, 0, 1)
-
-	/* Daemon start always calls plugin.Init thereby initializing a manager.
-	 * So manager on experimental builds can never be nil, even while
-	 * handling legacy plugins. However, there are legacy plugin unit
-	 * tests where volume subsystem directly talks with the plugin,
-	 * bypassing the daemon. For such tests, this check is necessary.*/
-	if manager != nil {
-		manager.RLock()
-		for _, p := range manager.plugins {
-			for _, typ := range p.PluginObj.Manifest.Interface.Types {
-				if strings.EqualFold(typ.Capability, capability) && typ.Prefix == "docker" {
-					result = append(result, p)
-					break
-				}
-			}
-		}
-		manager.RUnlock()
-	}
-
-	// Lookup with legacy model.
-	if allowV1PluginsFallback {
-		pl, err := plugins.GetAll(capability)
-		if err != nil {
-			return nil, fmt.Errorf("legacy plugin: %v", err)
-		}
-		for _, p := range pl {
-			result = append(result, p)
-		}
-	}
-	return result, nil
-}
-
-// LookupWithCapability returns a plugin matching the given name and capability.
-func LookupWithCapability(name, capability string) (Plugin, error) {
-	var (
-		p   *plugin
-		err error
-	)
-
-	// Lookup using new model.
-	if manager != nil {
-		fullName := name
-		if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
-			if reference.IsNameOnly(named) {
-				named = reference.WithDefaultTag(named)
-			}
-			ref, ok := named.(reference.NamedTagged)
-			if !ok {
-				return nil, fmt.Errorf("invalid name: %s", named.String())
-			}
-			fullName = ref.String()
-		}
-		p, err = manager.get(fullName)
-		if err == nil {
-			capability = strings.ToLower(capability)
-			for _, typ := range p.PluginObj.Manifest.Interface.Types {
-				if typ.Capability == capability && typ.Prefix == "docker" {
-					return p, nil
-				}
-			}
-			return nil, ErrInadequateCapability{name, capability}
-		}
-		if _, ok := err.(ErrNotFound); !ok {
-			return nil, err
-		}
-	}
-
-	// Lookup using legacy model
-	if allowV1PluginsFallback {
-		p, err := plugins.Get(name, capability)
-		if err != nil {
-			return nil, fmt.Errorf("legacy plugin: %v", err)
-		}
-		return p, nil
-	}
-
-	return nil, err
-}
-
 // StateChanged updates plugin internals using libcontainerd events.
 func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
 	logrus.Debugf("plugin state changed %s %#v", id, e)
@@ -272,13 +109,11 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
 		shutdown = pm.shutdown
 		pm.RUnlock()
 		if shutdown {
-			pm.RLock()
-			p, idOk := pm.plugins[id]
-			pm.RUnlock()
-			if !idOk {
-				return ErrNotFound(id)
+			p, err := pm.pluginStore.GetByID(id)
+			if err != nil {
+				return err
 			}
-			close(p.exitChan)
+			close(p.ExitChan)
 		}
 	}
 
@@ -313,24 +148,24 @@ func (pm *Manager) init() error {
 	}
 	defer dt.Close()
 
-	if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil {
+	plugins := make(map[string]*v2.Plugin)
+	if err := json.NewDecoder(dt).Decode(&plugins); err != nil {
 		return err
 	}
+	pm.pluginStore.SetAll(plugins)
 
 	var group sync.WaitGroup
-	group.Add(len(pm.plugins))
-	for _, p := range pm.plugins {
-		go func(p *plugin) {
+	group.Add(len(plugins))
+	for _, p := range plugins {
+		go func(p *v2.Plugin) {
 			defer group.Done()
 			if err := pm.restorePlugin(p); err != nil {
 				logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err)
 				return
 			}
 
-			pm.Lock()
-			pm.nameToID[p.Name()] = p.PluginObj.ID
-			requiresManualRestore := !pm.liveRestore && p.PluginObj.Enabled
-			pm.Unlock()
+			pm.pluginStore.Add(p)
+			requiresManualRestore := !pm.liveRestore && p.IsEnabled()
 
 			if requiresManualRestore {
 				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
@@ -341,80 +176,6 @@ func (pm *Manager) init() error {
 		}(p)
 	}
 	group.Wait()
-	return pm.save()
-}
-
-func (pm *Manager) initPlugin(p *plugin) error {
-	dt, err := os.Open(filepath.Join(pm.libRoot, p.PluginObj.ID, "manifest.json"))
-	if err != nil {
-		return err
-	}
-	err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
-	dt.Close()
-	if err != nil {
-		return err
-	}
-
-	p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
-	for i, mount := range p.PluginObj.Manifest.Mounts {
-		p.PluginObj.Config.Mounts[i] = mount
-	}
-	p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
-	for _, env := range p.PluginObj.Manifest.Env {
-		if env.Value != nil {
-			p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
-		}
-	}
-	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
-
-	f, err := os.Create(filepath.Join(pm.libRoot, p.PluginObj.ID, "plugin-config.json"))
-	if err != nil {
-		return err
-	}
-	err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
-	f.Close()
-	return err
-}
-
-func (pm *Manager) remove(p *plugin, force bool) error {
-	if p.PluginObj.Enabled {
-		if !force {
-			return fmt.Errorf("plugin %s is enabled", p.Name())
-		}
-		if err := pm.disable(p); err != nil {
-			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
-		}
-	}
-	pm.Lock() // fixme: lock single record
-	defer pm.Unlock()
-	delete(pm.plugins, p.PluginObj.ID)
-	delete(pm.nameToID, p.Name())
-	pm.save()
-	return os.RemoveAll(filepath.Join(pm.libRoot, p.PluginObj.ID))
-}
-
-func (pm *Manager) set(p *plugin, args []string) error {
-	m := make(map[string]string, len(args))
-	for _, arg := range args {
-		i := strings.Index(arg, "=")
-		if i < 0 {
-			return fmt.Errorf("no equal sign '=' found in %s", arg)
-		}
-		m[arg[:i]] = arg[i+1:]
-	}
-	return errors.New("not implemented")
-}
-
-// fixme: not safe
-func (pm *Manager) save() error {
-	filePath := filepath.Join(pm.libRoot, "plugins.json")
-
-	jsonData, err := json.Marshal(pm.plugins)
-	if err != nil {
-		logrus.Debugf("failure in json.Marshal: %v", err)
-		return err
-	}
-	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
 	return nil
 }
 
@@ -428,40 +189,3 @@ func (l logHook) Fire(entry *logrus.Entry) error {
 	entry.Data = logrus.Fields{"plugin": l.id}
 	return nil
 }
-
-func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
-	var privileges types.PluginPrivileges
-	if m.Network.Type != "null" && m.Network.Type != "bridge" {
-		privileges = append(privileges, types.PluginPrivilege{
-			Name:        "network",
-			Description: "",
-			Value:       []string{m.Network.Type},
-		})
-	}
-	for _, mount := range m.Mounts {
-		if mount.Source != nil {
-			privileges = append(privileges, types.PluginPrivilege{
-				Name:        "mount",
-				Description: "",
-				Value:       []string{*mount.Source},
-			})
-		}
-	}
-	for _, device := range m.Devices {
-		if device.Path != nil {
-			privileges = append(privileges, types.PluginPrivilege{
-				Name:        "device",
-				Description: "",
-				Value:       []string{*device.Path},
-			})
-		}
-	}
-	if len(m.Capabilities) > 0 {
-		privileges = append(privileges, types.PluginPrivilege{
-			Name:        "capabilities",
-			Description: "",
-			Value:       m.Capabilities,
-		})
-	}
-	return privileges
-}

+ 34 - 105
plugin/manager_linux.go

@@ -4,7 +4,6 @@ package plugin
 
 import (
 	"fmt"
-	"os"
 	"path/filepath"
 	"syscall"
 	"time"
@@ -13,45 +12,38 @@ import (
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/oci"
 	"github.com/docker/docker/pkg/plugins"
-	"github.com/docker/docker/pkg/system"
+	"github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/restartmanager"
-	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
-	"github.com/opencontainers/runtime-spec/specs-go"
 )
 
-func (pm *Manager) enable(p *plugin, force bool) error {
-	if p.PluginObj.Enabled && !force {
+func (pm *Manager) enable(p *v2.Plugin, force bool) error {
+	if p.IsEnabled() && !force {
 		return fmt.Errorf("plugin %s is already enabled", p.Name())
 	}
-	spec, err := pm.initSpec(p)
+	spec, err := p.InitSpec(oci.DefaultSpec(), pm.libRoot)
 	if err != nil {
 		return err
 	}
 
-	p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
-	if err := pm.containerdClient.Create(p.PluginObj.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only
-		if err := p.restartManager.Cancel(); err != nil {
+	p.RestartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
+	if err := pm.containerdClient.Create(p.GetID(), libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.RestartManager)); err != nil {
+		if err := p.RestartManager.Cancel(); err != nil {
 			logrus.Errorf("enable: restartManager.Cancel failed due to %v", err)
 		}
 		return err
 	}
 
-	socket := p.PluginObj.Manifest.Interface.Socket
-	p.client, err = plugins.NewClient("unix://"+filepath.Join(p.runtimeSourcePath, socket), nil)
+	p.PClient, err = plugins.NewClient("unix://"+filepath.Join(p.RuntimeSourcePath, p.GetSocket()), nil)
 	if err != nil {
-		if err := p.restartManager.Cancel(); err != nil {
+		if err := p.RestartManager.Cancel(); err != nil {
 			logrus.Errorf("enable: restartManager.Cancel failed due to %v", err)
 		}
 		return err
 	}
 
-	pm.Lock() // fixme: lock single record
-	p.PluginObj.Enabled = true
-	pm.save()
-	pm.Unlock()
-
-	for _, typ := range p.PluginObj.Manifest.Interface.Types {
+	pm.pluginStore.SetState(p, true)
+	for _, typ := range p.GetTypes() {
 		if handler := pm.handlers[typ.String()]; handler != nil {
 			handler(p.Name(), p.Client())
 		}
@@ -60,90 +52,25 @@ func (pm *Manager) enable(p *plugin, force bool) error {
 	return nil
 }
 
-func (pm *Manager) restore(p *plugin) error {
-	p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
-	return pm.containerdClient.Restore(p.PluginObj.ID, libcontainerd.WithRestartManager(p.restartManager))
+func (pm *Manager) restore(p *v2.Plugin) error {
+	p.RestartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
+	return pm.containerdClient.Restore(p.GetID(), libcontainerd.WithRestartManager(p.RestartManager))
 }
 
-func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
-	s := oci.DefaultSpec()
-
-	rootfs := filepath.Join(pm.libRoot, p.PluginObj.ID, "rootfs")
-	s.Root = specs.Root{
-		Path:     rootfs,
-		Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
-	}
-
-	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
-		Source:      &p.runtimeSourcePath,
-		Destination: defaultPluginRuntimeDestination,
-		Type:        "bind",
-		Options:     []string{"rbind", "rshared"},
-	})
-	for _, mount := range mounts {
-		m := specs.Mount{
-			Destination: mount.Destination,
-			Type:        mount.Type,
-			Options:     mount.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" {
-			/* Debugging issue #25511: Volumes and other content created under the
-			bind mount should be recursively propagated. rshared, not shared.
-			This could be the reason for EBUSY during removal. Override options
-			with rbind, rshared and see if CI errors are fixed. */
-			m.Options = []string{"rbind", "rshared"}
-			fi, err := os.Lstat(filepath.Join(rootfs, string(os.PathSeparator), 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
-				}
-			}
-		}
-		s.Mounts = append(s.Mounts, m)
-	}
-
-	envs := make([]string, 1, len(p.PluginObj.Config.Env)+1)
-	envs[0] = "PATH=" + system.DefaultPathEnv
-	envs = append(envs, p.PluginObj.Config.Env...)
-
-	args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...)
-	cwd := p.PluginObj.Manifest.Workdir
-	if len(cwd) == 0 {
-		cwd = "/"
-	}
-	s.Process = specs.Process{
-		Terminal: false,
-		Args:     args,
-		Cwd:      cwd,
-		Env:      envs,
-	}
-
-	return &s, nil
-}
-
-func (pm *Manager) disable(p *plugin) error {
-	if !p.PluginObj.Enabled {
+func (pm *Manager) disable(p *v2.Plugin) error {
+	if !p.IsEnabled() {
 		return fmt.Errorf("plugin %s is already disabled", p.Name())
 	}
-	if err := p.restartManager.Cancel(); err != nil {
+	if err := p.RestartManager.Cancel(); err != nil {
+		logrus.Error(err)
+	}
+	if err := pm.containerdClient.Signal(p.GetID(), int(syscall.SIGKILL)); err != nil {
 		logrus.Error(err)
 	}
-	if err := pm.containerdClient.Signal(p.PluginObj.ID, int(syscall.SIGKILL)); err != nil {
+	if err := p.RemoveFromDisk(); err != nil {
 		logrus.Error(err)
 	}
-	os.RemoveAll(p.runtimeSourcePath)
-	pm.Lock() // fixme: lock single record
-	defer pm.Unlock()
-	p.PluginObj.Enabled = false
-	pm.save()
+	pm.pluginStore.SetState(p, false)
 	return nil
 }
 
@@ -155,34 +82,36 @@ func (pm *Manager) Shutdown() {
 
 	pm.RLock()
 	defer pm.RUnlock()
-	for _, p := range pm.plugins {
-		if pm.liveRestore && p.PluginObj.Enabled {
-			logrus.Debug("Plugin enabled when liveRestore is set, skipping shutdown")
+	plugins := pm.pluginStore.GetAll()
+	for _, p := range plugins {
+		if pm.liveRestore && p.IsEnabled() {
+			logrus.Debug("Plugin active when liveRestore is set, skipping shutdown")
 			continue
 		}
-		if p.restartManager != nil {
-			if err := p.restartManager.Cancel(); err != nil {
+		if p.RestartManager != nil {
+			if err := p.RestartManager.Cancel(); err != nil {
 				logrus.Error(err)
 			}
 		}
-		if pm.containerdClient != nil && p.PluginObj.Enabled {
-			p.exitChan = make(chan bool)
+		if pm.containerdClient != nil && p.IsEnabled() {
+			pluginID := p.GetID()
+			p.ExitChan = make(chan bool)
 			err := pm.containerdClient.Signal(p.PluginObj.ID, int(syscall.SIGTERM))
 			if err != nil {
 				logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err)
 			} else {
 				select {
-				case <-p.exitChan:
+				case <-p.ExitChan:
 					logrus.Debug("Clean shutdown of plugin")
 				case <-time.After(time.Second * 10):
 					logrus.Debug("Force shutdown plugin")
-					if err := pm.containerdClient.Signal(p.PluginObj.ID, int(syscall.SIGKILL)); err != nil {
+					if err := pm.containerdClient.Signal(pluginID, int(syscall.SIGKILL)); err != nil {
 						logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err)
 					}
 				}
 			}
 		}
-		if err := os.RemoveAll(p.runtimeSourcePath); err != nil {
+		if err := p.RemoveFromDisk(); err != nil {
 			logrus.Errorf("Remove plugin runtime failed with error: %v", err)
 		}
 	}

+ 5 - 4
plugin/manager_windows.go

@@ -5,22 +5,23 @@ package plugin
 import (
 	"fmt"
 
+	"github.com/docker/docker/plugin/v2"
 	"github.com/opencontainers/runtime-spec/specs-go"
 )
 
-func (pm *Manager) enable(p *plugin, force bool) error {
+func (pm *Manager) enable(p *v2.Plugin, force bool) error {
 	return fmt.Errorf("Not implemented")
 }
 
-func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
+func (pm *Manager) initSpec(p *v2.Plugin) (*specs.Spec, error) {
 	return nil, fmt.Errorf("Not implemented")
 }
 
-func (pm *Manager) disable(p *plugin) error {
+func (pm *Manager) disable(p *v2.Plugin) error {
 	return fmt.Errorf("Not implemented")
 }
 
-func (pm *Manager) restore(p *plugin) error {
+func (pm *Manager) restore(p *v2.Plugin) error {
 	return fmt.Errorf("Not implemented")
 }
 

+ 10 - 0
plugin/store/interface.go

@@ -0,0 +1,10 @@
+package store
+
+import "github.com/docker/docker/pkg/plugins"
+
+// CompatPlugin is a abstraction to handle both new and legacy (v1) plugins.
+type CompatPlugin interface {
+	Client() *plugins.Client
+	Name() string
+	IsLegacy() bool
+}

+ 7 - 5
plugin/legacy.go → plugin/store/legacy.go

@@ -1,16 +1,18 @@
 // +build !experimental
 
-package plugin
+package store
 
-import "github.com/docker/docker/pkg/plugins"
+import (
+	"github.com/docker/docker/pkg/plugins"
+)
 
 // FindWithCapability returns a list of plugins matching the given capability.
-func FindWithCapability(capability string) ([]Plugin, error) {
+func FindWithCapability(capability string) ([]CompatPlugin, error) {
 	pl, err := plugins.GetAll(capability)
 	if err != nil {
 		return nil, err
 	}
-	result := make([]Plugin, len(pl))
+	result := make([]CompatPlugin, len(pl))
 	for i, p := range pl {
 		result[i] = p
 	}
@@ -18,6 +20,6 @@ func FindWithCapability(capability string) ([]Plugin, error) {
 }
 
 // LookupWithCapability returns a plugin matching the given name and capability.
-func LookupWithCapability(name, capability string) (Plugin, error) {
+func LookupWithCapability(name, capability string) (CompatPlugin, error) {
 	return plugins.Get(name, capability)
 }

+ 224 - 0
plugin/store/store.go

@@ -0,0 +1,224 @@
+// +build experimental
+
+package store
+
+import (
+	"encoding/json"
+	"fmt"
+	"path/filepath"
+	"sync"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/plugin/v2"
+	"github.com/docker/docker/reference"
+)
+
+var (
+	store *PluginStore
+	/* allowV1PluginsFallback determines daemon's support for V1 plugins.
+	 * When the time comes to remove support for V1 plugins, flipping
+	 * this bool is all that will be needed.
+	 */
+	allowV1PluginsFallback = true
+)
+
+// ErrNotFound indicates that a plugin was not found locally.
+type ErrNotFound string
+
+func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
+
+// PluginStore manages the plugin inventory in memory and on-disk
+type PluginStore struct {
+	sync.RWMutex
+	plugins  map[string]*v2.Plugin
+	nameToID map[string]string
+	plugindb string
+}
+
+// NewPluginStore creates a PluginStore.
+func NewPluginStore(libRoot string) *PluginStore {
+	store = &PluginStore{
+		plugins:  make(map[string]*v2.Plugin),
+		nameToID: make(map[string]string),
+		plugindb: filepath.Join(libRoot, "plugins.json"),
+	}
+	return store
+}
+
+// GetByName retreives a plugin by name.
+func (ps *PluginStore) GetByName(name string) (*v2.Plugin, error) {
+	ps.RLock()
+	defer ps.RUnlock()
+
+	id, nameOk := ps.nameToID[name]
+	if !nameOk {
+		return nil, ErrNotFound(name)
+	}
+
+	p, idOk := ps.plugins[id]
+	if !idOk {
+		return nil, ErrNotFound(id)
+	}
+	return p, nil
+}
+
+// GetByID retreives a plugin by ID.
+func (ps *PluginStore) GetByID(id string) (*v2.Plugin, error) {
+	ps.RLock()
+	defer ps.RUnlock()
+
+	p, idOk := ps.plugins[id]
+	if !idOk {
+		return nil, ErrNotFound(id)
+	}
+	return p, nil
+}
+
+// GetAll retreives all plugins.
+func (ps *PluginStore) GetAll() map[string]*v2.Plugin {
+	ps.RLock()
+	defer ps.RUnlock()
+	return ps.plugins
+}
+
+// SetAll initialized plugins during daemon restore.
+func (ps *PluginStore) SetAll(plugins map[string]*v2.Plugin) {
+	ps.Lock()
+	defer ps.Unlock()
+	ps.plugins = plugins
+}
+
+func (ps *PluginStore) getByCap(name string, capability string) (*v2.Plugin, error) {
+	ps.RLock()
+	defer ps.RUnlock()
+
+	p, err := ps.GetByName(name)
+	if err != nil {
+		return nil, err
+	}
+	return p.FilterByCap(capability)
+}
+
+func (ps *PluginStore) getAllByCap(capability string) []CompatPlugin {
+	ps.RLock()
+	defer ps.RUnlock()
+
+	result := make([]CompatPlugin, 0, 1)
+	for _, p := range ps.plugins {
+		if _, err := p.FilterByCap(capability); err == nil {
+			result = append(result, p)
+		}
+	}
+	return result
+}
+
+// SetState sets the active state of the plugin and updates plugindb.
+func (ps *PluginStore) SetState(p *v2.Plugin, state bool) {
+	ps.Lock()
+	defer ps.Unlock()
+
+	p.PluginObj.Enabled = state
+	ps.updatePluginDB()
+}
+
+// Add adds a plugin to memory and plugindb.
+func (ps *PluginStore) Add(p *v2.Plugin) {
+	ps.Lock()
+	ps.plugins[p.GetID()] = p
+	ps.nameToID[p.Name()] = p.GetID()
+	ps.updatePluginDB()
+	ps.Unlock()
+}
+
+// Remove removes a plugin from memory, plugindb and disk.
+func (ps *PluginStore) Remove(p *v2.Plugin) {
+	ps.Lock()
+	delete(ps.plugins, p.GetID())
+	delete(ps.nameToID, p.Name())
+	ps.updatePluginDB()
+	p.RemoveFromDisk()
+	ps.Unlock()
+}
+
+// Callers are expected to hold the store lock.
+func (ps *PluginStore) updatePluginDB() error {
+	jsonData, err := json.Marshal(ps.plugins)
+	if err != nil {
+		logrus.Debugf("Error in json.Marshal: %v", err)
+		return err
+	}
+	ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600)
+	return nil
+}
+
+// LookupWithCapability returns a plugin matching the given name and capability.
+func LookupWithCapability(name, capability string) (CompatPlugin, error) {
+	var (
+		p   *v2.Plugin
+		err error
+	)
+
+	// Lookup using new model.
+	if store != nil {
+		fullName := name
+		if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
+			if reference.IsNameOnly(named) {
+				named = reference.WithDefaultTag(named)
+			}
+			ref, ok := named.(reference.NamedTagged)
+			if !ok {
+				return nil, fmt.Errorf("invalid name: %s", named.String())
+			}
+			fullName = ref.String()
+		}
+		p, err = store.GetByName(fullName)
+		if err == nil {
+			return p.FilterByCap(capability)
+		}
+		if _, ok := err.(ErrNotFound); !ok {
+			return nil, err
+		}
+	}
+
+	// Lookup using legacy model.
+	if allowV1PluginsFallback {
+		p, err := plugins.Get(name, capability)
+		if err != nil {
+			return nil, fmt.Errorf("legacy plugin: %v", err)
+		}
+		return p, nil
+	}
+
+	return nil, err
+}
+
+// FindWithCapability returns a list of plugins matching the given capability.
+func FindWithCapability(capability string) ([]CompatPlugin, error) {
+	result := make([]CompatPlugin, 0, 1)
+
+	/* Daemon start always calls plugin.Init thereby initializing a store.
+	 * So store on experimental builds can never be nil, even while
+	 * handling legacy plugins. However, there are legacy plugin unit
+	 * tests where the volume subsystem directly talks with the plugin,
+	 * bypassing the daemon. For such tests, this check is necessary.
+	 */
+	if store != nil {
+		store.RLock()
+		result = store.getAllByCap(capability)
+		store.RUnlock()
+	}
+
+	// Lookup with legacy model
+	if allowV1PluginsFallback {
+		pl, err := plugins.GetAll(capability)
+		if err != nil {
+			return nil, fmt.Errorf("legacy plugin: %v", err)
+		}
+		for _, p := range pl {
+			result = append(result, p)
+		}
+	}
+	return result, nil
+}

+ 261 - 0
plugin/v2/plugin.go

@@ -0,0 +1,261 @@
+// +build experimental
+
+package v2
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/pkg/system"
+	"github.com/docker/docker/restartmanager"
+	"github.com/docker/engine-api/types"
+	"github.com/opencontainers/runtime-spec/specs-go"
+)
+
+const defaultPluginRuntimeDestination = "/run/docker/plugins"
+
+// ErrInadequateCapability indicates that the plugin did not have the requested capability.
+type ErrInadequateCapability string
+
+func (cap ErrInadequateCapability) Error() string {
+	return fmt.Sprintf("plugin does not provide %q capability", cap)
+}
+
+// Plugin represents an individual plugin.
+type Plugin struct {
+	sync.RWMutex
+	PluginObj         types.Plugin                  `json:"plugin"`
+	PClient           *plugins.Client               `json:"-"`
+	RestartManager    restartmanager.RestartManager `json:"-"`
+	RuntimeSourcePath string                        `json:"-"`
+	ExitChan          chan bool                     `json:"-"`
+}
+
+func newPluginObj(name, id, tag string) types.Plugin {
+	return types.Plugin{Name: name, ID: id, Tag: tag}
+}
+
+// NewPlugin creates a plugin.
+func NewPlugin(name, id, runRoot, tag string) *Plugin {
+	return &Plugin{
+		PluginObj:         newPluginObj(name, id, tag),
+		RuntimeSourcePath: filepath.Join(runRoot, id),
+	}
+}
+
+// Client returns the plugin client.
+func (p *Plugin) Client() *plugins.Client {
+	return p.PClient
+}
+
+// IsLegacy returns true for legacy plugins and false otherwise.
+func (p *Plugin) IsLegacy() bool {
+	return false
+}
+
+// Name returns the plugin name.
+func (p *Plugin) Name() string {
+	name := p.PluginObj.Name
+	if len(p.PluginObj.Tag) > 0 {
+		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
+		name += ":" + p.PluginObj.Tag
+	}
+	return name
+}
+
+// FilterByCap query the plugin for a given capability.
+func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
+	capability = strings.ToLower(capability)
+	for _, typ := range p.PluginObj.Manifest.Interface.Types {
+		if typ.Capability == capability && typ.Prefix == "docker" {
+			return p, nil
+		}
+	}
+	return nil, ErrInadequateCapability(capability)
+}
+
+// RemoveFromDisk deletes the plugin's runtime files from disk.
+func (p *Plugin) RemoveFromDisk() error {
+	return os.RemoveAll(p.RuntimeSourcePath)
+}
+
+// InitPlugin populates the plugin object from the plugin manifest file.
+func (p *Plugin) InitPlugin(libRoot string) error {
+	dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json"))
+	if err != nil {
+		return err
+	}
+	err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
+	dt.Close()
+	if err != nil {
+		return err
+	}
+
+	p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
+	for i, mount := range p.PluginObj.Manifest.Mounts {
+		p.PluginObj.Config.Mounts[i] = mount
+	}
+	p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
+	for _, env := range p.PluginObj.Manifest.Env {
+		if env.Value != nil {
+			p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
+		}
+	}
+	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
+
+	f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json"))
+	if err != nil {
+		return err
+	}
+	err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
+	f.Close()
+	return err
+}
+
+// Set is used to pass arguments to the plugin.
+func (p *Plugin) Set(args []string) error {
+	m := make(map[string]string, len(args))
+	for _, arg := range args {
+		i := strings.Index(arg, "=")
+		if i < 0 {
+			return fmt.Errorf("No equal sign '=' found in %s", arg)
+		}
+		m[arg[:i]] = arg[i+1:]
+	}
+	return errors.New("not implemented")
+}
+
+// ComputePrivileges takes the manifest file and computes the list of access necessary
+// for the plugin on the host.
+func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
+	m := p.PluginObj.Manifest
+	var privileges types.PluginPrivileges
+	if m.Network.Type != "null" && m.Network.Type != "bridge" {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "network",
+			Description: "",
+			Value:       []string{m.Network.Type},
+		})
+	}
+	for _, mount := range m.Mounts {
+		if mount.Source != nil {
+			privileges = append(privileges, types.PluginPrivilege{
+				Name:        "mount",
+				Description: "",
+				Value:       []string{*mount.Source},
+			})
+		}
+	}
+	for _, device := range m.Devices {
+		if device.Path != nil {
+			privileges = append(privileges, types.PluginPrivilege{
+				Name:        "device",
+				Description: "",
+				Value:       []string{*device.Path},
+			})
+		}
+	}
+	if len(m.Capabilities) > 0 {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "capabilities",
+			Description: "",
+			Value:       m.Capabilities,
+		})
+	}
+	return privileges
+}
+
+// IsEnabled returns the active state of the plugin.
+func (p *Plugin) IsEnabled() bool {
+	p.RLock()
+	defer p.RUnlock()
+
+	return p.PluginObj.Enabled
+}
+
+// GetID returns the plugin's ID.
+func (p *Plugin) GetID() string {
+	p.RLock()
+	defer p.RUnlock()
+
+	return p.PluginObj.ID
+}
+
+// GetSocket returns the plugin socket.
+func (p *Plugin) GetSocket() string {
+	p.RLock()
+	defer p.RUnlock()
+
+	return p.PluginObj.Manifest.Interface.Socket
+}
+
+// GetTypes returns the interface types of a plugin.
+func (p *Plugin) GetTypes() []types.PluginInterfaceType {
+	p.RLock()
+	defer p.RUnlock()
+
+	return p.PluginObj.Manifest.Interface.Types
+}
+
+// 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")
+	s.Root = specs.Root{
+		Path:     rootfs,
+		Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
+	}
+
+	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
+		Source:      &p.RuntimeSourcePath,
+		Destination: defaultPluginRuntimeDestination,
+		Type:        "bind",
+		Options:     []string{"rbind", "rshared"},
+	})
+	for _, mount := range mounts {
+		m := specs.Mount{
+			Destination: mount.Destination,
+			Type:        mount.Type,
+			Options:     mount.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, string(os.PathSeparator), 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
+				}
+			}
+		}
+		s.Mounts = append(s.Mounts, m)
+	}
+
+	envs := make([]string, 1, len(p.PluginObj.Config.Env)+1)
+	envs[0] = "PATH=" + system.DefaultPathEnv
+	envs = append(envs, p.PluginObj.Config.Env...)
+
+	args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...)
+	cwd := p.PluginObj.Manifest.Workdir
+	if len(cwd) == 0 {
+		cwd = "/"
+	}
+	s.Process = specs.Process{
+		Terminal: false,
+		Args:     args,
+		Cwd:      cwd,
+		Env:      envs,
+	}
+
+	return &s, nil
+}

+ 3 - 3
volume/drivers/extpoint.go

@@ -7,7 +7,7 @@ import (
 	"sync"
 
 	"github.com/docker/docker/pkg/locker"
-	"github.com/docker/docker/plugin"
+	pluginStore "github.com/docker/docker/plugin/store"
 	"github.com/docker/docker/volume"
 )
 
@@ -102,7 +102,7 @@ func lookup(name string) (volume.Driver, error) {
 		return ext, nil
 	}
 
-	p, err := plugin.LookupWithCapability(name, extName)
+	p, err := pluginStore.LookupWithCapability(name, extName)
 	if err != nil {
 		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
 	}
@@ -151,7 +151,7 @@ func GetDriverList() []string {
 
 // GetAllDrivers lists all the registered drivers
 func GetAllDrivers() ([]volume.Driver, error) {
-	plugins, err := plugin.FindWithCapability(extName)
+	plugins, err := pluginStore.FindWithCapability(extName)
 	if err != nil {
 		return nil, fmt.Errorf("error listing plugins: %v", err)
 	}