github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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  	"time"
    26  
    27  	"github.com/hashicorp/go-multierror"
    28  	"github.com/pkg/errors"
    29  	"golang.org/x/sys/unix"
    30  
    31  	"github.com/containerd/containerd/log"
    32  	"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
    33  )
    34  
    35  // PoolDevice ties together data and metadata volumes, represents thin-pool and manages volumes, snapshots and device ids.
    36  type PoolDevice struct {
    37  	poolName string
    38  	metadata *PoolMetadata
    39  }
    40  
    41  // NewPoolDevice creates new thin-pool from existing data and metadata volumes.
    42  // If pool 'poolName' already exists, it'll be reloaded with new parameters.
    43  func NewPoolDevice(ctx context.Context, config *Config) (*PoolDevice, error) {
    44  	log.G(ctx).Infof("initializing pool device %q", config.PoolName)
    45  
    46  	version, err := dmsetup.Version()
    47  	if err != nil {
    48  		log.G(ctx).Errorf("dmsetup not available")
    49  		return nil, err
    50  	}
    51  
    52  	log.G(ctx).Infof("using dmsetup:\n%s", version)
    53  
    54  	dbpath := filepath.Join(config.RootPath, config.PoolName+".db")
    55  	poolMetaStore, err := NewPoolMetadata(dbpath)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	// Make sure pool exists and available
    61  	poolPath := dmsetup.GetFullDevicePath(config.PoolName)
    62  	if _, err := dmsetup.Info(poolPath); err != nil {
    63  		return nil, errors.Wrapf(err, "failed to query pool %q", poolPath)
    64  	}
    65  
    66  	poolDevice := &PoolDevice{
    67  		poolName: config.PoolName,
    68  		metadata: poolMetaStore,
    69  	}
    70  
    71  	if err := poolDevice.ensureDeviceStates(ctx); err != nil {
    72  		return nil, errors.Wrap(err, "failed to check devices state")
    73  	}
    74  
    75  	return poolDevice, nil
    76  }
    77  
    78  // ensureDeviceStates updates devices to their real state:
    79  //   - marks devices with incomplete states (after crash) as 'Faulty'
    80  //   - activates devices if they are marked as 'Activated' but the dm
    81  //     device is not active, which can happen to a stopped container
    82  //     after a reboot
    83  func (p *PoolDevice) ensureDeviceStates(ctx context.Context) error {
    84  	var faultyDevices []*DeviceInfo
    85  	var activatedDevices []*DeviceInfo
    86  
    87  	if err := p.WalkDevices(ctx, func(info *DeviceInfo) error {
    88  		switch info.State {
    89  		case Suspended, Resumed, Deactivated, Removed, Faulty:
    90  		case Activated:
    91  			activatedDevices = append(activatedDevices, info)
    92  		default:
    93  			faultyDevices = append(faultyDevices, info)
    94  		}
    95  		return nil
    96  	}); err != nil {
    97  		return errors.Wrap(err, "failed to query devices from metastore")
    98  	}
    99  
   100  	var result *multierror.Error
   101  	for _, dev := range activatedDevices {
   102  		if p.IsActivated(dev.Name) {
   103  			continue
   104  		}
   105  
   106  		log.G(ctx).Warnf("devmapper device %q marked as %q but not active, activating it", dev.Name, dev.State)
   107  		if err := p.activateDevice(ctx, dev); err != nil {
   108  			result = multierror.Append(result, err)
   109  		}
   110  	}
   111  
   112  	for _, dev := range faultyDevices {
   113  		log.G(ctx).
   114  			WithField("dev_id", dev.DeviceID).
   115  			WithField("parent", dev.ParentName).
   116  			WithField("error", dev.Error).
   117  			Warnf("devmapper device %q has invalid state %q, marking as faulty", dev.Name, dev.State)
   118  
   119  		if err := p.metadata.MarkFaulty(ctx, dev.Name); err != nil {
   120  			result = multierror.Append(result, err)
   121  		}
   122  	}
   123  
   124  	return multierror.Prefix(result.ErrorOrNil(), "devmapper:")
   125  }
   126  
   127  // transition invokes 'updateStateFn' callback to perform devmapper operation and reflects device state changes/errors in meta store.
   128  // 'tryingState' will be set before invoking callback. If callback succeeded 'successState' will be set, otherwise
   129  // error details will be recorded in meta store.
   130  func (p *PoolDevice) transition(ctx context.Context, deviceName string, tryingState DeviceState, successState DeviceState, updateStateFn func() error) error {
   131  	// Set device to trying state
   132  	uerr := p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error {
   133  		deviceInfo.State = tryingState
   134  		return nil
   135  	})
   136  
   137  	if uerr != nil {
   138  		return errors.Wrapf(uerr, "failed to set device %q state to %q", deviceName, tryingState)
   139  	}
   140  
   141  	var result *multierror.Error
   142  
   143  	// Invoke devmapper operation
   144  	err := updateStateFn()
   145  
   146  	if err != nil {
   147  		result = multierror.Append(result, err)
   148  	}
   149  
   150  	// If operation succeeded transition to success state, otherwise save error details
   151  	uerr = p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error {
   152  		if err == nil {
   153  			deviceInfo.State = successState
   154  			deviceInfo.Error = ""
   155  		} else {
   156  			deviceInfo.Error = err.Error()
   157  		}
   158  		return nil
   159  	})
   160  
   161  	if uerr != nil {
   162  		result = multierror.Append(result, uerr)
   163  	}
   164  
   165  	return unwrapError(result)
   166  }
   167  
   168  // unwrapError converts multierror.Error to the original error when it is possible.
   169  // multierror 1.1.0 has the similar function named Unwrap, but it requires Go 1.14.
   170  func unwrapError(e *multierror.Error) error {
   171  	if e == nil {
   172  		return nil
   173  	}
   174  
   175  	// If the error can be expressed without multierror, return the original error.
   176  	if len(e.Errors) == 1 {
   177  		return e.Errors[0]
   178  	}
   179  
   180  	return e.ErrorOrNil()
   181  }
   182  
   183  // CreateThinDevice creates new devmapper thin-device with given name and size.
   184  // Device ID for thin-device will be allocated from metadata store.
   185  // If allocation successful, device will be activated with /dev/mapper/<deviceName>
   186  func (p *PoolDevice) CreateThinDevice(ctx context.Context, deviceName string, virtualSizeBytes uint64) (retErr error) {
   187  	info := &DeviceInfo{
   188  		Name:  deviceName,
   189  		Size:  virtualSizeBytes,
   190  		State: Unknown,
   191  	}
   192  
   193  	var (
   194  		metaErr   error
   195  		devErr    error
   196  		activeErr error
   197  	)
   198  
   199  	defer func() {
   200  		// We've created a devmapper device, but failed to activate it, try rollback everything
   201  		if activeErr != nil {
   202  			retErr = p.rollbackActivate(ctx, info, activeErr)
   203  			return
   204  		}
   205  
   206  		// We're unable to create the devmapper device, most likely something wrong with the deviceID
   207  		if devErr != nil {
   208  			retErr = multierror.Append(retErr, p.metadata.MarkFaulty(ctx, info.Name))
   209  			return
   210  		}
   211  	}()
   212  
   213  	// Save initial device metadata and allocate new device ID from store
   214  	metaErr = p.metadata.AddDevice(ctx, info)
   215  	if metaErr != nil {
   216  		return metaErr
   217  	}
   218  
   219  	// Create thin device
   220  	devErr = p.createDevice(ctx, info)
   221  	if devErr != nil {
   222  		return devErr
   223  	}
   224  
   225  	// Activate thin device
   226  	activeErr = p.activateDevice(ctx, info)
   227  	if activeErr != nil {
   228  		return activeErr
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func (p *PoolDevice) rollbackActivate(ctx context.Context, info *DeviceInfo, activateErr error) error {
   235  	// Delete the device first.
   236  	delErr := p.deleteDevice(ctx, info)
   237  	if delErr != nil {
   238  		// Failed to rollback, mark the device as faulty and keep metadata in order to
   239  		// preserve the faulty device ID
   240  		return multierror.Append(activateErr, delErr, p.metadata.MarkFaulty(ctx, info.Name))
   241  	}
   242  
   243  	// The devmapper device has been successfully deleted, deallocate device ID
   244  	if err := p.RemoveDevice(ctx, info.Name); err != nil {
   245  		return multierror.Append(activateErr, err)
   246  	}
   247  
   248  	return activateErr
   249  }
   250  
   251  // createDevice creates thin device
   252  func (p *PoolDevice) createDevice(ctx context.Context, info *DeviceInfo) error {
   253  	if err := p.transition(ctx, info.Name, Creating, Created, func() error {
   254  		return dmsetup.CreateDevice(p.poolName, info.DeviceID)
   255  	}); err != nil {
   256  		return errors.Wrapf(err, "failed to create new thin device %q (dev: %d)", info.Name, info.DeviceID)
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  // activateDevice activates thin device
   263  func (p *PoolDevice) activateDevice(ctx context.Context, info *DeviceInfo) error {
   264  	if err := p.transition(ctx, info.Name, Activating, Activated, func() error {
   265  		return dmsetup.ActivateDevice(p.poolName, info.Name, info.DeviceID, info.Size, "")
   266  	}); err != nil {
   267  		return errors.Wrapf(err, "failed to activate new thin device %q (dev: %d)", info.Name, info.DeviceID)
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  // CreateSnapshotDevice creates and activates new thin-device from parent thin-device (makes snapshot)
   274  func (p *PoolDevice) CreateSnapshotDevice(ctx context.Context, deviceName string, snapshotName string, virtualSizeBytes uint64) (retErr error) {
   275  	baseInfo, err := p.metadata.GetDevice(ctx, deviceName)
   276  	if err != nil {
   277  		return errors.Wrapf(err, "failed to query device metadata for %q", deviceName)
   278  	}
   279  
   280  	snapInfo := &DeviceInfo{
   281  		Name:       snapshotName,
   282  		Size:       virtualSizeBytes,
   283  		ParentName: deviceName,
   284  		State:      Unknown,
   285  	}
   286  
   287  	var (
   288  		metaErr   error
   289  		devErr    error
   290  		activeErr error
   291  	)
   292  
   293  	defer func() {
   294  		// We've created a devmapper device, but failed to activate it, try rollback everything
   295  		if activeErr != nil {
   296  			retErr = p.rollbackActivate(ctx, snapInfo, activeErr)
   297  			return
   298  		}
   299  
   300  		// We're unable to create the devmapper device, most likely something wrong with the deviceID
   301  		if devErr != nil {
   302  			retErr = multierror.Append(retErr, p.metadata.MarkFaulty(ctx, snapInfo.Name))
   303  			return
   304  		}
   305  	}()
   306  
   307  	// Save snapshot metadata and allocate new device ID
   308  	metaErr = p.metadata.AddDevice(ctx, snapInfo)
   309  	if metaErr != nil {
   310  		return metaErr
   311  	}
   312  
   313  	// The base device must be suspend before taking a snapshot to
   314  	// avoid corruption.
   315  	// https://github.com/torvalds/linux/blob/v5.7/Documentation/admin-guide/device-mapper/thin-provisioning.rst#internal-snapshots
   316  	if p.IsLoaded(deviceName) {
   317  		log.G(ctx).Debugf("suspending %q before taking its snapshot", deviceName)
   318  		suspendErr := p.SuspendDevice(ctx, deviceName)
   319  		if suspendErr != nil {
   320  			return suspendErr
   321  		}
   322  		defer func() {
   323  			err := p.ResumeDevice(ctx, deviceName)
   324  			if err != nil {
   325  				log.G(ctx).WithError(err).Errorf("failed to resume base device %q after taking its snapshot", baseInfo.Name)
   326  			}
   327  		}()
   328  	}
   329  
   330  	// Create thin device snapshot
   331  	devErr = p.createSnapshot(ctx, baseInfo, snapInfo)
   332  	if devErr != nil {
   333  		return devErr
   334  	}
   335  
   336  	// Activate the snapshot device
   337  	activeErr = p.activateDevice(ctx, snapInfo)
   338  	if activeErr != nil {
   339  		return activeErr
   340  	}
   341  
   342  	return nil
   343  }
   344  
   345  func (p *PoolDevice) createSnapshot(ctx context.Context, baseInfo, snapInfo *DeviceInfo) error {
   346  	if err := p.transition(ctx, snapInfo.Name, Creating, Created, func() error {
   347  		return dmsetup.CreateSnapshot(p.poolName, snapInfo.DeviceID, baseInfo.DeviceID)
   348  	}); err != nil {
   349  		return errors.Wrapf(err,
   350  			"failed to create snapshot %q (dev: %d) from %q (dev: %d)",
   351  			snapInfo.Name,
   352  			snapInfo.DeviceID,
   353  			baseInfo.Name,
   354  			baseInfo.DeviceID)
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  // SuspendDevice flushes the outstanding IO and blocks the further IO
   361  func (p *PoolDevice) SuspendDevice(ctx context.Context, deviceName string) error {
   362  	if err := p.transition(ctx, deviceName, Suspending, Suspended, func() error {
   363  		return dmsetup.SuspendDevice(deviceName)
   364  	}); err != nil {
   365  		return errors.Wrapf(err, "failed to suspend device %q", deviceName)
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  func (p *PoolDevice) ResumeDevice(ctx context.Context, deviceName string) error {
   372  	if err := p.transition(ctx, deviceName, Resuming, Resumed, func() error {
   373  		return dmsetup.ResumeDevice(deviceName)
   374  	}); err != nil {
   375  		return errors.Wrapf(err, "failed to resume device %q", deviceName)
   376  	}
   377  
   378  	return nil
   379  }
   380  
   381  // DeactivateDevice deactivates thin device
   382  func (p *PoolDevice) DeactivateDevice(ctx context.Context, deviceName string, deferred, withForce bool) error {
   383  	if !p.IsLoaded(deviceName) {
   384  		return nil
   385  	}
   386  
   387  	opts := []dmsetup.RemoveDeviceOpt{dmsetup.RemoveWithRetries}
   388  	if deferred {
   389  		opts = append(opts, dmsetup.RemoveDeferred)
   390  	}
   391  	if withForce {
   392  		opts = append(opts, dmsetup.RemoveWithForce)
   393  	}
   394  
   395  	if err := p.transition(ctx, deviceName, Deactivating, Deactivated, func() error {
   396  		var (
   397  			maxRetries = 100
   398  			retryDelay = 100 * time.Millisecond
   399  			retryErr   error
   400  		)
   401  
   402  		for attempt := 1; attempt <= maxRetries; attempt++ {
   403  			retryErr = dmsetup.RemoveDevice(deviceName, opts...)
   404  			if retryErr == nil {
   405  				return nil
   406  			} else if retryErr != unix.EBUSY {
   407  				return retryErr
   408  			}
   409  
   410  			// Don't spam logs
   411  			if attempt%10 == 0 {
   412  				log.G(ctx).WithError(retryErr).Warnf("failed to deactivate device, retrying... (%d of %d)", attempt, maxRetries)
   413  			}
   414  
   415  			// Devmapper device is busy, give it a bit of time and retry removal
   416  			time.Sleep(retryDelay)
   417  		}
   418  
   419  		return retryErr
   420  	}); err != nil {
   421  		return errors.Wrapf(err, "failed to deactivate device %q", deviceName)
   422  	}
   423  
   424  	return nil
   425  }
   426  
   427  // IsActivated returns true if thin-device is activated
   428  func (p *PoolDevice) IsActivated(deviceName string) bool {
   429  	infos, err := dmsetup.Info(deviceName)
   430  	if err != nil || len(infos) != 1 {
   431  		// Couldn't query device info, device not active
   432  		return false
   433  	}
   434  
   435  	if devInfo := infos[0]; devInfo.TableLive {
   436  		return true
   437  	}
   438  
   439  	return false
   440  }
   441  
   442  // IsLoaded returns true if thin-device is visible for dmsetup
   443  func (p *PoolDevice) IsLoaded(deviceName string) bool {
   444  	_, err := dmsetup.Info(deviceName)
   445  	return err == nil
   446  }
   447  
   448  // GetUsage reports total size in bytes consumed by a thin-device.
   449  // It relies on the number of used blocks reported by 'dmsetup status'.
   450  // The output looks like:
   451  //  device2: 0 204800 thin 17280 204799
   452  // Where 17280 is the number of used sectors
   453  func (p *PoolDevice) GetUsage(deviceName string) (int64, error) {
   454  	status, err := dmsetup.Status(deviceName)
   455  	if err != nil {
   456  		return 0, errors.Wrapf(err, "can't get status for device %q", deviceName)
   457  	}
   458  
   459  	if len(status.Params) == 0 {
   460  		return 0, errors.Errorf("failed to get the number of used blocks, unexpected output from dmsetup status")
   461  	}
   462  
   463  	count, err := strconv.ParseInt(status.Params[0], 10, 64)
   464  	if err != nil {
   465  		return 0, errors.Wrapf(err, "failed to parse status params: %q", status.Params[0])
   466  	}
   467  
   468  	return count * dmsetup.SectorSize, nil
   469  }
   470  
   471  // RemoveDevice completely wipes out thin device from thin-pool and frees it's device ID
   472  func (p *PoolDevice) RemoveDevice(ctx context.Context, deviceName string) error {
   473  	info, err := p.metadata.GetDevice(ctx, deviceName)
   474  	if err != nil {
   475  		return errors.Wrapf(err, "can't query metadata for device %q", deviceName)
   476  	}
   477  
   478  	if err := p.DeactivateDevice(ctx, deviceName, false, true); err != nil {
   479  		return err
   480  	}
   481  
   482  	if err := p.deleteDevice(ctx, info); err != nil {
   483  		return err
   484  	}
   485  
   486  	// Remove record from meta store and free device ID
   487  	if err := p.metadata.RemoveDevice(ctx, deviceName); err != nil {
   488  		return errors.Wrapf(err, "can't remove device %q metadata from store after removal", deviceName)
   489  	}
   490  
   491  	return nil
   492  }
   493  
   494  func (p *PoolDevice) deleteDevice(ctx context.Context, info *DeviceInfo) error {
   495  	if err := p.transition(ctx, info.Name, Removing, Removed, func() error {
   496  		// Send 'delete' message to thin-pool
   497  		e := dmsetup.DeleteDevice(p.poolName, info.DeviceID)
   498  
   499  		// Ignores the error if the device has been deleted already.
   500  		if e != nil && !errors.Is(e, unix.ENODATA) {
   501  			return e
   502  		}
   503  		return nil
   504  	}); err != nil {
   505  		return errors.Wrapf(err, "failed to delete device %q (dev id: %d)", info.Name, info.DeviceID)
   506  	}
   507  
   508  	return nil
   509  }
   510  
   511  // RemovePool deactivates all child thin-devices and removes thin-pool device
   512  func (p *PoolDevice) RemovePool(ctx context.Context) error {
   513  	deviceNames, err := p.metadata.GetDeviceNames(ctx)
   514  	if err != nil {
   515  		return errors.Wrap(err, "can't query device names")
   516  	}
   517  
   518  	var result *multierror.Error
   519  
   520  	// Deactivate devices if any
   521  	for _, name := range deviceNames {
   522  		if err := p.DeactivateDevice(ctx, name, true, true); err != nil {
   523  			result = multierror.Append(result, errors.Wrapf(err, "failed to remove %q", name))
   524  		}
   525  	}
   526  
   527  	if err := dmsetup.RemoveDevice(p.poolName, dmsetup.RemoveWithForce, dmsetup.RemoveWithRetries, dmsetup.RemoveDeferred); err != nil {
   528  		result = multierror.Append(result, errors.Wrapf(err, "failed to remove pool %q", p.poolName))
   529  	}
   530  
   531  	return result.ErrorOrNil()
   532  }
   533  
   534  // MarkDeviceState changes the device's state in metastore
   535  func (p *PoolDevice) MarkDeviceState(ctx context.Context, name string, state DeviceState) error {
   536  	return p.metadata.ChangeDeviceState(ctx, name, state)
   537  }
   538  
   539  // WalkDevices iterates all devices in pool metadata
   540  func (p *PoolDevice) WalkDevices(ctx context.Context, cb func(info *DeviceInfo) error) error {
   541  	return p.metadata.WalkDevices(ctx, func(info *DeviceInfo) error {
   542  		return cb(info)
   543  	})
   544  }
   545  
   546  // Close closes pool device (thin-pool will not be removed)
   547  func (p *PoolDevice) Close() error {
   548  	return p.metadata.Close()
   549  }