mirror of https://github.com/docker/docs.git
				
				
				
			Add support for volume scopes
This is similar to network scopes where a volume can either be `local` or `global`. A `global` volume is one that exists across the entire cluster where as a `local` volume exists on a single engine. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
		
							parent
							
								
									79ff6eaf21
								
							
						
					
					
						commit
						2f40b1b281
					
				|  | @ -745,7 +745,9 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	volumedrivers.Register(volumesDriver, volumesDriver.Name()) | 	if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) { | ||||||
|  | 		return nil, fmt.Errorf("local volume driver could not be registered") | ||||||
|  | 	} | ||||||
| 	return store.New(config.Root) | 	return store.New(config.Root) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,11 +27,13 @@ func volumeToAPIType(v volume.Volume) *types.Volume { | ||||||
| 		Name:   v.Name(), | 		Name:   v.Name(), | ||||||
| 		Driver: v.DriverName(), | 		Driver: v.DriverName(), | ||||||
| 	} | 	} | ||||||
| 	if v, ok := v.(interface { | 	if v, ok := v.(volume.LabeledVolume); ok { | ||||||
| 		Labels() map[string]string |  | ||||||
| 	}); ok { |  | ||||||
| 		tv.Labels = v.Labels() | 		tv.Labels = v.Labels() | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if v, ok := v.(volume.ScopedVolume); ok { | ||||||
|  | 		tv.Scope = v.Scope() | ||||||
|  | 	} | ||||||
| 	return tv | 	return tv | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ documentation](plugins.md) for more information. | ||||||
| ### 1.12.0 | ### 1.12.0 | ||||||
| 
 | 
 | ||||||
| - Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#)) | - Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#)) | ||||||
|  | - Add `VolumeDriver.Capabilities` to get capabilities of the volume driver([#22077](https://github.com/docker/docker/pull/22077)) | ||||||
| 
 | 
 | ||||||
| ### 1.10.0 | ### 1.10.0 | ||||||
| 
 | 
 | ||||||
|  | @ -236,3 +237,29 @@ Get the list of volumes registered with the plugin. | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Respond with a string error if an error occurred. | Respond with a string error if an error occurred. | ||||||
|  | 
 | ||||||
|  | ### /VolumeDriver.Capabilities | ||||||
|  | 
 | ||||||
|  | **Request**: | ||||||
|  | ```json | ||||||
|  | {} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Get the list of capabilities the driver supports. | ||||||
|  | The driver is not required to implement this endpoint, however in such cases | ||||||
|  | the default values will be taken. | ||||||
|  | 
 | ||||||
|  | **Response**: | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "Capabilities": { | ||||||
|  |     "Scope": "global" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Supported scopes are `global` and `local`. Any other value in `Scope` will be | ||||||
|  | ignored and assumed to be `local`. Scope allows cluster managers to handle the | ||||||
|  | volume differently, for instance with a scope of `global`, the cluster manager | ||||||
|  | knows it only needs to create the volume once instead of on every engine. More | ||||||
|  | capabilities may be added in the future. | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/docker/pkg/integration/checker" | 	"github.com/docker/docker/pkg/integration/checker" | ||||||
|  | 	"github.com/docker/docker/volume" | ||||||
| 	"github.com/docker/engine-api/types" | 	"github.com/docker/engine-api/types" | ||||||
| 	"github.com/go-check/check" | 	"github.com/go-check/check" | ||||||
| ) | ) | ||||||
|  | @ -35,6 +36,7 @@ type eventCounter struct { | ||||||
| 	paths       int | 	paths       int | ||||||
| 	lists       int | 	lists       int | ||||||
| 	gets        int | 	gets        int | ||||||
|  | 	caps        int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type DockerExternalVolumeSuite struct { | type DockerExternalVolumeSuite struct { | ||||||
|  | @ -225,6 +227,18 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) { | ||||||
| 		send(w, nil) | 		send(w, nil) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		s.ec.caps++ | ||||||
|  | 
 | ||||||
|  | 		_, err := read(r.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			send(w, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		send(w, `{"Capabilities": { "Scope": "global" }}`) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
| 	err := os.MkdirAll("/etc/docker/plugins", 0755) | 	err := os.MkdirAll("/etc/docker/plugins", 0755) | ||||||
| 	c.Assert(err, checker.IsNil) | 	c.Assert(err, checker.IsNil) | ||||||
| 
 | 
 | ||||||
|  | @ -491,3 +505,18 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) | ||||||
| 	c.Assert(err, checker.IsNil, check.Commentf(out)) | 	c.Assert(err, checker.IsNil, check.Commentf(out)) | ||||||
| 	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") | 	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Check that VolumeDriver.Capabilities gets called, and only called once
 | ||||||
|  | func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *check.C) { | ||||||
|  | 	c.Assert(s.d.Start(), checker.IsNil) | ||||||
|  | 	c.Assert(s.ec.caps, checker.Equals, 0) | ||||||
|  | 
 | ||||||
|  | 	for i := 0; i < 3; i++ { | ||||||
|  | 		out, err := s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", "--name", fmt.Sprintf("test%d", i)) | ||||||
|  | 		c.Assert(err, checker.IsNil, check.Commentf(out)) | ||||||
|  | 		c.Assert(s.ec.caps, checker.Equals, 1) | ||||||
|  | 		out, err = s.d.Cmd("volume", "inspect", "--format={{.Scope}}", fmt.Sprintf("test%d", i)) | ||||||
|  | 		c.Assert(err, checker.IsNil) | ||||||
|  | 		c.Assert(strings.TrimSpace(out), checker.Equals, volume.GlobalScope) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -78,7 +78,7 @@ func main() { | ||||||
| 
 | 
 | ||||||
| 	errorOut("parser error", generatedTempl.Execute(&buf, analysis)) | 	errorOut("parser error", generatedTempl.Execute(&buf, analysis)) | ||||||
| 	src, err := format.Source(buf.Bytes()) | 	src, err := format.Source(buf.Bytes()) | ||||||
| 	errorOut("error formating generated source:\n"+buf.String(), err) | 	errorOut("error formatting generated source:\n"+buf.String(), err) | ||||||
| 	errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644)) | 	errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,14 +1,22 @@ | ||||||
| package volumedrivers | package volumedrivers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"errors" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/Sirupsen/logrus" | ||||||
| 	"github.com/docker/docker/volume" | 	"github.com/docker/docker/volume" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	errInvalidScope = errors.New("invalid scope") | ||||||
|  | 	errNoSuchVolume = errors.New("no such volume") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| type volumeDriverAdapter struct { | type volumeDriverAdapter struct { | ||||||
| 	name  string | 	name         string | ||||||
| 	proxy *volumeDriverProxy | 	capabilities *volume.Capability | ||||||
|  | 	proxy        *volumeDriverProxy | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a *volumeDriverAdapter) Name() string { | func (a *volumeDriverAdapter) Name() string { | ||||||
|  | @ -56,7 +64,7 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) { | ||||||
| 
 | 
 | ||||||
| 	// plugin may have returned no volume and no error
 | 	// plugin may have returned no volume and no error
 | ||||||
| 	if v == nil { | 	if v == nil { | ||||||
| 		return nil, fmt.Errorf("no such volume") | 		return nil, errNoSuchVolume | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &volumeAdapter{ | 	return &volumeAdapter{ | ||||||
|  | @ -68,6 +76,38 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) { | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (a *volumeDriverAdapter) Scope() string { | ||||||
|  | 	cap := a.getCapabilities() | ||||||
|  | 	return cap.Scope | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *volumeDriverAdapter) getCapabilities() volume.Capability { | ||||||
|  | 	if a.capabilities != nil { | ||||||
|  | 		return *a.capabilities | ||||||
|  | 	} | ||||||
|  | 	cap, err := a.proxy.Capabilities() | ||||||
|  | 	if err != nil { | ||||||
|  | 		// `GetCapabilities` is a not a required endpoint.
 | ||||||
|  | 		// On error assume it's a local-only driver
 | ||||||
|  | 		logrus.Warnf("Volume driver %s returned an error while trying to query it's capabilities, using default capabilties: %v", a.name, err) | ||||||
|  | 		return volume.Capability{Scope: volume.LocalScope} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// don't spam the warn log below just because the plugin didn't provide a scope
 | ||||||
|  | 	if len(cap.Scope) == 0 { | ||||||
|  | 		cap.Scope = volume.LocalScope | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cap.Scope = strings.ToLower(cap.Scope) | ||||||
|  | 	if cap.Scope != volume.LocalScope && cap.Scope != volume.GlobalScope { | ||||||
|  | 		logrus.Warnf("Volume driver %q returned an invalid scope: %q", a.Name(), cap.Scope) | ||||||
|  | 		cap.Scope = volume.LocalScope | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	a.capabilities = &cap | ||||||
|  | 	return cap | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type volumeAdapter struct { | type volumeAdapter struct { | ||||||
| 	proxy      *volumeDriverProxy | 	proxy      *volumeDriverProxy | ||||||
| 	name       string | 	name       string | ||||||
|  |  | ||||||
|  | @ -42,6 +42,8 @@ type volumeDriver interface { | ||||||
| 	List() (volumes []*proxyVolume, err error) | 	List() (volumes []*proxyVolume, err error) | ||||||
| 	// Get retrieves the volume with the requested name
 | 	// Get retrieves the volume with the requested name
 | ||||||
| 	Get(name string) (volume *proxyVolume, err error) | 	Get(name string) (volume *proxyVolume, err error) | ||||||
|  | 	// Capabilities gets the list of capabilities of the driver
 | ||||||
|  | 	Capabilities() (capabilities volume.Capability, err error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type driverExtpoint struct { | type driverExtpoint struct { | ||||||
|  | @ -64,6 +66,11 @@ func Register(extension volume.Driver, name string) bool { | ||||||
| 	if exists { | 	if exists { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := validateDriver(extension); err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	drivers.extensions[name] = extension | 	drivers.extensions[name] = extension | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  | @ -107,10 +114,22 @@ func Lookup(name string) (volume.Driver, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	d := NewVolumeDriver(name, pl.Client) | 	d := NewVolumeDriver(name, pl.Client) | ||||||
|  | 	if err := validateDriver(d); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	drivers.extensions[name] = d | 	drivers.extensions[name] = d | ||||||
| 	return d, nil | 	return d, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // GetDriver returns a volume driver by its name.
 | // GetDriver returns a volume driver by its name.
 | ||||||
| // If the driver is empty, it looks for the local driver.
 | // If the driver is empty, it looks for the local driver.
 | ||||||
| func GetDriver(name string) (volume.Driver, error) { | func GetDriver(name string) (volume.Driver, error) { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,10 @@ | ||||||
| 
 | 
 | ||||||
| package volumedrivers | package volumedrivers | ||||||
| 
 | 
 | ||||||
| import "errors" | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"github.com/docker/docker/volume" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| type client interface { | type client interface { | ||||||
| 	Call(string, interface{}, interface{}) error | 	Call(string, interface{}, interface{}) error | ||||||
|  | @ -209,3 +212,30 @@ func (pp *volumeDriverProxy) Get(name string) (volume *proxyVolume, err error) { | ||||||
| 
 | 
 | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type volumeDriverProxyCapabilitiesRequest struct { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type volumeDriverProxyCapabilitiesResponse struct { | ||||||
|  | 	Capabilities volume.Capability | ||||||
|  | 	Err          string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pp *volumeDriverProxy) Capabilities() (capabilities volume.Capability, err error) { | ||||||
|  | 	var ( | ||||||
|  | 		req volumeDriverProxyCapabilitiesRequest | ||||||
|  | 		ret volumeDriverProxyCapabilitiesResponse | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	if err = pp.Call("VolumeDriver.Capabilities", req, &ret); err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	capabilities = ret.Capabilities | ||||||
|  | 
 | ||||||
|  | 	if ret.Err != "" { | ||||||
|  | 		err = errors.New(ret.Err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -52,6 +52,11 @@ func TestVolumeRequestError(t *testing.T) { | ||||||
| 		fmt.Fprintln(w, `{"Err": "Cannot get volume"}`) | 		fmt.Fprintln(w, `{"Err": "Cannot get volume"}`) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") | ||||||
|  | 		http.Error(w, "error", 500) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
| 	u, _ := url.Parse(server.URL) | 	u, _ := url.Parse(server.URL) | ||||||
| 	client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true}) | 	client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -119,4 +124,9 @@ func TestVolumeRequestError(t *testing.T) { | ||||||
| 	if !strings.Contains(err.Error(), "Cannot get volume") { | 	if !strings.Contains(err.Error(), "Cannot get volume") { | ||||||
| 		t.Fatalf("Unexpected error: %v\n", err) | 		t.Fatalf("Unexpected error: %v\n", err) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	_, err = driver.Capabilities() | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -248,6 +248,11 @@ func (r *Root) Get(name string) (volume.Volume, error) { | ||||||
| 	return v, nil | 	return v, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Scope returns the local volume scope
 | ||||||
|  | func (r *Root) Scope() string { | ||||||
|  | 	return volume.LocalScope | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *Root) validateName(name string) error { | func (r *Root) validateName(name string) error { | ||||||
| 	if !volumeNameRegex.MatchString(name) { | 	if !volumeNameRegex.MatchString(name) { | ||||||
| 		return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed", name, utils.RestrictedNameChars)} | 		return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed", name, utils.RestrictedNameChars)} | ||||||
|  |  | ||||||
|  | @ -25,15 +25,29 @@ type volumeMetadata struct { | ||||||
| 	Labels map[string]string | 	Labels map[string]string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type volumeWithLabels struct { | type volumeWrapper struct { | ||||||
| 	volume.Volume | 	volume.Volume | ||||||
| 	labels map[string]string | 	labels map[string]string | ||||||
|  | 	scope  string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (v volumeWithLabels) Labels() map[string]string { | func (v volumeWrapper) Labels() map[string]string { | ||||||
| 	return v.labels | 	return v.labels | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (v volumeWrapper) Scope() string { | ||||||
|  | 	return v.scope | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v volumeWrapper) CachedPath() string { | ||||||
|  | 	if vv, ok := v.Volume.(interface { | ||||||
|  | 		CachedPath() string | ||||||
|  | 	}); ok { | ||||||
|  | 		return vv.CachedPath() | ||||||
|  | 	} | ||||||
|  | 	return v.Volume.Path() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // New initializes a VolumeStore to keep
 | // New initializes a VolumeStore to keep
 | ||||||
| // reference counting of volumes in the system.
 | // reference counting of volumes in the system.
 | ||||||
| func New(rootPath string) (*VolumeStore, error) { | func New(rootPath string) (*VolumeStore, error) { | ||||||
|  | @ -166,6 +180,10 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) { | ||||||
| 				chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}} | 				chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}} | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | 			for i, v := range vs { | ||||||
|  | 				vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope()} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			chVols <- vols{vols: vs} | 			chVols <- vols{vols: vs} | ||||||
| 		}(vd) | 		}(vd) | ||||||
| 	} | 	} | ||||||
|  | @ -291,7 +309,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return volumeWithLabels{v, labels}, nil | 	return volumeWrapper{v, labels, vd.Scope()}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetWithRef gets a volume with the given name from the passed in driver and stores the ref
 | // GetWithRef gets a volume with the given name from the passed in driver and stores the ref
 | ||||||
|  | @ -313,10 +331,8 @@ func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, e | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	s.setNamed(v, ref) | 	s.setNamed(v, ref) | ||||||
| 	if labels, ok := s.labels[name]; ok { | 
 | ||||||
| 		return volumeWithLabels{v, labels}, nil | 	return volumeWrapper{v, s.labels[name], vd.Scope()}, nil | ||||||
| 	} |  | ||||||
| 	return v, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Get looks if a volume with the given name exists and returns it if so
 | // Get looks if a volume with the given name exists and returns it if so
 | ||||||
|  | @ -376,7 +392,7 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		return volumeWithLabels{vol, labels}, nil | 		return volumeWrapper{vol, labels, vd.Scope()}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	logrus.Debugf("Probing all drivers for volume with name: %s", name) | 	logrus.Debugf("Probing all drivers for volume with name: %s", name) | ||||||
|  | @ -391,7 +407,7 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return volumeWithLabels{v, labels}, nil | 		return volumeWrapper{v, labels, d.Scope()}, nil | ||||||
| 	} | 	} | ||||||
| 	return nil, errNoSuchVolume | 	return nil, errNoSuchVolume | ||||||
| } | } | ||||||
|  | @ -412,7 +428,7 @@ func (s *VolumeStore) Remove(v volume.Volume) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name) | 	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name) | ||||||
| 	vol := withoutLabels(v) | 	vol := unwrapVolume(v) | ||||||
| 	if err := vd.Remove(vol); err != nil { | 	if err := vd.Remove(vol); err != nil { | ||||||
| 		return &OpErr{Err: err, Name: name, Op: "remove"} | 		return &OpErr{Err: err, Name: name, Op: "remove"} | ||||||
| 	} | 	} | ||||||
|  | @ -465,6 +481,9 @@ func (s *VolumeStore) FilterByDriver(name string) ([]volume.Volume, error) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, &OpErr{Err: err, Name: name, Op: "list"} | 		return nil, &OpErr{Err: err, Name: name, Op: "list"} | ||||||
| 	} | 	} | ||||||
|  | 	for i, v := range ls { | ||||||
|  | 		ls[i] = volumeWrapper{v, s.labels[v.Name()], vd.Scope()} | ||||||
|  | 	} | ||||||
| 	return ls, nil | 	return ls, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -497,8 +516,8 @@ func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume | ||||||
| 	return ls | 	return ls | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func withoutLabels(v volume.Volume) volume.Volume { | func unwrapVolume(v volume.Volume) volume.Volume { | ||||||
| 	if vol, ok := v.(volumeWithLabels); ok { | 	if vol, ok := v.(volumeWrapper); ok { | ||||||
| 		return vol.Volume | 		return vol.Volume | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -109,3 +109,8 @@ func (d *FakeDriver) Get(name string) (volume.Volume, error) { | ||||||
| 	} | 	} | ||||||
| 	return nil, fmt.Errorf("no such volume") | 	return nil, fmt.Errorf("no such volume") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Scope returns the local scope
 | ||||||
|  | func (*FakeDriver) Scope() string { | ||||||
|  | 	return "local" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,14 @@ import ( | ||||||
| 
 | 
 | ||||||
| // DefaultDriverName is the driver name used for the driver
 | // DefaultDriverName is the driver name used for the driver
 | ||||||
| // implemented in the local package.
 | // implemented in the local package.
 | ||||||
| const DefaultDriverName string = "local" | const DefaultDriverName = "local" | ||||||
|  | 
 | ||||||
|  | // Scopes define if a volume has is cluster-wide (global) or local only.
 | ||||||
|  | // Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
 | ||||||
|  | const ( | ||||||
|  | 	LocalScope  = "local" | ||||||
|  | 	GlobalScope = "global" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| // Driver is for creating and removing volumes.
 | // Driver is for creating and removing volumes.
 | ||||||
| type Driver interface { | type Driver interface { | ||||||
|  | @ -27,6 +34,18 @@ type Driver interface { | ||||||
| 	List() ([]Volume, error) | 	List() ([]Volume, error) | ||||||
| 	// Get retrieves the volume with the requested name
 | 	// Get retrieves the volume with the requested name
 | ||||||
| 	Get(name string) (Volume, error) | 	Get(name string) (Volume, error) | ||||||
|  | 	// Scope returns the scope of the driver (e.g. `golbal` or `local`).
 | ||||||
|  | 	// Scope determines how the driver is handled at a cluster level
 | ||||||
|  | 	Scope() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Capability defines a set of capabilities that a driver is able to handle.
 | ||||||
|  | type Capability struct { | ||||||
|  | 	// Scope is the scope of the driver, `global` or `local`
 | ||||||
|  | 	// A `global` scope indicates that the driver manages volumes across the cluster
 | ||||||
|  | 	// A `local` scope indicates that the driver only manages volumes resources local to the host
 | ||||||
|  | 	// Scope is declared by the driver
 | ||||||
|  | 	Scope string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Volume is a place to store data. It is backed by a specific driver, and can be mounted.
 | // Volume is a place to store data. It is backed by a specific driver, and can be mounted.
 | ||||||
|  | @ -46,6 +65,18 @@ type Volume interface { | ||||||
| 	Status() map[string]interface{} | 	Status() map[string]interface{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // LabeledVolume wraps a Volume with user-defined labels
 | ||||||
|  | type LabeledVolume interface { | ||||||
|  | 	Labels() map[string]string | ||||||
|  | 	Volume | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ScopedVolume wraps a volume with a cluster scope (e.g., `local` or `global`)
 | ||||||
|  | type ScopedVolume interface { | ||||||
|  | 	Scope() string | ||||||
|  | 	Volume | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // MountPoint is the intersection point between a volume and a container. It
 | // MountPoint is the intersection point between a volume and a container. It
 | ||||||
| // specifies which volume is to be used and where inside a container it should
 | // specifies which volume is to be used and where inside a container it should
 | ||||||
| // be mounted.
 | // be mounted.
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue