github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/runtime_volume_linux.go (about) 1 // +build linux 2 3 package libpod 4 5 import ( 6 "context" 7 "os" 8 "path/filepath" 9 "strings" 10 "time" 11 12 "github.com/containers/podman/v3/libpod/define" 13 "github.com/containers/podman/v3/libpod/events" 14 volplugin "github.com/containers/podman/v3/libpod/plugin" 15 "github.com/containers/storage/drivers/quota" 16 "github.com/containers/storage/pkg/stringid" 17 pluginapi "github.com/docker/go-plugins-helpers/volume" 18 "github.com/pkg/errors" 19 "github.com/sirupsen/logrus" 20 ) 21 22 // NewVolume creates a new empty volume 23 func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { 24 r.lock.Lock() 25 defer r.lock.Unlock() 26 27 if !r.valid { 28 return nil, define.ErrRuntimeStopped 29 } 30 return r.newVolume(ctx, options...) 31 } 32 33 // newVolume creates a new empty volume 34 func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) (_ *Volume, deferredErr error) { 35 volume := newVolume(r) 36 for _, option := range options { 37 if err := option(volume); err != nil { 38 return nil, errors.Wrapf(err, "error running volume create option") 39 } 40 } 41 42 if volume.config.Name == "" { 43 volume.config.Name = stringid.GenerateNonCryptoID() 44 } 45 if volume.config.Driver == "" { 46 volume.config.Driver = define.VolumeDriverLocal 47 } 48 volume.config.CreatedTime = time.Now() 49 50 // Check if volume with given name exists. 51 exists, err := r.state.HasVolume(volume.config.Name) 52 if err != nil { 53 return nil, errors.Wrapf(err, "error checking if volume with name %s exists", volume.config.Name) 54 } 55 if exists { 56 return nil, errors.Wrapf(define.ErrVolumeExists, "volume with name %s already exists", volume.config.Name) 57 } 58 59 // Plugin can be nil if driver is local, but that's OK - superfluous 60 // assignment doesn't hurt much. 61 plugin, err := r.getVolumePlugin(volume.config.Driver) 62 if err != nil { 63 return nil, errors.Wrapf(err, "volume %s uses volume plugin %s but it could not be retrieved", volume.config.Name, volume.config.Driver) 64 } 65 volume.plugin = plugin 66 67 if volume.config.Driver == define.VolumeDriverLocal { 68 logrus.Debugf("Validating options for local driver") 69 // Validate options 70 for key := range volume.config.Options { 71 switch key { 72 case "device", "o", "type", "UID", "GID", "SIZE", "INODES": 73 // Do nothing, valid keys 74 default: 75 return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key) 76 } 77 } 78 } 79 80 // Now we get conditional: we either need to make the volume in the 81 // volume plugin, or on disk if not using a plugin. 82 if volume.plugin != nil { 83 // We can't chown, or relabel, or similar the path the volume is 84 // using, because it's not managed by us. 85 // TODO: reevaluate this once we actually have volume plugins in 86 // use in production - it may be safe, but I can't tell without 87 // knowing what the actual plugin does... 88 if err := makeVolumeInPluginIfNotExist(volume.config.Name, volume.config.Options, volume.plugin); err != nil { 89 return nil, err 90 } 91 } else { 92 // Create the mountpoint of this volume 93 volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name) 94 if err := os.MkdirAll(volPathRoot, 0700); err != nil { 95 return nil, errors.Wrapf(err, "error creating volume directory %q", volPathRoot) 96 } 97 if err := os.Chown(volPathRoot, volume.config.UID, volume.config.GID); err != nil { 98 return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID) 99 } 100 fullVolPath := filepath.Join(volPathRoot, "_data") 101 if err := os.MkdirAll(fullVolPath, 0755); err != nil { 102 return nil, errors.Wrapf(err, "error creating volume directory %q", fullVolPath) 103 } 104 if err := os.Chown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { 105 return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID) 106 } 107 if err := LabelVolumePath(fullVolPath); err != nil { 108 return nil, err 109 } 110 projectQuotaSupported := false 111 112 q, err := quota.NewControl(r.config.Engine.VolumePath) 113 if err == nil { 114 projectQuotaSupported = true 115 } 116 quota := quota.Quota{} 117 if volume.config.Size > 0 || volume.config.Inodes > 0 { 118 if !projectQuotaSupported { 119 return nil, errors.New("Volume options size and inodes not supported. Filesystem does not support Project Quota") 120 } 121 quota.Size = volume.config.Size 122 quota.Inodes = volume.config.Inodes 123 } 124 if projectQuotaSupported { 125 if err := q.SetQuota(fullVolPath, quota); err != nil { 126 return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath) 127 } 128 } 129 130 volume.config.MountPoint = fullVolPath 131 } 132 133 lock, err := r.lockManager.AllocateLock() 134 if err != nil { 135 return nil, errors.Wrapf(err, "error allocating lock for new volume") 136 } 137 volume.lock = lock 138 volume.config.LockID = volume.lock.ID() 139 140 defer func() { 141 if deferredErr != nil { 142 if err := volume.lock.Free(); err != nil { 143 logrus.Errorf("Error freeing volume lock after failed creation: %v", err) 144 } 145 } 146 }() 147 148 volume.valid = true 149 150 // Add the volume to state 151 if err := r.state.AddVolume(volume); err != nil { 152 return nil, errors.Wrapf(err, "error adding volume to state") 153 } 154 defer volume.newVolumeEvent(events.Create) 155 return volume, nil 156 } 157 158 // makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it 159 // does not already exist. 160 func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error { 161 // Ping the volume plugin to see if it exists first. 162 // If it does, use the existing volume in the plugin. 163 // Options may not match exactly, but not much we can do about 164 // that. Not complaining avoids a lot of the sync issues we see 165 // with c/storage and libpod DB. 166 needsCreate := true 167 getReq := new(pluginapi.GetRequest) 168 getReq.Name = name 169 if resp, err := plugin.GetVolume(getReq); err == nil { 170 // TODO: What do we do if we get a 200 response, but the 171 // Volume is nil? The docs on the Plugin API are very 172 // nonspecific, so I don't know if this is valid or 173 // not... 174 if resp != nil { 175 needsCreate = false 176 logrus.Infof("Volume %q already exists in plugin %q, using existing volume", name, plugin.Name) 177 } 178 } 179 if needsCreate { 180 createReq := new(pluginapi.CreateRequest) 181 createReq.Name = name 182 createReq.Options = options 183 if err := plugin.CreateVolume(createReq); err != nil { 184 return errors.Wrapf(err, "error creating volume %q in plugin %s", name, plugin.Name) 185 } 186 } 187 188 return nil 189 } 190 191 // removeVolume removes the specified volume from state as well tears down its mountpoint and storage 192 func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error { 193 if !v.valid { 194 if ok, _ := r.state.HasVolume(v.Name()); !ok { 195 return nil 196 } 197 return define.ErrVolumeRemoved 198 } 199 200 v.lock.Lock() 201 defer v.lock.Unlock() 202 203 // Update volume status to pick up a potential removal from state 204 if err := v.update(); err != nil { 205 return err 206 } 207 208 deps, err := r.state.VolumeInUse(v) 209 if err != nil { 210 return err 211 } 212 if len(deps) != 0 { 213 depsStr := strings.Join(deps, ", ") 214 if !force { 215 return errors.Wrapf(define.ErrVolumeBeingUsed, "volume %s is being used by the following container(s): %s", v.Name(), depsStr) 216 } 217 218 // We need to remove all containers using the volume 219 for _, dep := range deps { 220 ctr, err := r.state.Container(dep) 221 if err != nil { 222 // If the container's removed, no point in 223 // erroring. 224 if errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrRemoved { 225 continue 226 } 227 228 return errors.Wrapf(err, "error removing container %s that depends on volume %s", dep, v.Name()) 229 } 230 231 logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) 232 233 // TODO: do we want to set force here when removing 234 // containers? 235 // I'm inclined to say no, in case someone accidentally 236 // wipes a container they're using... 237 if err := r.removeContainer(ctx, ctr, false, false, false); err != nil { 238 return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name()) 239 } 240 } 241 } 242 243 // If the volume is still mounted - force unmount it 244 if err := v.unmount(true); err != nil { 245 if force { 246 // If force is set, evict the volume, even if errors 247 // occur. Otherwise we'll never be able to get rid of 248 // them. 249 logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) 250 } else { 251 return errors.Wrapf(err, "error unmounting volume %s", v.Name()) 252 } 253 } 254 255 // Set volume as invalid so it can no longer be used 256 v.valid = false 257 258 var removalErr error 259 260 // If we use a volume plugin, we need to remove from the plugin. 261 if v.UsesVolumeDriver() { 262 canRemove := true 263 264 // Do we have a volume driver? 265 if v.plugin == nil { 266 canRemove = false 267 removalErr = errors.Wrapf(define.ErrMissingPlugin, "cannot remove volume %s from plugin %s, but it has been removed from Podman", v.Name(), v.Driver()) 268 } else { 269 // Ping the plugin first to verify the volume still 270 // exists. 271 // We're trying to be very tolerant of missing volumes 272 // in the backend, to avoid the problems we see with 273 // sync between c/storage and the Libpod DB. 274 getReq := new(pluginapi.GetRequest) 275 getReq.Name = v.Name() 276 if _, err := v.plugin.GetVolume(getReq); err != nil { 277 canRemove = false 278 removalErr = errors.Wrapf(err, "volume %s could not be retrieved from plugin %s, but it has been removed from Podman", v.Name(), v.Driver()) 279 } 280 } 281 if canRemove { 282 req := new(pluginapi.RemoveRequest) 283 req.Name = v.Name() 284 if err := v.plugin.RemoveVolume(req); err != nil { 285 return errors.Wrapf(err, "volume %s could not be removed from plugin %s", v.Name(), v.Driver()) 286 } 287 } 288 } 289 290 // Remove the volume from the state 291 if err := r.state.RemoveVolume(v); err != nil { 292 if removalErr != nil { 293 logrus.Errorf("Error removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr) 294 } 295 return errors.Wrapf(err, "error removing volume %s", v.Name()) 296 } 297 298 // Free the volume's lock 299 if err := v.lock.Free(); err != nil { 300 if removalErr == nil { 301 removalErr = errors.Wrapf(err, "error freeing lock for volume %s", v.Name()) 302 } else { 303 logrus.Errorf("Error freeing lock for volume %q: %v", v.Name(), err) 304 } 305 } 306 307 // Delete the mountpoint path of the volume, that is delete the volume 308 // from /var/lib/containers/storage/volumes 309 if err := v.teardownStorage(); err != nil { 310 if removalErr == nil { 311 removalErr = errors.Wrapf(err, "error cleaning up volume storage for %q", v.Name()) 312 } else { 313 logrus.Errorf("Error cleaning up volume storage for volume %q: %v", v.Name(), err) 314 } 315 } 316 317 defer v.newVolumeEvent(events.Remove) 318 logrus.Debugf("Removed volume %s", v.Name()) 319 return removalErr 320 }