Merge pull request #26645 from anusha-ragunathan/use-pluginv2-graph

Make graphdrivers work with pluginv2.
This commit is contained in:
Anusha Ragunathan 2016-09-20 10:59:33 -07:00 committed by GitHub
commit ef728a1641
24 changed files with 629 additions and 569 deletions

View file

@ -1,5 +1,3 @@
// +build experimental
package types
import (

8
daemon/daemon.go Executable file → Normal file
View file

@ -47,6 +47,7 @@ import (
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/truncindex"
pluginstore "github.com/docker/docker/plugin/store"
"github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
@ -94,6 +95,7 @@ type Daemon struct {
gidMaps []idtools.IDMap
layerStore layer.Store
imageStore image.Store
pluginStore *pluginstore.Store
nameIndex *registrar.Registrar
linkIndex *linkIndex
containerd libcontainerd.Client
@ -548,6 +550,9 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
if driverName == "" {
driverName = config.GraphDriver
}
d.pluginStore = pluginstore.NewStore(config.Root)
d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{
StorePath: config.Root,
MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
@ -555,6 +560,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
GraphDriverOptions: config.GraphOptions,
UIDMaps: uidMaps,
GIDMaps: gidMaps,
PluginGetter: d.pluginStore,
})
if err != nil {
return nil, err
@ -911,6 +917,8 @@ func (daemon *Daemon) configureVolumes(rootUID, rootGID int) (*store.VolumeStore
return nil, err
}
volumedrivers.RegisterPluginGetter(daemon.pluginStore)
if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
return nil, fmt.Errorf("local volume driver could not be registered")
}

View file

@ -13,7 +13,7 @@ func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.
}
func pluginInit(d *Daemon, cfg *Config, remote libcontainerd.Remote) error {
return plugin.Init(cfg.Root, remote, d.RegistryService, cfg.LiveRestoreEnabled, d.LogPluginEvent)
return plugin.Init(cfg.Root, d.pluginStore, remote, d.RegistryService, cfg.LiveRestoreEnabled, d.LogPluginEvent)
}
func pluginShutdown() {

View file

@ -12,6 +12,7 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/plugin/getter"
)
// FsMagic unsigned id of the filesystem in use.
@ -134,11 +135,11 @@ func Register(name string, initFunc InitFunc) error {
}
// GetDriver initializes and returns the registered driver
func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap, plugingetter getter.PluginGetter) (Driver, error) {
if initFunc, exists := drivers[name]; exists {
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
}
if pluginDriver, err := lookupPlugin(name, home, options); err == nil {
if pluginDriver, err := lookupPlugin(name, home, options, plugingetter); err == nil {
return pluginDriver, nil
}
logrus.Errorf("Failed to GetDriver graph %s %s", name, home)
@ -155,10 +156,10 @@ func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []id
}
// New creates the driver and initializes it at the specified root.
func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap, plugingetter getter.PluginGetter) (Driver, error) {
if name != "" {
logrus.Debugf("[graphdriver] trying provided driver: %s", name) // so the logs show specified driver
return GetDriver(name, root, options, uidMaps, gidMaps)
return GetDriver(name, root, options, uidMaps, gidMaps, plugingetter)
}
// Guess for prior driver

View file

@ -41,7 +41,7 @@ func newDriver(t testing.TB, name string, options []string) *Driver {
t.Fatal(err)
}
d, err := graphdriver.GetDriver(name, root, options, nil, nil)
d, err := graphdriver.GetDriver(name, root, options, nil, nil, nil)
if err != nil {
t.Logf("graphdriver: %v\n", err)
if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites || err == graphdriver.ErrIncompatibleFS {

View file

@ -6,7 +6,7 @@ import (
"fmt"
"io"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/plugin/getter"
)
type pluginClient interface {
@ -18,8 +18,8 @@ type pluginClient interface {
SendFile(string, io.Reader, interface{}) error
}
func lookupPlugin(name, home string, opts []string) (Driver, error) {
pl, err := plugins.Get(name, "GraphDriver")
func lookupPlugin(name, home string, opts []string, pluginGetter getter.PluginGetter) (Driver, error) {
pl, err := pluginGetter.Get(name, "GraphDriver", getter.LOOKUP)
if err != nil {
return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
}

View file

@ -2,6 +2,8 @@
package graphdriver
func lookupPlugin(name, home string, opts []string) (Driver, error) {
import "github.com/docker/docker/plugin/getter"
func lookupPlugin(name, home string, opts []string, plugingetter getter.PluginGetter) (Driver, error) {
return nil, ErrNotSupported
}

View file

@ -14,6 +14,7 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/plugin/getter"
"github.com/vbatts/tar-split/tar/asm"
"github.com/vbatts/tar-split/tar/storage"
)
@ -44,6 +45,7 @@ type StoreOptions struct {
GraphDriverOptions []string
UIDMaps []idtools.IDMap
GIDMaps []idtools.IDMap
PluginGetter getter.PluginGetter
}
// NewStoreFromOptions creates a new Store instance
@ -53,7 +55,8 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
options.GraphDriver,
options.GraphDriverOptions,
options.UIDMaps,
options.GIDMaps)
options.GIDMaps,
options.PluginGetter)
if err != nil {
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
}

View file

@ -39,7 +39,7 @@ func newVFSGraphDriver(td string) (graphdriver.Driver, error) {
},
}
return graphdriver.GetDriver("vfs", td, nil, uidMap, gidMap)
return graphdriver.GetDriver("vfs", td, nil, uidMap, gidMap, nil)
}
func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) {

View file

@ -83,8 +83,8 @@ func (p *Plugin) Client() *Client {
return p.client
}
// IsLegacy returns true for legacy plugins and false otherwise.
func (p *Plugin) IsLegacy() bool {
// IsV1 returns true for V1 plugins and false otherwise.
func (p *Plugin) IsV1() bool {
return true
}

View file

@ -17,7 +17,6 @@ import (
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/plugin/distribution"
"github.com/docker/docker/plugin/v2"
"github.com/docker/docker/reference"
)
// Disable deactivates a plugin, which implies that they cannot be used by containers.
@ -57,9 +56,9 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
// Pull pulls a plugin and computes the privileges required to install it.
func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
ref, err := reference.ParseNamed(name)
ref, err := distribution.GetRef(name)
if err != nil {
logrus.Debugf("error in reference.ParseNamed: %v", err)
logrus.Debugf("error in distribution.GetRef: %v", err)
return nil, err
}
name = ref.String()
@ -76,7 +75,7 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
return nil, err
}
pd, err := distribution.Pull(name, pm.registryService, metaHeader, authConfig)
pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig)
if err != nil {
logrus.Debugf("error in distribution.Pull(): %v", err)
return nil, err
@ -87,10 +86,7 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
return nil, err
}
var tag string
if ref, ok := ref.(reference.NamedTagged); ok {
tag = ref.Tag()
}
tag := distribution.GetTag(ref)
p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, tag)
if err := p.InitPlugin(pm.libRoot); err != nil {
return nil, err

View file

@ -62,14 +62,26 @@ func (pd *pullData) Layer() (io.ReadCloser, error) {
return rsc, nil
}
// Pull downloads the plugin from Store
func Pull(name string, rs registry.Service, metaheader http.Header, authConfig *types.AuthConfig) (PullData, error) {
// GetRef returns the distribution reference for a given name.
func GetRef(name string) (reference.Named, error) {
ref, err := reference.ParseNamed(name)
if err != nil {
logrus.Debugf("pull.go: error in ParseNamed: %v", err)
return nil, err
}
return ref, nil
}
// GetTag returns the tag associated with the given reference name.
func GetTag(ref reference.Named) string {
tag := DefaultTag
if ref, ok := ref.(reference.NamedTagged); ok {
tag = ref.Tag()
}
return tag
}
// Pull downloads the plugin from Store
func Pull(ref reference.Named, rs registry.Service, metaheader http.Header, authConfig *types.AuthConfig) (PullData, error) {
repoInfo, err := rs.ResolveRepository(ref)
if err != nil {
logrus.Debugf("pull.go: error in ResolveRepository: %v", err)

View file

@ -0,0 +1,25 @@
package getter
import "github.com/docker/docker/pkg/plugins"
const (
// LOOKUP doesn't update RefCount
LOOKUP = 0
// CREATE increments RefCount
CREATE = 1
// REMOVE decrements RefCount
REMOVE = -1
)
// CompatPlugin is a abstraction to handle both v2(new) and v1(legacy) plugins.
type CompatPlugin interface {
Client() *plugins.Client
Name() string
IsV1() bool
}
// PluginGetter is the interface implemented by Store
type PluginGetter interface {
Get(name, capability string, mode int) (CompatPlugin, error)
GetAllByCap(capability string) ([]CompatPlugin, error)
}

View file

@ -35,7 +35,7 @@ type Manager struct {
sync.RWMutex
libRoot string
runRoot string
pluginStore *store.PluginStore
pluginStore *store.Store
containerdClient libcontainerd.Client
registryService registry.Service
liveRestore bool
@ -50,7 +50,7 @@ func GetManager() *Manager {
// Init (was NewManager) instantiates the singleton Manager.
// TODO: revert this to NewManager once we get rid of all the singletons.
func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) {
func Init(root string, ps *store.Store, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) {
if manager != nil {
return nil
}
@ -59,7 +59,7 @@ func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRes
manager = &Manager{
libRoot: root,
runRoot: "/run/docker",
pluginStore: store.NewPluginStore(root),
pluginStore: ps,
registryService: rs,
liveRestore: liveRestore,
pluginEventLogger: evL,

31
plugin/store/defs.go Normal file
View file

@ -0,0 +1,31 @@
package store
import (
"path/filepath"
"sync"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/plugin/v2"
)
// Store manages the plugin inventory in memory and on-disk
type Store struct {
sync.RWMutex
plugins map[string]*v2.Plugin
/* handlers are necessary for transition path of legacy plugins
* to the new model. Legacy plugins use Handle() for registering an
* activation callback.*/
handlers map[string]func(string, *plugins.Client)
nameToID map[string]string
plugindb string
}
// NewStore creates a Store.
func NewStore(libRoot string) *Store {
return &Store{
plugins: make(map[string]*v2.Plugin),
handlers: make(map[string]func(string, *plugins.Client)),
nameToID: make(map[string]string),
plugindb: filepath.Join(libRoot, "plugins", "plugins.json"),
}
}

View file

@ -1,19 +0,0 @@
package store
import "github.com/docker/docker/pkg/plugins"
const (
// LOOKUP doesn't update RefCount
LOOKUP = 0
// CREATE increments RefCount
CREATE = 1
// REMOVE decrements RefCount
REMOVE = -1
)
// CompatPlugin is an abstraction to handle both new and legacy (v1) plugins.
type CompatPlugin interface {
Client() *plugins.Client
Name() string
IsLegacy() bool
}

View file

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

View file

@ -1,257 +1,26 @@
// +build experimental
// +build !experimental
package store
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"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"
"github.com/docker/docker/plugin/getter"
)
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
/* handlers are necessary for transition path of legacy plugins
* to the new model. Legacy plugins use Handle() for registering an
* activation callback.*/
handlers map[string]func(string, *plugins.Client)
nameToID map[string]string
plugindb string
}
// NewPluginStore creates a PluginStore.
func NewPluginStore(libRoot string) *PluginStore {
store = &PluginStore{
plugins: make(map[string]*v2.Plugin),
handlers: make(map[string]func(string, *plugins.Client)),
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)
// GetAllByCap returns a list of plugins matching the given capability.
func (ps Store) GetAllByCap(capability string) ([]getter.CompatPlugin, error) {
pl, err := plugins.GetAll(capability)
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, mode int) (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 {
p.Lock()
p.RefCount += mode
p.Unlock()
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)
}
result := make([]getter.CompatPlugin, len(pl))
for i, p := range pl {
result[i] = p
}
return result, nil
}
// Handle sets a callback for a given capability. It is only used by network
// and ipam drivers during plugin registration. The callback registers the
// driver with the subsystem (network, ipam).
func Handle(capability string, callback func(string, *plugins.Client)) {
pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
// Register callback with new plugin model.
store.handlers[pluginType] = callback
// Register callback with legacy plugin model.
if allowV1PluginsFallback {
plugins.Handle(capability, callback)
}
}
// CallHandler calls the registered callback. It is invoked during plugin enable.
func (ps *PluginStore) CallHandler(p *v2.Plugin) {
for _, typ := range p.GetTypes() {
if handler := ps.handlers[typ.String()]; handler != nil {
handler(p.Name(), p.Client())
}
}
// Get returns a plugin matching the given name and capability.
func (ps Store) Get(name, capability string, _ int) (getter.CompatPlugin, error) {
return plugins.Get(name, capability)
}

View file

@ -0,0 +1,234 @@
// +build experimental
package store
import (
"encoding/json"
"fmt"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/plugin/getter"
"github.com/docker/docker/plugin/v2"
"github.com/docker/docker/reference"
)
/* 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.
*/
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)) }
// GetByName retreives a plugin by name.
func (ps *Store) 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 *Store) 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 *Store) GetAll() map[string]*v2.Plugin {
ps.RLock()
defer ps.RUnlock()
return ps.plugins
}
// SetAll initialized plugins during daemon restore.
func (ps *Store) SetAll(plugins map[string]*v2.Plugin) {
ps.Lock()
defer ps.Unlock()
ps.plugins = plugins
}
func (ps *Store) 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 *Store) getAllByCap(capability string) []getter.CompatPlugin {
ps.RLock()
defer ps.RUnlock()
result := make([]getter.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 *Store) 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 *Store) 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 *Store) 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 *Store) 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
}
// Get returns a plugin matching the given name and capability.
func (ps *Store) Get(name, capability string, mode int) (getter.CompatPlugin, error) {
var (
p *v2.Plugin
err error
)
// Lookup using new model.
if ps != 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 = ps.GetByName(fullName)
if err == nil {
p.Lock()
p.RefCount += mode
p.Unlock()
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
}
// GetAllByCap returns a list of plugins matching the given capability.
func (ps *Store) GetAllByCap(capability string) ([]getter.CompatPlugin, error) {
result := make([]getter.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 ps != nil {
ps.RLock()
result = ps.getAllByCap(capability)
ps.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
}
// Handle sets a callback for a given capability. It is only used by network
// and ipam drivers during plugin registration. The callback registers the
// driver with the subsystem (network, ipam).
func (ps Store) Handle(capability string, callback func(string, *plugins.Client)) {
pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
store := &ps
// Register callback with new plugin model.
store.Lock()
store.handlers[pluginType] = callback
store.Unlock()
// Register callback with legacy plugin model.
if allowV1PluginsFallback {
plugins.Handle(capability, callback)
}
}
// CallHandler calls the registered callback. It is invoked during plugin enable.
func (ps *Store) CallHandler(p *v2.Plugin) {
for _, typ := range p.GetTypes() {
if handler := ps.handlers[typ.String()]; handler != nil {
handler(p.Name(), p.Client())
}
}
}

View file

@ -1,32 +1,13 @@
// +build experimental
package v2
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/restartmanager"
"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
@ -37,226 +18,3 @@ type Plugin struct {
ExitChan chan bool `json:"-"`
RefCount int `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
}

View file

@ -0,0 +1,249 @@
// +build experimental
package v2
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/system"
"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)
}
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
}
// IsV1 returns true for V1 plugins and false otherwise.
func (p *Plugin) IsV1() 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, 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
}

View file

@ -7,14 +7,17 @@ import (
"sync"
"github.com/docker/docker/pkg/locker"
pluginStore "github.com/docker/docker/plugin/store"
"github.com/docker/docker/plugin/getter"
"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), driverLock: &locker.Locker{}}
var drivers = &driverExtpoint{
extensions: make(map[string]volume.Driver),
driverLock: &locker.Locker{},
}
const extName = "VolumeDriver"
@ -49,7 +52,13 @@ type volumeDriver interface {
type driverExtpoint struct {
extensions map[string]volume.Driver
sync.Mutex
driverLock *locker.Locker
driverLock *locker.Locker
plugingetter getter.PluginGetter
}
// RegisterPluginGetter sets the plugingetter
func RegisterPluginGetter(plugingetter getter.PluginGetter) {
drivers.plugingetter = plugingetter
}
// Register associates the given driver to the given name, checking if
@ -72,6 +81,7 @@ func Register(extension volume.Driver, name string) bool {
}
drivers.extensions[name] = extension
return true
}
@ -102,7 +112,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
return ext, nil
}
p, err := pluginStore.LookupWithCapability(name, extName, mode)
p, err := drivers.plugingetter.Get(name, extName, mode)
if err != nil {
return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
}
@ -112,7 +122,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
return nil, err
}
if p.IsLegacy() {
if p.IsV1() {
drivers.Lock()
drivers.extensions[name] = d
drivers.Unlock()
@ -134,7 +144,7 @@ func GetDriver(name string) (volume.Driver, error) {
if name == "" {
name = volume.DefaultDriverName
}
return lookup(name, pluginStore.LOOKUP)
return lookup(name, getter.LOOKUP)
}
// CreateDriver returns a volume driver by its name and increments RefCount.
@ -143,7 +153,7 @@ func CreateDriver(name string) (volume.Driver, error) {
if name == "" {
name = volume.DefaultDriverName
}
return lookup(name, pluginStore.CREATE)
return lookup(name, getter.CREATE)
}
// RemoveDriver returns a volume driver by its name and decrements RefCount..
@ -152,7 +162,7 @@ func RemoveDriver(name string) (volume.Driver, error) {
if name == "" {
name = volume.DefaultDriverName
}
return lookup(name, pluginStore.REMOVE)
return lookup(name, getter.REMOVE)
}
// GetDriverList returns list of volume drivers registered.
@ -169,7 +179,7 @@ func GetDriverList() []string {
// GetAllDrivers lists all the registered drivers
func GetAllDrivers() ([]volume.Driver, error) {
plugins, err := pluginStore.FindWithCapability(extName)
plugins, err := drivers.plugingetter.GetAllByCap(extName)
if err != nil {
return nil, fmt.Errorf("error listing plugins: %v", err)
}
@ -190,7 +200,7 @@ func GetAllDrivers() ([]volume.Driver, error) {
}
ext = NewVolumeDriver(name, p.Client())
if p.IsLegacy() {
if p.IsV1() {
drivers.extensions[name] = ext
}
ds = append(ds, ext)

View file

@ -3,16 +3,20 @@ package volumedrivers
import (
"testing"
pluginstore "github.com/docker/docker/plugin/store"
volumetestutils "github.com/docker/docker/volume/testutils"
)
func TestGetDriver(t *testing.T) {
pluginStore := pluginstore.NewStore("/var/lib/docker")
RegisterPluginGetter(pluginStore)
_, err := GetDriver("missing")
if err == nil {
t.Fatal("Expected error, was nil")
}
Register(volumetestutils.NewFakeDriver("fake"), "fake")
d, err := GetDriver("fake")
if err != nil {
t.Fatal(err)

View file

@ -5,11 +5,15 @@ import (
"strings"
"testing"
pluginstore "github.com/docker/docker/plugin/store"
"github.com/docker/docker/volume/drivers"
volumetestutils "github.com/docker/docker/volume/testutils"
)
func TestCreate(t *testing.T) {
pluginStore := pluginstore.NewStore("/var/lib/docker")
volumedrivers.RegisterPluginGetter(pluginStore)
volumedrivers.Register(volumetestutils.NewFakeDriver("fake"), "fake")
defer volumedrivers.Unregister("fake")
s, err := New("")