dotcloud/docker

View on GitHub
volume/service/store.go

Summary

Maintainability
A
0 mins
Test Coverage
package service // import "github.com/docker/docker/volume/service"

import (
    "context"
    "fmt"
    "net"
    "os"
    "path/filepath"
    "sync"
    "time"

    "github.com/containerd/log"
    "github.com/docker/docker/api/types/events"
    "github.com/docker/docker/errdefs"
    "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/moby/locker"
    "github.com/pkg/errors"
    bolt "go.etcd.io/bbolt"
)

const (
    volumeDataDir = "volumes"
)

var _ volume.LiveRestorer = (*volumeWrapper)(nil)

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()
}

func (v volumeWrapper) LiveRestoreVolume(ctx context.Context, ref string) error {
    if vv, ok := v.Volume.(volume.LiveRestorer); ok {
        return vv.LiveRestoreVolume(ctx, ref)
    }
    return nil
}

// StoreOpt sets options for a VolumeStore
type StoreOpt func(store *VolumeStore) error

// NewStore creates a new volume store at the given path
func NewStore(rootPath string, drivers *drivers.Store, opts ...StoreOpt) (*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,
    }

    for _, o := range opts {
        if err := o(vs); err != nil {
            return nil, err
        }
    }

    if rootPath != "" {
        // initialize metadata store
        volPath := filepath.Join(rootPath, volumeDataDir)
        if err := os.MkdirAll(volPath, 0o750); err != nil {
            return nil, err
        }

        var err error
        dbPath := filepath.Join(volPath, "metadata.db")
        vs.db, err = bolt.Open(dbPath, 0o600, &bolt.Options{Timeout: 1 * time.Second})
        if err != nil {
            return nil, errors.Wrapf(err, "error while opening volume store metadata database (%s)", dbPath)
        }

        // 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
}

// WithEventLogger configures the VolumeStore with the given VolumeEventLogger
func WithEventLogger(logger VolumeEventLogger) StoreOpt {
    return func(store *VolumeStore) error {
        store.eventLogger = logger
        return 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 {
            log.G(ctx).WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
        }
    }
    if err := s.removeMeta(name); err != nil {
        log.G(ctx).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
    eventLogger VolumeEventLogger
}

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) {
    log.G(ctx).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() {
            log.G(ctx).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, created, 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"}
    }

    if created && s.eventLogger != nil {
        s.eventLogger.LogVolumeEvent(v.Name(), events.ActionCreate, map[string]string{"driver": v.DriverName()})
    }
    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, bool, error) {
    // Validate the name in a platform-specific manner

    // volume name validation is specific to the host os and not on container image
    parser := volumemounts.NewParser()
    err := parser.ValidateVolumeName(name)
    if err != nil {
        return nil, false, err
    }

    v, err := s.checkConflict(ctx, name, driverName)
    if err != nil {
        return nil, false, 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, false, 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, false, nil
        }
    }

    if driverName == "" {
        driverName = volume.DefaultDriverName
    }
    vd, err := s.drivers.CreateDriver(driverName)
    if err != nil {
        return nil, false, &OpErr{Op: "create", Name: name, Err: err}
    }

    log.G(ctx).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 {
                log.G(ctx).WithError(err).WithField("driver", driverName).Error("Error releasing reference to volume driver")
            }
            return nil, false, 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, true, err
    }
    return volumeWrapper{v, labels, vd.Scope(), opts}, true, 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
    }

    log.G(ctx).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 {
        var nErr net.Error
        if errors.As(err, &nErr) {
            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
        log.G(ctx).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"}
    }

    log.G(ctx).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
        }
    }
    if err == nil && s.eventLogger != nil {
        s.eventLogger.LogVolumeEvent(v.Name(), events.ActionDestroy, map[string]string{"driver": v.DriverName()})
    }
    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()
}