github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/volume/service/service.go (about) 1 package service // import "github.com/docker/docker/volume/service" 2 3 import ( 4 "context" 5 "strconv" 6 "sync/atomic" 7 8 "github.com/docker/docker/api/types" 9 "github.com/docker/docker/api/types/filters" 10 "github.com/docker/docker/errdefs" 11 "github.com/docker/docker/pkg/directory" 12 "github.com/docker/docker/pkg/idtools" 13 "github.com/docker/docker/pkg/plugingetter" 14 "github.com/docker/docker/pkg/stringid" 15 "github.com/docker/docker/volume" 16 "github.com/docker/docker/volume/drivers" 17 "github.com/docker/docker/volume/service/opts" 18 "github.com/pkg/errors" 19 "github.com/sirupsen/logrus" 20 "golang.org/x/sync/singleflight" 21 ) 22 23 type ds interface { 24 GetDriverList() []string 25 } 26 27 // VolumeEventLogger interface provides methods to log volume-related events 28 type VolumeEventLogger interface { 29 // LogVolumeEvent generates an event related to a volume. 30 LogVolumeEvent(volumeID, action string, attributes map[string]string) 31 } 32 33 // VolumesService manages access to volumes 34 // This is used as the main access point for volumes to higher level services and the API. 35 type VolumesService struct { 36 vs *VolumeStore 37 ds ds 38 pruneRunning int32 39 eventLogger VolumeEventLogger 40 usage singleflight.Group 41 } 42 43 // NewVolumeService creates a new volume service 44 func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.Identity, logger VolumeEventLogger) (*VolumesService, error) { 45 ds := drivers.NewStore(pg) 46 if err := setupDefaultDriver(ds, root, rootIDs); err != nil { 47 return nil, err 48 } 49 50 vs, err := NewStore(root, ds, WithEventLogger(logger)) 51 if err != nil { 52 return nil, err 53 } 54 return &VolumesService{vs: vs, ds: ds, eventLogger: logger}, nil 55 } 56 57 // GetDriverList gets the list of registered volume drivers 58 func (s *VolumesService) GetDriverList() []string { 59 return s.ds.GetDriverList() 60 } 61 62 // Create creates a volume 63 // If the caller is creating this volume to be consumed immediately, it is 64 // expected that the caller specifies a reference ID. 65 // This reference ID will protect this volume from removal. 66 // 67 // A good example for a reference ID is a container's ID. 68 // When whatever is going to reference this volume is removed the caller should defeference the volume by calling `Release`. 69 func (s *VolumesService) Create(ctx context.Context, name, driverName string, opts ...opts.CreateOption) (*types.Volume, error) { 70 if name == "" { 71 name = stringid.GenerateRandomID() 72 } 73 v, err := s.vs.Create(ctx, name, driverName, opts...) 74 if err != nil { 75 return nil, err 76 } 77 78 apiV := volumeToAPIType(v) 79 return &apiV, nil 80 } 81 82 // Get returns details about a volume 83 func (s *VolumesService) Get(ctx context.Context, name string, getOpts ...opts.GetOption) (*types.Volume, error) { 84 v, err := s.vs.Get(ctx, name, getOpts...) 85 if err != nil { 86 return nil, err 87 } 88 vol := volumeToAPIType(v) 89 90 var cfg opts.GetConfig 91 for _, o := range getOpts { 92 o(&cfg) 93 } 94 95 if cfg.ResolveStatus { 96 vol.Status = v.Status() 97 } 98 return &vol, nil 99 } 100 101 // Mount mounts the volume 102 // Callers should specify a uniqe reference for each Mount/Unmount pair. 103 // 104 // Example: 105 // ```go 106 // mountID := "randomString" 107 // s.Mount(ctx, vol, mountID) 108 // s.Unmount(ctx, vol, mountID) 109 // ``` 110 func (s *VolumesService) Mount(ctx context.Context, vol *types.Volume, ref string) (string, error) { 111 v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver)) 112 if err != nil { 113 if IsNotExist(err) { 114 err = errdefs.NotFound(err) 115 } 116 return "", err 117 } 118 return v.Mount(ref) 119 } 120 121 // Unmount unmounts the volume. 122 // Note that depending on the implementation, the volume may still be mounted due to other resources using it. 123 // 124 // The reference specified here should be the same reference specified during `Mount` and should be 125 // unique for each mount/unmount pair. 126 // See `Mount` documentation for an example. 127 func (s *VolumesService) Unmount(ctx context.Context, vol *types.Volume, ref string) error { 128 v, err := s.vs.Get(ctx, vol.Name, opts.WithGetDriver(vol.Driver)) 129 if err != nil { 130 if IsNotExist(err) { 131 err = errdefs.NotFound(err) 132 } 133 return err 134 } 135 return v.Unmount(ref) 136 } 137 138 // Release releases a volume reference 139 func (s *VolumesService) Release(ctx context.Context, name string, ref string) error { 140 return s.vs.Release(ctx, name, ref) 141 } 142 143 // Remove removes a volume 144 // An error is returned if the volume is still referenced. 145 func (s *VolumesService) Remove(ctx context.Context, name string, rmOpts ...opts.RemoveOption) error { 146 var cfg opts.RemoveConfig 147 for _, o := range rmOpts { 148 o(&cfg) 149 } 150 151 v, err := s.vs.Get(ctx, name) 152 if err != nil { 153 if IsNotExist(err) && cfg.PurgeOnError { 154 return nil 155 } 156 return err 157 } 158 159 err = s.vs.Remove(ctx, v, rmOpts...) 160 if IsNotExist(err) { 161 err = nil 162 } else if IsInUse(err) { 163 err = errdefs.Conflict(err) 164 } else if IsNotExist(err) && cfg.PurgeOnError { 165 err = nil 166 } 167 return err 168 } 169 170 var acceptedPruneFilters = map[string]bool{ 171 "label": true, 172 "label!": true, 173 } 174 175 var acceptedListFilters = map[string]bool{ 176 "dangling": true, 177 "name": true, 178 "driver": true, 179 "label": true, 180 } 181 182 // LocalVolumesSize gets all local volumes and fetches their size on disk 183 // Note that this intentionally skips volumes which have mount options. Typically 184 // volumes with mount options are not really local even if they are using the 185 // local driver. 186 func (s *VolumesService) LocalVolumesSize(ctx context.Context) ([]*types.Volume, error) { 187 ch := s.usage.DoChan("LocalVolumesSize", func() (interface{}, error) { 188 ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), CustomFilter(func(v volume.Volume) bool { 189 dv, ok := v.(volume.DetailedVolume) 190 return ok && len(dv.Options()) == 0 191 }))) 192 if err != nil { 193 return nil, err 194 } 195 return s.volumesToAPI(ctx, ls, calcSize(true)), nil 196 }) 197 select { 198 case <-ctx.Done(): 199 return nil, ctx.Err() 200 case res := <-ch: 201 if res.Err != nil { 202 return nil, res.Err 203 } 204 return res.Val.([]*types.Volume), nil 205 } 206 } 207 208 // Prune removes (local) volumes which match the past in filter arguments. 209 // Note that this intentionally skips volumes with mount options as there would 210 // be no space reclaimed in this case. 211 func (s *VolumesService) Prune(ctx context.Context, filter filters.Args) (*types.VolumesPruneReport, error) { 212 if !atomic.CompareAndSwapInt32(&s.pruneRunning, 0, 1) { 213 return nil, errdefs.Conflict(errors.New("a prune operation is already running")) 214 } 215 defer atomic.StoreInt32(&s.pruneRunning, 0) 216 217 by, err := filtersToBy(filter, acceptedPruneFilters) 218 if err != nil { 219 return nil, err 220 } 221 ls, _, err := s.vs.Find(ctx, And(ByDriver(volume.DefaultDriverName), ByReferenced(false), by, CustomFilter(func(v volume.Volume) bool { 222 dv, ok := v.(volume.DetailedVolume) 223 return ok && len(dv.Options()) == 0 224 }))) 225 if err != nil { 226 return nil, err 227 } 228 229 rep := &types.VolumesPruneReport{VolumesDeleted: make([]string, 0, len(ls))} 230 for _, v := range ls { 231 select { 232 case <-ctx.Done(): 233 err := ctx.Err() 234 if err == context.Canceled { 235 err = nil 236 } 237 return rep, err 238 default: 239 } 240 241 vSize, err := directory.Size(ctx, v.Path()) 242 if err != nil { 243 logrus.WithField("volume", v.Name()).WithError(err).Warn("could not determine size of volume") 244 } 245 if err := s.vs.Remove(ctx, v); err != nil { 246 logrus.WithError(err).WithField("volume", v.Name()).Warnf("Could not determine size of volume") 247 continue 248 } 249 rep.SpaceReclaimed += uint64(vSize) 250 rep.VolumesDeleted = append(rep.VolumesDeleted, v.Name()) 251 } 252 s.eventLogger.LogVolumeEvent("", "prune", map[string]string{ 253 "reclaimed": strconv.FormatInt(int64(rep.SpaceReclaimed), 10), 254 }) 255 return rep, nil 256 } 257 258 // List gets the list of volumes which match the past in filters 259 // If filters is nil or empty all volumes are returned. 260 func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) { 261 by, err := filtersToBy(filter, acceptedListFilters) 262 if err != nil { 263 return nil, nil, err 264 } 265 266 volumes, warnings, err := s.vs.Find(ctx, by) 267 if err != nil { 268 return nil, nil, err 269 } 270 271 return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil 272 } 273 274 // Shutdown shuts down the image service and dependencies 275 func (s *VolumesService) Shutdown() error { 276 return s.vs.Shutdown() 277 }