Przeglądaj źródła

volume/local: decouple presence of options from mounting

Signed-off-by: Timo Rothenpieler <timo@rothenpieler.org>
Timo Rothenpieler 4 lat temu
rodzic
commit
6d593fe6cc
3 zmienionych plików z 80 dodań i 12 usunięć
  1. 17 3
      volume/local/local.go
  2. 55 9
      volume/local/local_unix.go
  3. 8 0
      volume/local/local_windows.go

+ 17 - 3
volume/local/local.go

@@ -16,10 +16,12 @@ import (
 	"github.com/docker/docker/daemon/names"
 	"github.com/docker/docker/daemon/names"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
+	"github.com/docker/docker/quota"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/moby/sys/mount"
 	"github.com/moby/sys/mount"
 	"github.com/moby/sys/mountinfo"
 	"github.com/moby/sys/mountinfo"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
 )
 )
 
 
 // VolumeDataPathName is the name of the directory where the volume data is stored.
 // VolumeDataPathName is the name of the directory where the volume data is stored.
@@ -66,6 +68,10 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	if r.quotaCtl, err = quota.NewControl(rootDirectory); err != nil {
+		logrus.Debugf("No quota support for local volumes in %s: %v", rootDirectory, err)
+	}
+
 	for _, d := range dirs {
 	for _, d := range dirs {
 		if !d.IsDir() {
 		if !d.IsDir() {
 			continue
 			continue
@@ -76,6 +82,7 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
 			driverName: r.Name(),
 			driverName: r.Name(),
 			name:       name,
 			name:       name,
 			path:       r.DataPath(name),
 			path:       r.DataPath(name),
+			quotaCtl:   r.quotaCtl,
 		}
 		}
 		r.volumes[name] = v
 		r.volumes[name] = v
 		optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
 		optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
@@ -105,6 +112,7 @@ type Root struct {
 	m            sync.Mutex
 	m            sync.Mutex
 	scope        string
 	scope        string
 	path         string
 	path         string
+	quotaCtl     *quota.Control
 	volumes      map[string]*localVolume
 	volumes      map[string]*localVolume
 	rootIdentity idtools.Identity
 	rootIdentity idtools.Identity
 }
 }
@@ -162,6 +170,7 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error
 		driverName: r.Name(),
 		driverName: r.Name(),
 		name:       name,
 		name:       name,
 		path:       path,
 		path:       path,
+		quotaCtl:   r.quotaCtl,
 	}
 	}
 
 
 	if len(opts) != 0 {
 	if len(opts) != 0 {
@@ -273,6 +282,8 @@ type localVolume struct {
 	opts *optsConfig
 	opts *optsConfig
 	// active refcounts the active mounts
 	// active refcounts the active mounts
 	active activeMount
 	active activeMount
+	// reference to Root instances quotaCtl
+	quotaCtl *quota.Control
 }
 }
 
 
 // Name returns the name of the given Volume.
 // Name returns the name of the given Volume.
@@ -300,7 +311,7 @@ func (v *localVolume) CachedPath() string {
 func (v *localVolume) Mount(id string) (string, error) {
 func (v *localVolume) Mount(id string) (string, error) {
 	v.m.Lock()
 	v.m.Lock()
 	defer v.m.Unlock()
 	defer v.m.Unlock()
-	if v.opts != nil {
+	if v.needsMount() {
 		if !v.active.mounted {
 		if !v.active.mounted {
 			if err := v.mount(); err != nil {
 			if err := v.mount(); err != nil {
 				return "", errdefs.System(err)
 				return "", errdefs.System(err)
@@ -309,6 +320,9 @@ func (v *localVolume) Mount(id string) (string, error) {
 		}
 		}
 		v.active.count++
 		v.active.count++
 	}
 	}
+	if err := v.postMount(); err != nil {
+		return "", err
+	}
 	return v.path, nil
 	return v.path, nil
 }
 }
 
 
@@ -322,7 +336,7 @@ func (v *localVolume) Unmount(id string) error {
 	// Essentially docker doesn't care if this fails, it will send an error, but
 	// Essentially docker doesn't care if this fails, it will send an error, but
 	// ultimately there's nothing that can be done. If we don't decrement the count
 	// ultimately there's nothing that can be done. If we don't decrement the count
 	// this volume can never be removed until a daemon restart occurs.
 	// this volume can never be removed until a daemon restart occurs.
-	if v.opts != nil {
+	if v.needsMount() {
 		v.active.count--
 		v.active.count--
 	}
 	}
 
 
@@ -334,7 +348,7 @@ func (v *localVolume) Unmount(id string) error {
 }
 }
 
 
 func (v *localVolume) unmount() error {
 func (v *localVolume) unmount() error {
-	if v.opts != nil {
+	if v.needsMount() {
 		if err := mount.Unmount(v.path); err != nil {
 		if err := mount.Unmount(v.path); err != nil {
 			if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
 			if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
 				return errdefs.System(err)
 				return errdefs.System(err)

+ 55 - 9
volume/local/local_unix.go

@@ -15,6 +15,8 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
+	"github.com/docker/docker/quota"
+	units "github.com/docker/go-units"
 	"github.com/moby/sys/mount"
 	"github.com/moby/sys/mount"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
@@ -26,10 +28,12 @@ var (
 		"type":   {}, // specify the filesystem type for mount, e.g. nfs
 		"type":   {}, // specify the filesystem type for mount, e.g. nfs
 		"o":      {}, // generic mount options
 		"o":      {}, // generic mount options
 		"device": {}, // device to mount from
 		"device": {}, // device to mount from
+		"size":   {}, // quota size limit
 	}
 	}
-	mandatoryOpts = map[string]struct{}{
-		"device": {},
-		"type":   {},
+	mandatoryOpts = map[string][]string{
+		"device": []string{"type"},
+		"type":   []string{"device"},
+		"o":      []string{"device", "type"},
 	}
 	}
 )
 )
 
 
@@ -37,10 +41,11 @@ type optsConfig struct {
 	MountType   string
 	MountType   string
 	MountOpts   string
 	MountOpts   string
 	MountDevice string
 	MountDevice string
+	Quota       quota.Quota
 }
 }
 
 
 func (o *optsConfig) String() string {
 func (o *optsConfig) String() string {
-	return fmt.Sprintf("type='%s' device='%s' o='%s'", o.MountType, o.MountDevice, o.MountOpts)
+	return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size)
 }
 }
 
 
 // scopedPath verifies that the path where the volume is located
 // scopedPath verifies that the path where the volume is located
@@ -63,15 +68,25 @@ func setOpts(v *localVolume, opts map[string]string) error {
 	if len(opts) == 0 {
 	if len(opts) == 0 {
 		return nil
 		return nil
 	}
 	}
-	if err := validateOpts(opts); err != nil {
+	err := validateOpts(opts)
+	if err != nil {
 		return err
 		return err
 	}
 	}
-
 	v.opts = &optsConfig{
 	v.opts = &optsConfig{
 		MountType:   opts["type"],
 		MountType:   opts["type"],
 		MountOpts:   opts["o"],
 		MountOpts:   opts["o"],
 		MountDevice: opts["device"],
 		MountDevice: opts["device"],
 	}
 	}
+	if val, ok := opts["size"]; ok {
+		size, err := units.RAMInBytes(val)
+		if err != nil {
+			return err
+		}
+		if size > 0 && v.quotaCtl == nil {
+			return errdefs.InvalidParameter(errors.Errorf("quota size requested but no quota support"))
+		}
+		v.opts.Quota.Size = uint64(size)
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -84,14 +99,28 @@ func validateOpts(opts map[string]string) error {
 			return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt))
 			return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt))
 		}
 		}
 	}
 	}
-	for opt := range mandatoryOpts {
-		if _, ok := opts[opt]; !ok {
-			return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", opt))
+	for opt, reqopts := range mandatoryOpts {
+		if _, ok := opts[opt]; ok {
+			for _, reqopt := range reqopts {
+				if _, ok := opts[reqopt]; !ok {
+					return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt))
+				}
+			}
 		}
 		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+func (v *localVolume) needsMount() bool {
+	if v.opts == nil {
+		return false
+	}
+	if v.opts.MountDevice != "" || v.opts.MountType != "" {
+		return true
+	}
+	return false
+}
+
 func (v *localVolume) mount() error {
 func (v *localVolume) mount() error {
 	if v.opts.MountDevice == "" {
 	if v.opts.MountDevice == "" {
 		return fmt.Errorf("missing device in volume options")
 		return fmt.Errorf("missing device in volume options")
@@ -111,6 +140,23 @@ func (v *localVolume) mount() error {
 	return errors.Wrap(err, "failed to mount local volume")
 	return errors.Wrap(err, "failed to mount local volume")
 }
 }
 
 
+func (v *localVolume) postMount() error {
+	if v.opts == nil {
+		return nil
+	}
+	if v.opts.Quota.Size > 0 {
+		if v.quotaCtl != nil {
+			err := v.quotaCtl.SetQuota(v.path, v.opts.Quota)
+			if err != nil {
+				return err
+			}
+		} else {
+			return fmt.Errorf("size quota requested for volume but no quota support")
+		}
+	}
+	return nil
+}
+
 func (v *localVolume) CreatedAt() (time.Time, error) {
 func (v *localVolume) CreatedAt() (time.Time, error) {
 	fileInfo, err := os.Stat(v.path)
 	fileInfo, err := os.Stat(v.path)
 	if err != nil {
 	if err != nil {

+ 8 - 0
volume/local/local_windows.go

@@ -32,10 +32,18 @@ func setOpts(v *localVolume, opts map[string]string) error {
 	return nil
 	return nil
 }
 }
 
 
+func (v *localVolume) needsMount() bool {
+	return false
+}
+
 func (v *localVolume) mount() error {
 func (v *localVolume) mount() error {
 	return nil
 	return nil
 }
 }
 
 
+func (v *localVolume) postMount() error {
+	return nil
+}
+
 func (v *localVolume) CreatedAt() (time.Time, error) {
 func (v *localVolume) CreatedAt() (time.Time, error) {
 	fileInfo, err := os.Stat(v.path)
 	fileInfo, err := os.Stat(v.path)
 	if err != nil {
 	if err != nil {