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  }