package service // import "github.com/docker/docker/volume/service" import ( "context" "fmt" "net" "os" "path/filepath" "runtime" "sync" "time" "github.com/pkg/errors" "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/locker" "github.com/docker/docker/volume" "github.com/docker/docker/volume/drivers" volumemounts "github.com/docker/docker/volume/mounts" "github.com/docker/docker/volume/service/opts" "github.com/sirupsen/logrus" bolt "go.etcd.io/bbolt" ) const ( volumeDataDir = "volumes" ) type volumeWrapper struct { volume.Volume labels map[string]string scope string options map[string]string } func (v volumeWrapper) Options() map[string]string { if v.options == nil { return nil } options := make(map[string]string, len(v.options)) for key, value := range v.options { options[key] = value } return options } func (v volumeWrapper) Labels() map[string]string { if v.labels == nil { return nil } labels := make(map[string]string, len(v.labels)) for key, value := range v.labels { labels[key] = value } return 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() } // NewStore creates a new volume store at the given path func NewStore(rootPath string, drivers *drivers.Store) (*VolumeStore, error) { vs := &VolumeStore{ locks: &locker.Locker{}, names: make(map[string]volume.Volume), refs: make(map[string]map[string]struct{}), labels: make(map[string]map[string]string), options: make(map[string]map[string]string), drivers: drivers, } if rootPath != "" { // initialize metadata store volPath := filepath.Join(rootPath, volumeDataDir) if err := os.MkdirAll(volPath, 0750); err != nil { return nil, err } var err error vs.db, err = bolt.Open(filepath.Join(volPath, "metadata.db"), 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { return nil, errors.Wrap(err, "error while opening volume store metadata database") } // initialize volumes bucket if err := vs.db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucketIfNotExists(volumeBucketName); err != nil { return errors.Wrap(err, "error while setting up volume store metadata database") } return nil }); err != nil { return nil, err } } vs.restore() return vs, nil } func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) { s.globalLock.RLock() v, exists := s.names[name] s.globalLock.RUnlock() return v, exists } func (s *VolumeStore) setNamed(v volume.Volume, ref string) { name := v.Name() s.globalLock.Lock() s.names[name] = v if len(ref) > 0 { if s.refs[name] == nil { s.refs[name] = make(map[string]struct{}) } s.refs[name][ref] = struct{}{} } s.globalLock.Unlock() } // hasRef returns true if the given name has at least one ref. // Callers of this function are expected to hold the name lock. func (s *VolumeStore) hasRef(name string) bool { s.globalLock.RLock() l := len(s.refs[name]) s.globalLock.RUnlock() return l > 0 } // getRefs gets the list of refs for a given name // Callers of this function are expected to hold the name lock. func (s *VolumeStore) getRefs(name string) []string { s.globalLock.RLock() defer s.globalLock.RUnlock() refs := make([]string, 0, len(s.refs[name])) for r := range s.refs[name] { refs = append(refs, r) } return refs } // purge allows the cleanup of internal data on docker in case // the internal data is out of sync with volumes driver plugins. func (s *VolumeStore) purge(ctx context.Context, name string) error { s.globalLock.Lock() defer s.globalLock.Unlock() select { case <-ctx.Done(): return ctx.Err() default: } v, exists := s.names[name] if exists { driverName := v.DriverName() if _, err := s.drivers.ReleaseDriver(driverName); err != nil { logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver") } } if err := s.removeMeta(name); err != nil { logrus.Errorf("Error removing volume metadata for volume %q: %v", name, err) } delete(s.names, name) delete(s.refs, name) delete(s.labels, name) delete(s.options, name) return nil } // VolumeStore is responsible for storing and reference counting volumes. type VolumeStore struct { // locks ensures that only one action is being performed on a particular volume at a time without locking the entire store // since actions on volumes can be quite slow, this ensures the store is free to handle requests for other volumes. locks *locker.Locker drivers *drivers.Store // globalLock is used to protect access to mutable structures used by the store object globalLock sync.RWMutex // names stores the volume name -> volume relationship. // This is used for making lookups faster so we don't have to probe all drivers names map[string]volume.Volume // refs stores the volume name and the list of things referencing it refs map[string]map[string]struct{} // labels stores volume labels for each volume labels map[string]map[string]string // options stores volume options for each volume options map[string]map[string]string db *bolt.DB } func filterByDriver(names []string) filterFunc { return func(v volume.Volume) bool { for _, name := range names { if name == v.DriverName() { return true } } return false } } func (s *VolumeStore) byReferenced(referenced bool) filterFunc { return func(v volume.Volume) bool { return s.hasRef(v.Name()) == referenced } } func (s *VolumeStore) filter(ctx context.Context, vols *[]volume.Volume, by By) (warnings []string, err error) { // note that this specifically does not support the `FromList` By type. switch f := by.(type) { case nil: if *vols == nil { var ls []volume.Volume ls, warnings, err = s.list(ctx) if err != nil { return warnings, err } *vols = ls } case byDriver: if *vols != nil { filter(vols, filterByDriver([]string(f))) return nil, nil } var ls []volume.Volume ls, warnings, err = s.list(ctx, []string(f)...) if err != nil { return nil, err } *vols = ls case ByReferenced: // TODO(@cpuguy83): It would be nice to optimize this by looking at the list // of referenced volumes, however the locking strategy makes this difficult // without either providing inconsistent data or deadlocks. if *vols == nil { var ls []volume.Volume ls, warnings, err = s.list(ctx) if err != nil { return nil, err } *vols = ls } filter(vols, s.byReferenced(bool(f))) case andCombinator: for _, by := range f { w, err := s.filter(ctx, vols, by) if err != nil { return warnings, err } warnings = append(warnings, w...) } case orCombinator: for _, by := range f { switch by.(type) { case byDriver: var ls []volume.Volume w, err := s.filter(ctx, &ls, by) if err != nil { return warnings, err } warnings = append(warnings, w...) default: ls, w, err := s.list(ctx) if err != nil { return warnings, err } warnings = append(warnings, w...) w, err = s.filter(ctx, &ls, by) if err != nil { return warnings, err } warnings = append(warnings, w...) *vols = append(*vols, ls...) } } unique(vols) case CustomFilter: if *vols == nil { var ls []volume.Volume ls, warnings, err = s.list(ctx) if err != nil { return nil, err } *vols = ls } filter(vols, filterFunc(f)) default: return nil, errdefs.InvalidParameter(errors.Errorf("unsupported filter: %T", f)) } return warnings, nil } func unique(ls *[]volume.Volume) { names := make(map[string]bool, len(*ls)) filter(ls, func(v volume.Volume) bool { if names[v.Name()] { return false } names[v.Name()] = true return true }) } // Find lists volumes filtered by the past in filter. // If a driver returns a volume that has name which conflicts with another volume from a different driver, // the first volume is chosen and the conflicting volume is dropped. func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) { logrus.WithField("ByType", fmt.Sprintf("%T", by)).WithField("ByValue", fmt.Sprintf("%+v", by)).Debug("VolumeStore.Find") switch f := by.(type) { case nil, orCombinator, andCombinator, byDriver, ByReferenced, CustomFilter: warnings, err = s.filter(ctx, &vols, by) case fromList: warnings, err = s.filter(ctx, f.ls, f.by) default: // Really shouldn't be possible, but makes sure that any new By's are added to this check. err = errdefs.InvalidParameter(errors.Errorf("unsupported filter type: %T", f)) } if err != nil { return nil, nil, &OpErr{Err: err, Op: "list"} } var out []volume.Volume for _, v := range vols { name := normalizeVolumeName(v.Name()) s.locks.Lock(name) storedV, exists := s.getNamed(name) // Note: it's not safe to populate the cache here because the volume may have been // deleted before we acquire a lock on its name if exists && storedV.DriverName() != v.DriverName() { logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), storedV.DriverName(), v.DriverName()) s.locks.Unlock(v.Name()) continue } out = append(out, v) s.locks.Unlock(v.Name()) } return out, warnings, nil } type filterFunc func(volume.Volume) bool func filter(vols *[]volume.Volume, fn filterFunc) { var evict []int for i, v := range *vols { if !fn(v) { evict = append(evict, i) } } for n, i := range evict { copy((*vols)[i-n:], (*vols)[i-n+1:]) (*vols)[len(*vols)-1] = nil *vols = (*vols)[:len(*vols)-1] } } // list goes through each volume driver and asks for its list of volumes. // TODO(@cpuguy83): plumb context through func (s *VolumeStore) list(ctx context.Context, driverNames ...string) ([]volume.Volume, []string, error) { var ( ls = []volume.Volume{} // do not return a nil value as this affects filtering warnings []string ) var dls []volume.Driver all, err := s.drivers.GetAllDrivers() if err != nil { return nil, nil, err } if len(driverNames) == 0 { dls = all } else { idx := make(map[string]bool, len(driverNames)) for _, name := range driverNames { idx[name] = true } for _, d := range all { if idx[d.Name()] { dls = append(dls, d) } } } type vols struct { vols []volume.Volume err error driverName string } chVols := make(chan vols, len(dls)) for _, vd := range dls { go func(d volume.Driver) { vs, err := d.List() if err != nil { chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}} return } for i, v := range vs { s.globalLock.RLock() vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope(), s.options[v.Name()]} s.globalLock.RUnlock() } chVols <- vols{vols: vs} }(vd) } badDrivers := make(map[string]struct{}) for i := 0; i < len(dls); i++ { vs := <-chVols if vs.err != nil { warnings = append(warnings, vs.err.Error()) badDrivers[vs.driverName] = struct{}{} } ls = append(ls, vs.vols...) } if len(badDrivers) > 0 { s.globalLock.RLock() for _, v := range s.names { if _, exists := badDrivers[v.DriverName()]; exists { ls = append(ls, v) } } s.globalLock.RUnlock() } return ls, warnings, nil } // Create creates a volume with the given name and driver // If the volume needs to be created with a reference to prevent race conditions // with volume cleanup, make sure to use the `CreateWithReference` option. func (s *VolumeStore) Create(ctx context.Context, name, driverName string, createOpts ...opts.CreateOption) (volume.Volume, error) { var cfg opts.CreateConfig for _, o := range createOpts { o(&cfg) } name = normalizeVolumeName(name) s.locks.Lock(name) defer s.locks.Unlock(name) select { case <-ctx.Done(): return nil, ctx.Err() default: } v, err := s.create(ctx, name, driverName, cfg.Options, cfg.Labels) if err != nil { if _, ok := err.(*OpErr); ok { return nil, err } return nil, &OpErr{Err: err, Name: name, Op: "create"} } s.setNamed(v, cfg.Reference) return v, nil } // checkConflict checks the local cache for name collisions with the passed in name, // for existing volumes with the same name but in a different driver. // This is used by `Create` as a best effort to prevent name collisions for volumes. // If a matching volume is found that is not a conflict that is returned so the caller // does not need to perform an additional lookup. // When no matching volume is found, both returns will be nil // // Note: This does not probe all the drivers for name collisions because v1 plugins // are very slow, particularly if the plugin is down, and cause other issues, // particularly around locking the store. // TODO(cpuguy83): With v2 plugins this shouldn't be a problem. Could also potentially // use a connect timeout for this kind of check to ensure we aren't blocking for a // long time. func (s *VolumeStore) checkConflict(ctx context.Context, name, driverName string) (volume.Volume, error) { // check the local cache v, _ := s.getNamed(name) if v == nil { return nil, nil } vDriverName := v.DriverName() var conflict bool if driverName != "" { // Retrieve canonical driver name to avoid inconsistencies (for example // "plugin" vs. "plugin:latest") vd, err := s.drivers.GetDriver(driverName) if err != nil { return nil, err } if vDriverName != vd.Name() { conflict = true } } // let's check if the found volume ref // is stale by checking with the driver if it still exists exists, err := volumeExists(ctx, s.drivers, v) if err != nil { return nil, errors.Wrapf(errNameConflict, "found reference to volume '%s' in driver '%s', but got an error while checking the driver: %v", name, vDriverName, err) } if exists { if conflict { return nil, errors.Wrapf(errNameConflict, "driver '%s' already has volume '%s'", vDriverName, name) } return v, nil } if s.hasRef(v.Name()) { // Containers are referencing this volume but it doesn't seem to exist anywhere. // Return a conflict error here, the user can fix this with `docker volume rm -f` return nil, errors.Wrapf(errNameConflict, "found references to volume '%s' in driver '%s' but the volume was not found in the driver -- you may need to remove containers referencing this volume or force remove the volume to re-create it", name, vDriverName) } // doesn't exist, so purge it from the cache s.purge(ctx, name) return nil, nil } // volumeExists returns if the volume is still present in the driver. // An error is returned if there was an issue communicating with the driver. func volumeExists(ctx context.Context, store *drivers.Store, v volume.Volume) (bool, error) { exists, err := lookupVolume(ctx, store, v.DriverName(), v.Name()) if err != nil { return false, err } return exists != nil, nil } // create asks the given driver to create a volume with the name/opts. // If a volume with the name is already known, it will ask the stored driver for the volume. // If the passed in driver name does not match the driver name which is stored // for the given volume name, an error is returned after checking if the reference is stale. // If the reference is stale, it will be purged and this create can continue. // It is expected that callers of this function hold any necessary locks. func (s *VolumeStore) create(ctx context.Context, name, driverName string, opts, labels map[string]string) (volume.Volume, error) { // Validate the name in a platform-specific manner // volume name validation is specific to the host os and not on container image // windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS parser := volumemounts.NewParser(runtime.GOOS) err := parser.ValidateVolumeName(name) if err != nil { return nil, err } v, err := s.checkConflict(ctx, name, driverName) if err != nil { return nil, err } if v != nil { // there is an existing volume, if we already have this stored locally, return it. // TODO: there could be some inconsistent details such as labels here if vv, _ := s.getNamed(v.Name()); vv != nil { return vv, nil } } // Since there isn't a specified driver name, let's see if any of the existing drivers have this volume name if driverName == "" { v, _ = s.getVolume(ctx, name, "") if v != nil { return v, nil } } if driverName == "" { driverName = volume.DefaultDriverName } vd, err := s.drivers.CreateDriver(driverName) if err != nil { return nil, &OpErr{Op: "create", Name: name, Err: err} } logrus.Debugf("Registering new volume reference: driver %q, name %q", vd.Name(), name) if v, _ = vd.Get(name); v == nil { v, err = vd.Create(name, opts) if err != nil { if _, err := s.drivers.ReleaseDriver(driverName); err != nil { logrus.WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver") } return nil, err } } s.globalLock.Lock() s.labels[name] = labels s.options[name] = opts s.refs[name] = make(map[string]struct{}) s.globalLock.Unlock() metadata := volumeMetadata{ Name: name, Driver: vd.Name(), Labels: labels, Options: opts, } if err := s.setMeta(name, metadata); err != nil { return nil, err } return volumeWrapper{v, labels, vd.Scope(), opts}, nil } // Get looks if a volume with the given name exists and returns it if so func (s *VolumeStore) Get(ctx context.Context, name string, getOptions ...opts.GetOption) (volume.Volume, error) { var cfg opts.GetConfig for _, o := range getOptions { o(&cfg) } name = normalizeVolumeName(name) s.locks.Lock(name) defer s.locks.Unlock(name) v, err := s.getVolume(ctx, name, cfg.Driver) if err != nil { return nil, &OpErr{Err: err, Name: name, Op: "get"} } if cfg.Driver != "" && v.DriverName() != cfg.Driver { return nil, &OpErr{Name: name, Op: "get", Err: errdefs.Conflict(errors.New("found volume driver does not match passed in driver"))} } s.setNamed(v, cfg.Reference) return v, nil } // getVolume requests the volume, if the driver info is stored it just accesses that driver, // if the driver is unknown it probes all drivers until it finds the first volume with that name. // it is expected that callers of this function hold any necessary locks func (s *VolumeStore) getVolume(ctx context.Context, name, driverName string) (volume.Volume, error) { var meta volumeMetadata meta, err := s.getMeta(name) if err != nil { return nil, err } if driverName != "" { if meta.Driver == "" { meta.Driver = driverName } if driverName != meta.Driver { return nil, errdefs.Conflict(errors.New("provided volume driver does not match stored driver")) } } if driverName == "" { driverName = meta.Driver } if driverName == "" { s.globalLock.RLock() select { case <-ctx.Done(): s.globalLock.RUnlock() return nil, ctx.Err() default: } v, exists := s.names[name] s.globalLock.RUnlock() if exists { meta.Driver = v.DriverName() if err := s.setMeta(name, meta); err != nil { return nil, err } } } if meta.Driver != "" { vol, err := lookupVolume(ctx, s.drivers, meta.Driver, name) if err != nil { return nil, err } if vol == nil { s.purge(ctx, name) return nil, errNoSuchVolume } var scope string vd, err := s.drivers.GetDriver(meta.Driver) if err == nil { scope = vd.Scope() } return volumeWrapper{vol, meta.Labels, scope, meta.Options}, nil } logrus.Debugf("Probing all drivers for volume with name: %s", name) drivers, err := s.drivers.GetAllDrivers() if err != nil { return nil, err } for _, d := range drivers { select { case <-ctx.Done(): return nil, ctx.Err() default: } v, err := d.Get(name) if err != nil || v == nil { continue } meta.Driver = v.DriverName() if err := s.setMeta(name, meta); err != nil { return nil, err } return volumeWrapper{v, meta.Labels, d.Scope(), meta.Options}, nil } return nil, errNoSuchVolume } // lookupVolume gets the specified volume from the specified driver. // This will only return errors related to communications with the driver. // If the driver returns an error that is not communication related the // error is logged but not returned. // If the volume is not found it will return `nil, nil`` // TODO(@cpuguy83): plumb through the context to lower level components func lookupVolume(ctx context.Context, store *drivers.Store, driverName, volumeName string) (volume.Volume, error) { if driverName == "" { driverName = volume.DefaultDriverName } vd, err := store.GetDriver(driverName) if err != nil { return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName) } v, err := vd.Get(volumeName) if err != nil { err = errors.Cause(err) if _, ok := err.(net.Error); ok { if v != nil { volumeName = v.Name() driverName = v.DriverName() } return nil, errors.Wrapf(err, "error while checking if volume %q exists in driver %q", volumeName, driverName) } // At this point, the error could be anything from the driver, such as "no such volume" // Let's not check an error here, and instead check if the driver returned a volume logrus.WithError(err).WithField("driver", driverName).WithField("volume", volumeName).Debug("Error while looking up volume") } return v, nil } // Remove removes the requested volume. A volume is not removed if it has any refs func (s *VolumeStore) Remove(ctx context.Context, v volume.Volume, rmOpts ...opts.RemoveOption) error { var cfg opts.RemoveConfig for _, o := range rmOpts { o(&cfg) } name := v.Name() s.locks.Lock(name) defer s.locks.Unlock(name) select { case <-ctx.Done(): return ctx.Err() default: } if s.hasRef(name) { return &OpErr{Err: errVolumeInUse, Name: name, Op: "remove", Refs: s.getRefs(name)} } v, err := s.getVolume(ctx, name, v.DriverName()) if err != nil { return err } vd, err := s.drivers.GetDriver(v.DriverName()) if err != nil { return &OpErr{Err: err, Name: v.DriverName(), Op: "remove"} } logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name) vol := unwrapVolume(v) err = vd.Remove(vol) if err != nil { err = &OpErr{Err: err, Name: name, Op: "remove"} } if err == nil || cfg.PurgeOnError { if e := s.purge(ctx, name); e != nil && err == nil { err = e } } return err } // Release releases the specified reference to the volume func (s *VolumeStore) Release(ctx context.Context, name string, ref string) error { s.locks.Lock(name) defer s.locks.Unlock(name) select { case <-ctx.Done(): return ctx.Err() default: } s.globalLock.Lock() defer s.globalLock.Unlock() select { case <-ctx.Done(): return ctx.Err() default: } if s.refs[name] != nil { delete(s.refs[name], ref) } return nil } // CountReferences gives a count of all references for a given volume. func (s *VolumeStore) CountReferences(v volume.Volume) int { name := normalizeVolumeName(v.Name()) s.locks.Lock(name) defer s.locks.Unlock(name) s.globalLock.Lock() defer s.globalLock.Unlock() return len(s.refs[name]) } func unwrapVolume(v volume.Volume) volume.Volume { if vol, ok := v.(volumeWrapper); ok { return vol.Volume } return v } // Shutdown releases all resources used by the volume store // It does not make any changes to volumes, drivers, etc. func (s *VolumeStore) Shutdown() error { return s.db.Close() }