f35a0f3210
This patches avoids registering (and calling) the same plugin more than once. Using an helper map which indexes by name guarantees this and keeps the order. The behavior of overriding the same name in a flag is consistent with, for instance, the `docker run -v /test -v /test` flag which register the volume just once. Adds integration tests. Without this patch: ``` Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.080901676+01:00" level=debug msg="Calling GET /v1.22/info" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.081213202+01:00" level=debug msg="AuthZ request using plugin docker-novolume-plugin" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.081268132+01:00" level=debug msg="docker-novolume-plugin implements: authz" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.081699788+01:00" level=debug msg="AuthZ request using plugin docker-novolume-plugin" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.081762507+01:00" level=debug msg="docker-novolume-plugin implements: authz" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.082092480+01:00" level=debug msg="GET /v1.22/info" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.628691038+01:00" level=debug msg="AuthZ response using plugin docker-novolume-plugin" Dec 20 19:34:52 localhost.localdomain docker[9988]: time="2015-12-20T19:34:52.629880930+01:00" level=debug msg="AuthZ response using plugin docker-novolume-plugin" ``` With this patch: ``` Dec 20 19:37:32 localhost.localdomain docker[16620]: time="2015-12-20T19:37:32.376523958+01:00" level=debug msg="Calling GET /v1.22/info" Dec 20 19:37:32 localhost.localdomain docker[16620]: time="2015-12-20T19:37:32.376715483+01:00" level=debug msg="AuthZ request using plugin docker-novolume-plugin" Dec 20 19:37:32 localhost.localdomain docker[16620]: time="2015-12-20T19:37:32.376771230+01:00" level=debug msg="docker-novolume-plugin implements: authz" Dec 20 19:37:32 localhost.localdomain docker[16620]: time="2015-12-20T19:37:32.377698897+01:00" level=debug msg="GET /v1.22/info" Dec 20 19:37:32 localhost.localdomain docker[16620]: time="2015-12-20T19:37:32.951016441+01:00" level=debug msg="AuthZ response using plugin docker-novolume-plugin" ``` Also removes a somehow duplicate debug statement (leaving only the second one as it's a loop of plugin's manifest): ``` Dec 20 19:52:30 localhost.localdomain docker[25767]: time="2015-12-20T19:52:30.544090518+01:00" level=debug msg="docker-novolume-plugin's manifest: &{[authz]}" Dec 20 19:52:30 localhost.localdomain docker[25767]: time="2015-12-20T19:52:30.544170677+01:00" level=debug msg="docker-novolume-plugin implements: authz" ``` Signed-off-by: Antonio Murdaca <runcom@redhat.com>
181 lines
4.2 KiB
Go
181 lines
4.2 KiB
Go
// Package plugins provides structures and helper functions to manage Docker
|
|
// plugins.
|
|
//
|
|
// Docker discovers plugins by looking for them in the plugin directory whenever
|
|
// a user or container tries to use one by name. UNIX domain socket files must
|
|
// be located under /run/docker/plugins, whereas spec files can be located
|
|
// either under /etc/docker/plugins or /usr/lib/docker/plugins. This is handled
|
|
// by the Registry interface, which lets you list all plugins or get a plugin by
|
|
// its name if it exists.
|
|
//
|
|
// The plugins need to implement an HTTP server and bind this to the UNIX socket
|
|
// or the address specified in the spec files.
|
|
// A handshake is send at /Plugin.Activate, and plugins are expected to return
|
|
// a Manifest with a list of of Docker subsystems which this plugin implements.
|
|
//
|
|
// In order to use a plugins, you can use the ``Get`` with the name of the
|
|
// plugin and the subsystem it implements.
|
|
//
|
|
// plugin, err := plugins.Get("example", "VolumeDriver")
|
|
// if err != nil {
|
|
// return fmt.Errorf("Error looking up volume plugin example: %v", err)
|
|
// }
|
|
package plugins
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/pkg/tlsconfig"
|
|
)
|
|
|
|
var (
|
|
// ErrNotImplements is returned if the plugin does not implement the requested driver.
|
|
ErrNotImplements = errors.New("Plugin does not implement the requested driver")
|
|
)
|
|
|
|
type plugins struct {
|
|
sync.Mutex
|
|
plugins map[string]*Plugin
|
|
}
|
|
|
|
var (
|
|
storage = plugins{plugins: make(map[string]*Plugin)}
|
|
extpointHandlers = make(map[string]func(string, *Client))
|
|
)
|
|
|
|
// Manifest lists what a plugin implements.
|
|
type Manifest struct {
|
|
// List of subsystem the plugin implements.
|
|
Implements []string
|
|
}
|
|
|
|
// Plugin is the definition of a docker plugin.
|
|
type Plugin struct {
|
|
// Name of the plugin
|
|
Name string `json:"-"`
|
|
// Address of the plugin
|
|
Addr string
|
|
// TLS configuration of the plugin
|
|
TLSConfig tlsconfig.Options
|
|
// Client attached to the plugin
|
|
Client *Client `json:"-"`
|
|
// Manifest of the plugin (see above)
|
|
Manifest *Manifest `json:"-"`
|
|
|
|
activatErr error
|
|
activateOnce sync.Once
|
|
}
|
|
|
|
func newLocalPlugin(name, addr string) *Plugin {
|
|
return &Plugin{
|
|
Name: name,
|
|
Addr: addr,
|
|
TLSConfig: tlsconfig.Options{InsecureSkipVerify: true},
|
|
}
|
|
}
|
|
|
|
func (p *Plugin) activate() error {
|
|
p.activateOnce.Do(func() {
|
|
p.activatErr = p.activateWithLock()
|
|
})
|
|
return p.activatErr
|
|
}
|
|
|
|
func (p *Plugin) activateWithLock() error {
|
|
c, err := NewClient(p.Addr, p.TLSConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.Client = c
|
|
|
|
m := new(Manifest)
|
|
if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.Manifest = m
|
|
|
|
for _, iface := range m.Implements {
|
|
handler, handled := extpointHandlers[iface]
|
|
if !handled {
|
|
continue
|
|
}
|
|
handler(p.Name, p.Client)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func load(name string) (*Plugin, error) {
|
|
return loadWithRetry(name, true)
|
|
}
|
|
|
|
func loadWithRetry(name string, retry bool) (*Plugin, error) {
|
|
registry := newLocalRegistry()
|
|
start := time.Now()
|
|
|
|
var retries int
|
|
for {
|
|
pl, err := registry.Plugin(name)
|
|
if err != nil {
|
|
if !retry {
|
|
return nil, err
|
|
}
|
|
|
|
timeOff := backoff(retries)
|
|
if abort(start, timeOff) {
|
|
return nil, err
|
|
}
|
|
retries++
|
|
logrus.Warnf("Unable to locate plugin: %s, retrying in %v", name, timeOff)
|
|
time.Sleep(timeOff)
|
|
continue
|
|
}
|
|
|
|
storage.Lock()
|
|
storage.plugins[name] = pl
|
|
storage.Unlock()
|
|
|
|
err = pl.activate()
|
|
|
|
if err != nil {
|
|
storage.Lock()
|
|
delete(storage.plugins, name)
|
|
storage.Unlock()
|
|
}
|
|
|
|
return pl, err
|
|
}
|
|
}
|
|
|
|
func get(name string) (*Plugin, error) {
|
|
storage.Lock()
|
|
pl, ok := storage.plugins[name]
|
|
storage.Unlock()
|
|
if ok {
|
|
return pl, pl.activate()
|
|
}
|
|
return load(name)
|
|
}
|
|
|
|
// Get returns the plugin given the specified name and requested implementation.
|
|
func Get(name, imp string) (*Plugin, error) {
|
|
pl, err := get(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, driver := range pl.Manifest.Implements {
|
|
logrus.Debugf("%s implements: %s", name, driver)
|
|
if driver == imp {
|
|
return pl, nil
|
|
}
|
|
}
|
|
return nil, ErrNotImplements
|
|
}
|
|
|
|
// Handle adds the specified function to the extpointHandlers.
|
|
func Handle(iface string, fn func(string, *Client)) {
|
|
extpointHandlers[iface] = fn
|
|
}
|