moby--moby/plugin/store/store.go

258 lines
6.3 KiB
Go
Raw Normal View History

// +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"
)
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)
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)
}
}
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())
}
}
}