blob: b2131c20ef068e8da0bbb735331e1fab050171c4 [file] [log] [blame]
//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type volumeDriver -name VolumeDriver
package drivers // import "github.com/docker/docker/volume/drivers"
import (
"fmt"
"sort"
"sync"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/locker"
getter "github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/volume"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const extName = "VolumeDriver"
// volumeDriver defines the available functions that volume plugins must implement.
// This interface is only defined to generate the proxy objects.
// It's not intended to be public or reused.
// nolint: deadcode
type volumeDriver interface {
// Create a volume with the given name
Create(name string, opts map[string]string) (err error)
// Remove the volume with the given name
Remove(name string) (err error)
// Get the mountpoint of the given volume
Path(name string) (mountpoint string, err error)
// Mount the given volume and return the mountpoint
Mount(name, id string) (mountpoint string, err error)
// Unmount the given volume
Unmount(name, id string) (err error)
// List lists all the volumes known to the driver
List() (volumes []*proxyVolume, err error)
// Get retrieves the volume with the requested name
Get(name string) (volume *proxyVolume, err error)
// Capabilities gets the list of capabilities of the driver
Capabilities() (capabilities volume.Capability, err error)
}
// Store is an in-memory store for volume drivers
type Store struct {
extensions map[string]volume.Driver
mu sync.Mutex
driverLock *locker.Locker
pluginGetter getter.PluginGetter
}
// NewStore creates a new volume driver store
func NewStore(pg getter.PluginGetter) *Store {
return &Store{
extensions: make(map[string]volume.Driver),
driverLock: locker.New(),
pluginGetter: pg,
}
}
type driverNotFoundError string
func (e driverNotFoundError) Error() string {
return "volume driver not found: " + string(e)
}
func (driverNotFoundError) NotFound() {}
// lookup returns the driver associated with the given name. If a
// driver with the given name has not been registered it checks if
// there is a VolumeDriver plugin available with the given name.
func (s *Store) lookup(name string, mode int) (volume.Driver, error) {
if name == "" {
return nil, errdefs.InvalidParameter(errors.New("driver name cannot be empty"))
}
s.driverLock.Lock(name)
defer s.driverLock.Unlock(name)
s.mu.Lock()
ext, ok := s.extensions[name]
s.mu.Unlock()
if ok {
return ext, nil
}
if s.pluginGetter != nil {
p, err := s.pluginGetter.Get(name, extName, mode)
if err != nil {
return nil, errors.Wrap(err, "error looking up volume plugin "+name)
}
d, err := makePluginAdapter(p)
if err != nil {
return nil, errors.Wrap(err, "error making plugin client")
}
if err := validateDriver(d); err != nil {
if mode > 0 {
// Undo any reference count changes from the initial `Get`
if _, err := s.pluginGetter.Get(name, extName, mode*-1); err != nil {
logrus.WithError(err).WithField("action", "validate-driver").WithField("plugin", name).Error("error releasing reference to plugin")
}
}
return nil, err
}
if p.IsV1() {
s.mu.Lock()
s.extensions[name] = d
s.mu.Unlock()
}
return d, nil
}
return nil, driverNotFoundError(name)
}
func validateDriver(vd volume.Driver) error {
scope := vd.Scope()
if scope != volume.LocalScope && scope != volume.GlobalScope {
return fmt.Errorf("Driver %q provided an invalid capability scope: %s", vd.Name(), scope)
}
return nil
}
// Register associates the given driver to the given name, checking if
// the name is already associated
func (s *Store) Register(d volume.Driver, name string) bool {
if name == "" {
return false
}
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.extensions[name]; exists {
return false
}
if err := validateDriver(d); err != nil {
return false
}
s.extensions[name] = d
return true
}
// GetDriver returns a volume driver by its name.
// If the driver is empty, it looks for the local driver.
func (s *Store) GetDriver(name string) (volume.Driver, error) {
return s.lookup(name, getter.Lookup)
}
// CreateDriver returns a volume driver by its name and increments RefCount.
// If the driver is empty, it looks for the local driver.
func (s *Store) CreateDriver(name string) (volume.Driver, error) {
return s.lookup(name, getter.Acquire)
}
// ReleaseDriver returns a volume driver by its name and decrements RefCount..
// If the driver is empty, it looks for the local driver.
func (s *Store) ReleaseDriver(name string) (volume.Driver, error) {
return s.lookup(name, getter.Release)
}
// GetDriverList returns list of volume drivers registered.
// If no driver is registered, empty string list will be returned.
func (s *Store) GetDriverList() []string {
var driverList []string
s.mu.Lock()
defer s.mu.Unlock()
for driverName := range s.extensions {
driverList = append(driverList, driverName)
}
sort.Strings(driverList)
return driverList
}
// GetAllDrivers lists all the registered drivers
func (s *Store) GetAllDrivers() ([]volume.Driver, error) {
var plugins []getter.CompatPlugin
if s.pluginGetter != nil {
var err error
plugins, err = s.pluginGetter.GetAllByCap(extName)
if err != nil {
return nil, fmt.Errorf("error listing plugins: %v", err)
}
}
var ds []volume.Driver
s.mu.Lock()
defer s.mu.Unlock()
for _, d := range s.extensions {
ds = append(ds, d)
}
for _, p := range plugins {
name := p.Name()
if _, ok := s.extensions[name]; ok {
continue
}
ext, err := makePluginAdapter(p)
if err != nil {
return nil, errors.Wrap(err, "error making plugin client")
}
if p.IsV1() {
s.extensions[name] = ext
}
ds = append(ds, ext)
}
return ds, nil
}
func makePluginAdapter(p getter.CompatPlugin) (*volumeDriverAdapter, error) {
if pc, ok := p.(getter.PluginWithV1Client); ok {
return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{pc.Client()}}, nil
}
pa, ok := p.(getter.PluginAddr)
if !ok {
return nil, errdefs.System(errors.Errorf("got unknown plugin instance %T", p))
}
if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 {
return nil, errors.Errorf("plugin protocol not supported: %s", p)
}
addr := pa.Addr()
client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
if err != nil {
return nil, errors.Wrap(err, "error creating plugin client")
}
return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{client}}, nil
}