| package service // import "github.com/docker/docker/volume/service" |
| |
| import ( |
| "context" |
| "sync/atomic" |
| |
| "github.com/docker/docker/api/types" |
| "github.com/docker/docker/api/types/filters" |
| "github.com/docker/docker/errdefs" |
| "github.com/docker/docker/pkg/directory" |
| "github.com/docker/docker/pkg/idtools" |
| "github.com/docker/docker/pkg/plugingetter" |
| "github.com/docker/docker/pkg/stringid" |
| "github.com/docker/docker/volume" |
| "github.com/docker/docker/volume/drivers" |
| "github.com/docker/docker/volume/service/opts" |
| "github.com/pkg/errors" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| type ds interface { |
| GetDriverList() []string |
| } |
| |
| type volumeEventLogger interface { |
| LogVolumeEvent(volumeID, action string, attributes map[string]string) |
| } |
| |
| // VolumesService manages access to volumes |
| // This is used as the main access point for volumes to higher level services and the API. |
| type VolumesService struct { |
| vs *VolumeStore |
| ds ds |
| pruneRunning int32 |
| eventLogger volumeEventLogger |
| } |
| |
| // NewVolumeService creates a new volume service |
| func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.Identity, logger volumeEventLogger) (*VolumesService, error) { |
| ds := drivers.NewStore(pg) |
| if err := setupDefaultDriver(ds, root, rootIDs); err != nil { |
| return nil, err |
| } |
| |
| vs, err := NewStore(root, ds) |
| if err != nil { |
| return nil, err |
| } |
| return &VolumesService{vs: vs, ds: ds, eventLogger: logger}, nil |
| } |
| |
| // GetDriverList gets the list of registered volume drivers |
| func (s *VolumesService) GetDriverList() []string { |
| return s.ds.GetDriverList() |
| } |
| |
| // Create creates a volume |
| // If the caller is creating this volume to be consumed immediately, it is |
| // expected that the caller specifies a reference ID. |
| // This reference ID will protect this volume from removal. |
| // |
| // A good example for a reference ID is a container's ID. |
| // When whatever is going to reference this volume is removed the caller should defeference the volume by calling `Release`. |
| func (s *VolumesService) Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) { |
| if name == "" { |
| name = stringid.GenerateRandomID() |
| } |
| v, err := s.vs.Create(ctx, name, driverName, opts...) |
| if err != nil { |
| return nil, err |
| } |
| |
| s.eventLogger.LogVolumeEvent(v.Name(), "create", map[string]string{"driver": v.DriverName()}) |
| apiV := volumeToAPIType(v) |
| return &apiV, nil |
| } |
| |
| // Get returns details about a volume |
| func (s *VolumesService) Get(ctx context.Context, name string, getOpts ...opts.GetOption) (*types.Volume, error) { |
| v, err := s.vs.Get(ctx, name, getOpts...) |
| if err != nil { |
| return nil, err |
| } |
| vol := volumeToAPIType(v) |
| |
| var cfg opts.GetConfig |
| for _, o := range getOpts { |
| o(&cfg) |
| } |
| |
| if cfg.ResolveStatus { |
| vol.Status = v.Status() |
| } |
| return &vol, nil |
| } |
| |
| // Mount mounts the volume |
| // Callers should specify a uniqe reference for each Mount/Unmount pair. |
| // |
| // Example: |
| // ```go |
| // mountID := "randomString" |
| // s.Mount(ctx, vol, mountID) |
| // s.Unmount(ctx, vol, mountID) |
| // ``` |
| func (s *VolumesService) Mount(ctx context.Context, vol *types.Volume, ref string) (string, error) { |
| v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver)) |
| if err != nil { |
| if IsNotExist(err) { |
| err = errdefs.NotFound(err) |
| } |
| return "", err |
| } |
| return v.Mount(ref) |
| } |
| |
| // Unmount unmounts the volume. |
| // Note that depending on the implementation, the volume may still be mounted due to other resources using it. |
| // |
| // The reference specified here should be the same reference specified during `Mount` and should be |
| // unique for each mount/unmount pair. |
| // See `Mount` documentation for an example. |
| func (s *VolumesService) Unmount(ctx context.Context, vol *types.Volume, ref string) error { |
| v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver)) |
| if err != nil { |
| if IsNotExist(err) { |
| err = errdefs.NotFound(err) |
| } |
| return err |
| } |
| return v.Unmount(ref) |
| } |
| |
| // Release releases a volume reference |
| func (s *VolumesService) Release(ctx context.Context, name string, ref string) error { |
| return s.vs.Release(ctx, name, ref) |
| } |
| |
| // Remove removes a volume |
| // An error is returned if the volume is still referenced. |
| func (s *VolumesService) Remove(ctx context.Context, name string, rmOpts ...opts.RemoveOption) error { |
| var cfg opts.RemoveConfig |
| for _, o := range rmOpts { |
| o(&cfg) |
| } |
| |
| v, err := s.vs.Get(ctx, name) |
| if err != nil { |
| if IsNotExist(err) && cfg.PurgeOnError { |
| return nil |
| } |
| return err |
| } |
| |
| err = s.vs.Remove(ctx, v, rmOpts...) |
| if IsNotExist(err) { |
| err = nil |
| } else if IsInUse(err) { |
| err = errdefs.Conflict(err) |
| } else if IsNotExist(err) && cfg.PurgeOnError { |
| err = nil |
| } |
| |
| if err == nil { |
| s.eventLogger.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()}) |
| } |
| return err |
| } |
| |
| var acceptedPruneFilters = map[string]bool{ |
| "label": true, |
| "label!": true, |
| } |
| |
| var acceptedListFilters = map[string]bool{ |
| "dangling": true, |
| "name": true, |
| "driver": true, |
| "label": true, |
| } |
| |
| // LocalVolumesSize gets all local volumes and fetches their size on disk |
| // Note that this intentionally skips volumes which have mount options. Typically |
| // volumes with mount options are not really local even if they are using the |
| // local driver. |
| func (s *VolumesService) LocalVolumesSize(ctx context.Context) ([]*types.Volume, error) { |
| ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), CustomFilter(func(v volume.Volume) bool { |
| dv, ok := v.(volume.DetailedVolume) |
| return ok && len(dv.Options()) == 0 |
| }))) |
| if err != nil { |
| return nil, err |
| } |
| return s.volumesToAPI(ctx, ls, calcSize(true)), nil |
| } |
| |
| // Prune removes (local) volumes which match the past in filter arguments. |
| // Note that this intentionally skips volumes with mount options as there would |
| // be no space reclaimed in this case. |
| func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types.VolumesPruneReport, error) { |
| if !atomic.CompareAndSwapInt32(&s.pruneRunning, 0, 1) { |
| return nil, errdefs.Conflict(errors.New("a prune operation is already running")) |
| } |
| defer atomic.StoreInt32(&s.pruneRunning, 0) |
| |
| by, err := filtersToBy(filter, acceptedPruneFilters) |
| if err != nil { |
| return nil, err |
| } |
| ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), ByReferenced(false), by, CustomFilter(func(v volume.Volume) bool { |
| dv, ok := v.(volume.DetailedVolume) |
| return ok && len(dv.Options()) == 0 |
| }))) |
| if err != nil { |
| return nil, err |
| } |
| |
| rep := &types.VolumesPruneReport{VolumesDeleted: make([]string, 0, len(ls))} |
| for _, v := range ls { |
| select { |
| case <-ctx.Done(): |
| err := ctx.Err() |
| if err == context.Canceled { |
| err = nil |
| } |
| return rep, err |
| default: |
| } |
| |
| vSize, err := directory.Size(ctx, v.Path()) |
| if err != nil { |
| logrus.WithField("volume", v.Name()).WithError(err).Warn("could not determine size of volume") |
| } |
| if err := s.vs.Remove(ctx, v); err != nil { |
| logrus.WithError(err).WithField("volume", v.Name()).Warnf("Could not determine size of volume") |
| continue |
| } |
| rep.SpaceReclaimed += uint64(vSize) |
| rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name()) |
| } |
| return rep, nil |
| } |
| |
| // List gets the list of volumes which match the past in filters |
| // If filters is nil or empty all volumes are returned. |
| func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) { |
| by, err := filtersToBy(filter, acceptedListFilters) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| volumes, warnings, err := s.vs.Find(ctx, by) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil |
| } |
| |
| // Shutdown shuts down the image service and dependencies |
| func (s *VolumesService) Shutdown() error { |
| return s.vs.Shutdown() |
| } |