Signed-off-by: Timo Rothenpieler <timo@rothenpieler.org>
| ... | ... |
@@ -16,10 +16,12 @@ import ( |
| 16 | 16 |
"github.com/docker/docker/daemon/names" |
| 17 | 17 |
"github.com/docker/docker/errdefs" |
| 18 | 18 |
"github.com/docker/docker/pkg/idtools" |
| 19 |
+ "github.com/docker/docker/quota" |
|
| 19 | 20 |
"github.com/docker/docker/volume" |
| 20 | 21 |
"github.com/moby/sys/mount" |
| 21 | 22 |
"github.com/moby/sys/mountinfo" |
| 22 | 23 |
"github.com/pkg/errors" |
| 24 |
+ "github.com/sirupsen/logrus" |
|
| 23 | 25 |
) |
| 24 | 26 |
|
| 25 | 27 |
// 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) {
|
| 66 | 66 |
return nil, err |
| 67 | 67 |
} |
| 68 | 68 |
|
| 69 |
+ if r.quotaCtl, err = quota.NewControl(rootDirectory); err != nil {
|
|
| 70 |
+ logrus.Debugf("No quota support for local volumes in %s: %v", rootDirectory, err)
|
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 69 | 73 |
for _, d := range dirs {
|
| 70 | 74 |
if !d.IsDir() {
|
| 71 | 75 |
continue |
| ... | ... |
@@ -76,6 +82,7 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
|
| 76 | 76 |
driverName: r.Name(), |
| 77 | 77 |
name: name, |
| 78 | 78 |
path: r.DataPath(name), |
| 79 |
+ quotaCtl: r.quotaCtl, |
|
| 79 | 80 |
} |
| 80 | 81 |
r.volumes[name] = v |
| 81 | 82 |
optsFilePath := filepath.Join(rootDirectory, name, "opts.json") |
| ... | ... |
@@ -105,6 +112,7 @@ type Root struct {
|
| 105 | 105 |
m sync.Mutex |
| 106 | 106 |
scope string |
| 107 | 107 |
path string |
| 108 |
+ quotaCtl *quota.Control |
|
| 108 | 109 |
volumes map[string]*localVolume |
| 109 | 110 |
rootIdentity idtools.Identity |
| 110 | 111 |
} |
| ... | ... |
@@ -162,6 +170,7 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error |
| 162 | 162 |
driverName: r.Name(), |
| 163 | 163 |
name: name, |
| 164 | 164 |
path: path, |
| 165 |
+ quotaCtl: r.quotaCtl, |
|
| 165 | 166 |
} |
| 166 | 167 |
|
| 167 | 168 |
if len(opts) != 0 {
|
| ... | ... |
@@ -273,6 +282,8 @@ type localVolume struct {
|
| 273 | 273 |
opts *optsConfig |
| 274 | 274 |
// active refcounts the active mounts |
| 275 | 275 |
active activeMount |
| 276 |
+ // reference to Root instances quotaCtl |
|
| 277 |
+ quotaCtl *quota.Control |
|
| 276 | 278 |
} |
| 277 | 279 |
|
| 278 | 280 |
// Name returns the name of the given Volume. |
| ... | ... |
@@ -300,7 +311,7 @@ func (v *localVolume) CachedPath() string {
|
| 300 | 300 |
func (v *localVolume) Mount(id string) (string, error) {
|
| 301 | 301 |
v.m.Lock() |
| 302 | 302 |
defer v.m.Unlock() |
| 303 |
- if v.opts != nil {
|
|
| 303 |
+ if v.needsMount() {
|
|
| 304 | 304 |
if !v.active.mounted {
|
| 305 | 305 |
if err := v.mount(); err != nil {
|
| 306 | 306 |
return "", errdefs.System(err) |
| ... | ... |
@@ -309,6 +320,9 @@ func (v *localVolume) Mount(id string) (string, error) {
|
| 309 | 309 |
} |
| 310 | 310 |
v.active.count++ |
| 311 | 311 |
} |
| 312 |
+ if err := v.postMount(); err != nil {
|
|
| 313 |
+ return "", err |
|
| 314 |
+ } |
|
| 312 | 315 |
return v.path, nil |
| 313 | 316 |
} |
| 314 | 317 |
|
| ... | ... |
@@ -322,7 +336,7 @@ func (v *localVolume) Unmount(id string) error {
|
| 322 | 322 |
// Essentially docker doesn't care if this fails, it will send an error, but |
| 323 | 323 |
// ultimately there's nothing that can be done. If we don't decrement the count |
| 324 | 324 |
// this volume can never be removed until a daemon restart occurs. |
| 325 |
- if v.opts != nil {
|
|
| 325 |
+ if v.needsMount() {
|
|
| 326 | 326 |
v.active.count-- |
| 327 | 327 |
} |
| 328 | 328 |
|
| ... | ... |
@@ -334,7 +348,7 @@ func (v *localVolume) Unmount(id string) error {
|
| 334 | 334 |
} |
| 335 | 335 |
|
| 336 | 336 |
func (v *localVolume) unmount() error {
|
| 337 |
- if v.opts != nil {
|
|
| 337 |
+ if v.needsMount() {
|
|
| 338 | 338 |
if err := mount.Unmount(v.path); err != nil {
|
| 339 | 339 |
if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
|
| 340 | 340 |
return errdefs.System(err) |
| ... | ... |
@@ -15,6 +15,8 @@ import ( |
| 15 | 15 |
"time" |
| 16 | 16 |
|
| 17 | 17 |
"github.com/docker/docker/errdefs" |
| 18 |
+ "github.com/docker/docker/quota" |
|
| 19 |
+ units "github.com/docker/go-units" |
|
| 18 | 20 |
"github.com/moby/sys/mount" |
| 19 | 21 |
"github.com/pkg/errors" |
| 20 | 22 |
) |
| ... | ... |
@@ -26,10 +28,12 @@ var ( |
| 26 | 26 |
"type": {}, // specify the filesystem type for mount, e.g. nfs
|
| 27 | 27 |
"o": {}, // generic mount options
|
| 28 | 28 |
"device": {}, // device to mount from
|
| 29 |
+ "size": {}, // quota size limit
|
|
| 29 | 30 |
} |
| 30 |
- mandatoryOpts = map[string]struct{}{
|
|
| 31 |
- "device": {},
|
|
| 32 |
- "type": {},
|
|
| 31 |
+ mandatoryOpts = map[string][]string{
|
|
| 32 |
+ "device": []string{"type"},
|
|
| 33 |
+ "type": []string{"device"},
|
|
| 34 |
+ "o": []string{"device", "type"},
|
|
| 33 | 35 |
} |
| 34 | 36 |
) |
| 35 | 37 |
|
| ... | ... |
@@ -37,10 +41,11 @@ type optsConfig struct {
|
| 37 | 37 |
MountType string |
| 38 | 38 |
MountOpts string |
| 39 | 39 |
MountDevice string |
| 40 |
+ Quota quota.Quota |
|
| 40 | 41 |
} |
| 41 | 42 |
|
| 42 | 43 |
func (o *optsConfig) String() string {
|
| 43 |
- return fmt.Sprintf("type='%s' device='%s' o='%s'", o.MountType, o.MountDevice, o.MountOpts)
|
|
| 44 |
+ return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size)
|
|
| 44 | 45 |
} |
| 45 | 46 |
|
| 46 | 47 |
// scopedPath verifies that the path where the volume is located |
| ... | ... |
@@ -63,15 +68,25 @@ func setOpts(v *localVolume, opts map[string]string) error {
|
| 63 | 63 |
if len(opts) == 0 {
|
| 64 | 64 |
return nil |
| 65 | 65 |
} |
| 66 |
- if err := validateOpts(opts); err != nil {
|
|
| 66 |
+ err := validateOpts(opts) |
|
| 67 |
+ if err != nil {
|
|
| 67 | 68 |
return err |
| 68 | 69 |
} |
| 69 |
- |
|
| 70 | 70 |
v.opts = &optsConfig{
|
| 71 | 71 |
MountType: opts["type"], |
| 72 | 72 |
MountOpts: opts["o"], |
| 73 | 73 |
MountDevice: opts["device"], |
| 74 | 74 |
} |
| 75 |
+ if val, ok := opts["size"]; ok {
|
|
| 76 |
+ size, err := units.RAMInBytes(val) |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ return err |
|
| 79 |
+ } |
|
| 80 |
+ if size > 0 && v.quotaCtl == nil {
|
|
| 81 |
+ return errdefs.InvalidParameter(errors.Errorf("quota size requested but no quota support"))
|
|
| 82 |
+ } |
|
| 83 |
+ v.opts.Quota.Size = uint64(size) |
|
| 84 |
+ } |
|
| 75 | 85 |
return nil |
| 76 | 86 |
} |
| 77 | 87 |
|
| ... | ... |
@@ -84,14 +99,28 @@ func validateOpts(opts map[string]string) error {
|
| 84 | 84 |
return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt))
|
| 85 | 85 |
} |
| 86 | 86 |
} |
| 87 |
- for opt := range mandatoryOpts {
|
|
| 88 |
- if _, ok := opts[opt]; !ok {
|
|
| 89 |
- return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", opt))
|
|
| 87 |
+ for opt, reqopts := range mandatoryOpts {
|
|
| 88 |
+ if _, ok := opts[opt]; ok {
|
|
| 89 |
+ for _, reqopt := range reqopts {
|
|
| 90 |
+ if _, ok := opts[reqopt]; !ok {
|
|
| 91 |
+ return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt))
|
|
| 92 |
+ } |
|
| 93 |
+ } |
|
| 90 | 94 |
} |
| 91 | 95 |
} |
| 92 | 96 |
return nil |
| 93 | 97 |
} |
| 94 | 98 |
|
| 99 |
+func (v *localVolume) needsMount() bool {
|
|
| 100 |
+ if v.opts == nil {
|
|
| 101 |
+ return false |
|
| 102 |
+ } |
|
| 103 |
+ if v.opts.MountDevice != "" || v.opts.MountType != "" {
|
|
| 104 |
+ return true |
|
| 105 |
+ } |
|
| 106 |
+ return false |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 95 | 109 |
func (v *localVolume) mount() error {
|
| 96 | 110 |
if v.opts.MountDevice == "" {
|
| 97 | 111 |
return fmt.Errorf("missing device in volume options")
|
| ... | ... |
@@ -111,6 +140,23 @@ func (v *localVolume) mount() error {
|
| 111 | 111 |
return errors.Wrap(err, "failed to mount local volume") |
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 |
+func (v *localVolume) postMount() error {
|
|
| 115 |
+ if v.opts == nil {
|
|
| 116 |
+ return nil |
|
| 117 |
+ } |
|
| 118 |
+ if v.opts.Quota.Size > 0 {
|
|
| 119 |
+ if v.quotaCtl != nil {
|
|
| 120 |
+ err := v.quotaCtl.SetQuota(v.path, v.opts.Quota) |
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ return err |
|
| 123 |
+ } |
|
| 124 |
+ } else {
|
|
| 125 |
+ return fmt.Errorf("size quota requested for volume but no quota support")
|
|
| 126 |
+ } |
|
| 127 |
+ } |
|
| 128 |
+ return nil |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 114 | 131 |
func (v *localVolume) CreatedAt() (time.Time, error) {
|
| 115 | 132 |
fileInfo, err := os.Stat(v.path) |
| 116 | 133 |
if err != nil {
|
| ... | ... |
@@ -32,10 +32,18 @@ func setOpts(v *localVolume, opts map[string]string) error {
|
| 32 | 32 |
return nil |
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 |
+func (v *localVolume) needsMount() bool {
|
|
| 36 |
+ return false |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 35 | 39 |
func (v *localVolume) mount() error {
|
| 36 | 40 |
return nil |
| 37 | 41 |
} |
| 38 | 42 |
|
| 43 |
+func (v *localVolume) postMount() error {
|
|
| 44 |
+ return nil |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 39 | 47 |
func (v *localVolume) CreatedAt() (time.Time, error) {
|
| 40 | 48 |
fileInfo, err := os.Stat(v.path) |
| 41 | 49 |
if err != nil {
|