github.com/demonoid81/containerd@v1.3.4/snapshots/devmapper/pool_device.go (about) 1 // +build linux 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package devmapper 20 21 import ( 22 "context" 23 "path/filepath" 24 "strconv" 25 26 "github.com/hashicorp/go-multierror" 27 "github.com/pkg/errors" 28 29 "github.com/containerd/containerd/log" 30 "github.com/containerd/containerd/snapshots/devmapper/dmsetup" 31 ) 32 33 // PoolDevice ties together data and metadata volumes, represents thin-pool and manages volumes, snapshots and device ids. 34 type PoolDevice struct { 35 poolName string 36 metadata *PoolMetadata 37 } 38 39 // NewPoolDevice creates new thin-pool from existing data and metadata volumes. 40 // If pool 'poolName' already exists, it'll be reloaded with new parameters. 41 func NewPoolDevice(ctx context.Context, config *Config) (*PoolDevice, error) { 42 log.G(ctx).Infof("initializing pool device %q", config.PoolName) 43 44 version, err := dmsetup.Version() 45 if err != nil { 46 log.G(ctx).Errorf("dmsetup not available") 47 return nil, err 48 } 49 50 log.G(ctx).Infof("using dmsetup:\n%s", version) 51 52 dbpath := filepath.Join(config.RootPath, config.PoolName+".db") 53 poolMetaStore, err := NewPoolMetadata(dbpath) 54 if err != nil { 55 return nil, err 56 } 57 58 // Make sure pool exists and available 59 poolPath := dmsetup.GetFullDevicePath(config.PoolName) 60 if _, err := dmsetup.Info(poolPath); err != nil { 61 return nil, errors.Wrapf(err, "failed to query pool %q", poolPath) 62 } 63 64 poolDevice := &PoolDevice{ 65 poolName: config.PoolName, 66 metadata: poolMetaStore, 67 } 68 69 if err := poolDevice.ensureDeviceStates(ctx); err != nil { 70 return nil, errors.Wrap(err, "failed to check devices state") 71 } 72 73 return poolDevice, nil 74 } 75 76 // ensureDeviceStates updates devices to their real state: 77 // - marks devices with incomplete states (after crash) as 'Faulty' 78 // - activates devices if they are marked as 'Activated' but the dm 79 // device is not active, which can happen to a stopped container 80 // after a reboot 81 func (p *PoolDevice) ensureDeviceStates(ctx context.Context) error { 82 var faultyDevices []*DeviceInfo 83 var activatedDevices []*DeviceInfo 84 85 if err := p.metadata.WalkDevices(ctx, func(info *DeviceInfo) error { 86 switch info.State { 87 case Suspended, Resumed, Deactivated, Removed, Faulty: 88 case Activated: 89 activatedDevices = append(activatedDevices, info) 90 default: 91 faultyDevices = append(faultyDevices, info) 92 } 93 return nil 94 }); err != nil { 95 return errors.Wrap(err, "failed to query devices from metastore") 96 } 97 98 var result *multierror.Error 99 for _, dev := range activatedDevices { 100 if p.IsActivated(dev.Name) { 101 continue 102 } 103 104 log.G(ctx).Warnf("devmapper device %q marked as %q but not active, activating it", dev.Name, dev.State) 105 if err := p.activateDevice(ctx, dev); err != nil { 106 result = multierror.Append(result, err) 107 } 108 } 109 110 for _, dev := range faultyDevices { 111 log.G(ctx). 112 WithField("dev_id", dev.DeviceID). 113 WithField("parent", dev.ParentName). 114 WithField("error", dev.Error). 115 Warnf("devmapper device %q has invalid state %q, marking as faulty", dev.Name, dev.State) 116 117 if err := p.metadata.MarkFaulty(ctx, dev.Name); err != nil { 118 result = multierror.Append(result, err) 119 } 120 } 121 122 return multierror.Prefix(result.ErrorOrNil(), "devmapper:") 123 } 124 125 // transition invokes 'updateStateFn' callback to perform devmapper operation and reflects device state changes/errors in meta store. 126 // 'tryingState' will be set before invoking callback. If callback succeeded 'successState' will be set, otherwise 127 // error details will be recorded in meta store. 128 func (p *PoolDevice) transition(ctx context.Context, deviceName string, tryingState DeviceState, successState DeviceState, updateStateFn func() error) error { 129 // Set device to trying state 130 uerr := p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error { 131 deviceInfo.State = tryingState 132 return nil 133 }) 134 135 if uerr != nil { 136 return errors.Wrapf(uerr, "failed to set device %q state to %q", deviceName, tryingState) 137 } 138 139 var result *multierror.Error 140 141 // Invoke devmapper operation 142 err := updateStateFn() 143 144 if err != nil { 145 result = multierror.Append(result, err) 146 } 147 148 // If operation succeeded transition to success state, otherwise save error details 149 uerr = p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error { 150 if err == nil { 151 deviceInfo.State = successState 152 deviceInfo.Error = "" 153 } else { 154 deviceInfo.Error = err.Error() 155 } 156 return nil 157 }) 158 159 if uerr != nil { 160 result = multierror.Append(result, uerr) 161 } 162 163 return result.ErrorOrNil() 164 } 165 166 // CreateThinDevice creates new devmapper thin-device with given name and size. 167 // Device ID for thin-device will be allocated from metadata store. 168 // If allocation successful, device will be activated with /dev/mapper/<deviceName> 169 func (p *PoolDevice) CreateThinDevice(ctx context.Context, deviceName string, virtualSizeBytes uint64) (retErr error) { 170 info := &DeviceInfo{ 171 Name: deviceName, 172 Size: virtualSizeBytes, 173 State: Unknown, 174 } 175 176 var ( 177 metaErr error 178 devErr error 179 activeErr error 180 ) 181 182 defer func() { 183 // We've created a devmapper device, but failed to activate it, try rollback everything 184 if activeErr != nil { 185 // Delete the device first. 186 delErr := p.deleteDevice(ctx, info) 187 if delErr != nil { 188 // Failed to rollback, mark the device as faulty and keep metadata in order to 189 // preserve the faulty device ID 190 retErr = multierror.Append(retErr, delErr, p.metadata.MarkFaulty(ctx, info.Name)) 191 return 192 } 193 194 // The devmapper device has been successfully deleted, deallocate device ID 195 if err := p.RemoveDevice(ctx, info.Name); err != nil { 196 retErr = multierror.Append(retErr, err) 197 return 198 } 199 200 return 201 } 202 203 // We're unable to create the devmapper device, most likely something wrong with the deviceID 204 if devErr != nil { 205 retErr = multierror.Append(retErr, p.metadata.MarkFaulty(ctx, info.Name)) 206 return 207 } 208 }() 209 210 // Save initial device metadata and allocate new device ID from store 211 metaErr = p.metadata.AddDevice(ctx, info) 212 if metaErr != nil { 213 return metaErr 214 } 215 216 // Create thin device 217 devErr = p.createDevice(ctx, info) 218 if devErr != nil { 219 return devErr 220 } 221 222 // Activate thin device 223 activeErr = p.activateDevice(ctx, info) 224 if activeErr != nil { 225 return activeErr 226 } 227 228 return nil 229 } 230 231 // createDevice creates thin device 232 func (p *PoolDevice) createDevice(ctx context.Context, info *DeviceInfo) error { 233 if err := p.transition(ctx, info.Name, Creating, Created, func() error { 234 return dmsetup.CreateDevice(p.poolName, info.DeviceID) 235 }); err != nil { 236 return errors.Wrapf(err, "failed to create new thin device %q (dev: %d)", info.Name, info.DeviceID) 237 } 238 239 return nil 240 } 241 242 // activateDevice activates thin device 243 func (p *PoolDevice) activateDevice(ctx context.Context, info *DeviceInfo) error { 244 if err := p.transition(ctx, info.Name, Activating, Activated, func() error { 245 return dmsetup.ActivateDevice(p.poolName, info.Name, info.DeviceID, info.Size, "") 246 }); err != nil { 247 return errors.Wrapf(err, "failed to activate new thin device %q (dev: %d)", info.Name, info.DeviceID) 248 } 249 250 return nil 251 } 252 253 // CreateSnapshotDevice creates and activates new thin-device from parent thin-device (makes snapshot) 254 func (p *PoolDevice) CreateSnapshotDevice(ctx context.Context, deviceName string, snapshotName string, virtualSizeBytes uint64) (retErr error) { 255 baseInfo, err := p.metadata.GetDevice(ctx, deviceName) 256 if err != nil { 257 return errors.Wrapf(err, "failed to query device metadata for %q", deviceName) 258 } 259 260 snapInfo := &DeviceInfo{ 261 Name: snapshotName, 262 Size: virtualSizeBytes, 263 ParentName: deviceName, 264 State: Unknown, 265 } 266 267 var ( 268 metaErr error 269 devErr error 270 activeErr error 271 ) 272 273 defer func() { 274 // We've created a devmapper device, but failed to activate it, try rollback everything 275 if activeErr != nil { 276 // Delete the device first. 277 delErr := p.deleteDevice(ctx, snapInfo) 278 if delErr != nil { 279 // Failed to rollback, mark the device as faulty and keep metadata in order to 280 // preserve the faulty device ID 281 retErr = multierror.Append(retErr, delErr, p.metadata.MarkFaulty(ctx, snapInfo.Name)) 282 return 283 } 284 285 // The devmapper device has been successfully deleted, deallocate device ID 286 if err := p.RemoveDevice(ctx, snapInfo.Name); err != nil { 287 retErr = multierror.Append(retErr, err) 288 return 289 } 290 291 return 292 } 293 294 // We're unable to create the devmapper device, most likely something wrong with the deviceID 295 if devErr != nil { 296 retErr = multierror.Append(retErr, p.metadata.MarkFaulty(ctx, snapInfo.Name)) 297 return 298 } 299 }() 300 301 // Save snapshot metadata and allocate new device ID 302 metaErr = p.metadata.AddDevice(ctx, snapInfo) 303 if metaErr != nil { 304 return metaErr 305 } 306 307 // Create thin device snapshot 308 devErr = p.createSnapshot(ctx, baseInfo, snapInfo) 309 if devErr != nil { 310 return devErr 311 } 312 313 // Activate the snapshot device 314 activeErr = p.activateDevice(ctx, snapInfo) 315 if activeErr != nil { 316 return activeErr 317 } 318 319 return nil 320 } 321 322 func (p *PoolDevice) createSnapshot(ctx context.Context, baseInfo, snapInfo *DeviceInfo) error { 323 if err := p.transition(ctx, snapInfo.Name, Creating, Created, func() error { 324 return dmsetup.CreateSnapshot(p.poolName, snapInfo.DeviceID, baseInfo.DeviceID) 325 }); err != nil { 326 return errors.Wrapf(err, 327 "failed to create snapshot %q (dev: %d) from %q (dev: %d)", 328 snapInfo.Name, 329 snapInfo.DeviceID, 330 baseInfo.Name, 331 baseInfo.DeviceID) 332 } 333 334 return nil 335 } 336 337 // SuspendDevice flushes the outstanding IO and blocks the further IO 338 func (p *PoolDevice) SuspendDevice(ctx context.Context, deviceName string) error { 339 if err := p.transition(ctx, deviceName, Suspending, Suspended, func() error { 340 return dmsetup.SuspendDevice(deviceName) 341 }); err != nil { 342 return errors.Wrapf(err, "failed to suspend device %q", deviceName) 343 } 344 345 return nil 346 } 347 348 // DeactivateDevice deactivates thin device 349 func (p *PoolDevice) DeactivateDevice(ctx context.Context, deviceName string, deferred, withForce bool) error { 350 if !p.IsLoaded(deviceName) { 351 return nil 352 } 353 354 opts := []dmsetup.RemoveDeviceOpt{dmsetup.RemoveWithRetries} 355 if deferred { 356 opts = append(opts, dmsetup.RemoveDeferred) 357 } 358 if withForce { 359 opts = append(opts, dmsetup.RemoveWithForce) 360 } 361 362 if err := p.transition(ctx, deviceName, Deactivating, Deactivated, func() error { 363 return dmsetup.RemoveDevice(deviceName, opts...) 364 }); err != nil { 365 return errors.Wrapf(err, "failed to deactivate device %q", deviceName) 366 } 367 368 return nil 369 } 370 371 // IsActivated returns true if thin-device is activated 372 func (p *PoolDevice) IsActivated(deviceName string) bool { 373 infos, err := dmsetup.Info(deviceName) 374 if err != nil || len(infos) != 1 { 375 // Couldn't query device info, device not active 376 return false 377 } 378 379 if devInfo := infos[0]; devInfo.TableLive { 380 return true 381 } 382 383 return false 384 } 385 386 // IsLoaded returns true if thin-device is visible for dmsetup 387 func (p *PoolDevice) IsLoaded(deviceName string) bool { 388 _, err := dmsetup.Info(deviceName) 389 return err == nil 390 } 391 392 // GetUsage reports total size in bytes consumed by a thin-device. 393 // It relies on the number of used blocks reported by 'dmsetup status'. 394 // The output looks like: 395 // device2: 0 204800 thin 17280 204799 396 // Where 17280 is the number of used sectors 397 func (p *PoolDevice) GetUsage(deviceName string) (int64, error) { 398 status, err := dmsetup.Status(deviceName) 399 if err != nil { 400 return 0, errors.Wrapf(err, "can't get status for device %q", deviceName) 401 } 402 403 if len(status.Params) == 0 { 404 return 0, errors.Errorf("failed to get the number of used blocks, unexpected output from dmsetup status") 405 } 406 407 count, err := strconv.ParseInt(status.Params[0], 10, 64) 408 if err != nil { 409 return 0, errors.Wrapf(err, "failed to parse status params: %q", status.Params[0]) 410 } 411 412 return count * dmsetup.SectorSize, nil 413 } 414 415 // RemoveDevice completely wipes out thin device from thin-pool and frees it's device ID 416 func (p *PoolDevice) RemoveDevice(ctx context.Context, deviceName string) error { 417 info, err := p.metadata.GetDevice(ctx, deviceName) 418 if err != nil { 419 return errors.Wrapf(err, "can't query metadata for device %q", deviceName) 420 } 421 422 if err := p.DeactivateDevice(ctx, deviceName, false, true); err != nil { 423 return err 424 } 425 426 if err := p.deleteDevice(ctx, info); err != nil { 427 return err 428 } 429 430 // Remove record from meta store and free device ID 431 if err := p.metadata.RemoveDevice(ctx, deviceName); err != nil { 432 return errors.Wrapf(err, "can't remove device %q metadata from store after removal", deviceName) 433 } 434 435 return nil 436 } 437 438 func (p *PoolDevice) deleteDevice(ctx context.Context, info *DeviceInfo) error { 439 if err := p.transition(ctx, info.Name, Removing, Removed, func() error { 440 // Send 'delete' message to thin-pool 441 return dmsetup.DeleteDevice(p.poolName, info.DeviceID) 442 }); err != nil { 443 return errors.Wrapf(err, "failed to delete device %q (dev id: %d)", info.Name, info.DeviceID) 444 } 445 446 return nil 447 } 448 449 // RemovePool deactivates all child thin-devices and removes thin-pool device 450 func (p *PoolDevice) RemovePool(ctx context.Context) error { 451 deviceNames, err := p.metadata.GetDeviceNames(ctx) 452 if err != nil { 453 return errors.Wrap(err, "can't query device names") 454 } 455 456 var result *multierror.Error 457 458 // Deactivate devices if any 459 for _, name := range deviceNames { 460 if err := p.DeactivateDevice(ctx, name, true, true); err != nil { 461 result = multierror.Append(result, errors.Wrapf(err, "failed to remove %q", name)) 462 } 463 } 464 465 if err := dmsetup.RemoveDevice(p.poolName, dmsetup.RemoveWithForce, dmsetup.RemoveWithRetries, dmsetup.RemoveDeferred); err != nil { 466 result = multierror.Append(result, errors.Wrapf(err, "failed to remove pool %q", p.poolName)) 467 } 468 469 return result.ErrorOrNil() 470 } 471 472 // Close closes pool device (thin-pool will not be removed) 473 func (p *PoolDevice) Close() error { 474 return p.metadata.Close() 475 }