mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
volume/local: decouple presence of options from mounting
Signed-off-by: Timo Rothenpieler <timo@rothenpieler.org>
This commit is contained in:
parent
c677e4cc87
commit
6d593fe6cc
3 changed files with 80 additions and 12 deletions
|
@ -16,10 +16,12 @@ import (
|
|||
"github.com/docker/docker/daemon/names"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/quota"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/moby/sys/mount"
|
||||
"github.com/moby/sys/mountinfo"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 {
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
|
@ -76,6 +82,7 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
|
|||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: r.DataPath(name),
|
||||
quotaCtl: r.quotaCtl,
|
||||
}
|
||||
r.volumes[name] = v
|
||||
optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
|
||||
|
@ -105,6 +112,7 @@ type Root struct {
|
|||
m sync.Mutex
|
||||
scope string
|
||||
path string
|
||||
quotaCtl *quota.Control
|
||||
volumes map[string]*localVolume
|
||||
rootIdentity idtools.Identity
|
||||
}
|
||||
|
@ -162,6 +170,7 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error
|
|||
driverName: r.Name(),
|
||||
name: name,
|
||||
path: path,
|
||||
quotaCtl: r.quotaCtl,
|
||||
}
|
||||
|
||||
if len(opts) != 0 {
|
||||
|
@ -273,6 +282,8 @@ type localVolume struct {
|
|||
opts *optsConfig
|
||||
// active refcounts the active mounts
|
||||
active activeMount
|
||||
// reference to Root instances quotaCtl
|
||||
quotaCtl *quota.Control
|
||||
}
|
||||
|
||||
// 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) {
|
||||
v.m.Lock()
|
||||
defer v.m.Unlock()
|
||||
if v.opts != nil {
|
||||
if v.needsMount() {
|
||||
if !v.active.mounted {
|
||||
if err := v.mount(); err != nil {
|
||||
return "", errdefs.System(err)
|
||||
|
@ -309,6 +320,9 @@ func (v *localVolume) Mount(id string) (string, error) {
|
|||
}
|
||||
v.active.count++
|
||||
}
|
||||
if err := v.postMount(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
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
|
||||
// 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.
|
||||
if v.opts != nil {
|
||||
if v.needsMount() {
|
||||
v.active.count--
|
||||
}
|
||||
|
||||
|
@ -334,7 +348,7 @@ func (v *localVolume) Unmount(id string) error {
|
|||
}
|
||||
|
||||
func (v *localVolume) unmount() error {
|
||||
if v.opts != nil {
|
||||
if v.needsMount() {
|
||||
if err := mount.Unmount(v.path); err != nil {
|
||||
if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
|
||||
return errdefs.System(err)
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/quota"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/moby/sys/mount"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -26,10 +28,12 @@ var (
|
|||
"type": {}, // specify the filesystem type for mount, e.g. nfs
|
||||
"o": {}, // generic mount options
|
||||
"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
|
||||
MountOpts string
|
||||
MountDevice string
|
||||
Quota quota.Quota
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -63,15 +68,25 @@ func setOpts(v *localVolume, opts map[string]string) error {
|
|||
if len(opts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := validateOpts(opts); err != nil {
|
||||
err := validateOpts(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.opts = &optsConfig{
|
||||
MountType: opts["type"],
|
||||
MountOpts: opts["o"],
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -84,14 +99,28 @@ func validateOpts(opts map[string]string) error {
|
|||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
if v.opts.MountDevice == "" {
|
||||
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")
|
||||
}
|
||||
|
||||
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) {
|
||||
fileInfo, err := os.Stat(v.path)
|
||||
if err != nil {
|
||||
|
|
|
@ -32,10 +32,18 @@ func setOpts(v *localVolume, opts map[string]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) needsMount() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *localVolume) mount() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) postMount() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *localVolume) CreatedAt() (time.Time, error) {
|
||||
fileInfo, err := os.Stat(v.path)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Reference in a new issue