github.com/demonoid81/containerd@v1.3.4/snapshots/devmapper/metadata.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  	"encoding/json"
    24  	"fmt"
    25  	"strconv"
    26  
    27  	"github.com/pkg/errors"
    28  	bolt "go.etcd.io/bbolt"
    29  )
    30  
    31  type (
    32  	// DeviceInfoCallback is a callback used for device updates
    33  	DeviceInfoCallback func(deviceInfo *DeviceInfo) error
    34  )
    35  
    36  type deviceIDState byte
    37  
    38  const (
    39  	deviceFree deviceIDState = iota
    40  	deviceTaken
    41  	deviceFaulty
    42  )
    43  
    44  // Bucket names
    45  var (
    46  	devicesBucketName  = []byte("devices")    // Contains thin devices metadata <device_name>=<DeviceInfo>
    47  	deviceIDBucketName = []byte("device_ids") // Tracks used device ids <device_id_[0..maxDeviceID)>=<byte_[0/1]>
    48  )
    49  
    50  var (
    51  	// ErrNotFound represents an error returned when object not found in meta store
    52  	ErrNotFound = errors.New("not found")
    53  	// ErrAlreadyExists represents an error returned when object can't be duplicated in meta store
    54  	ErrAlreadyExists = errors.New("object already exists")
    55  )
    56  
    57  // PoolMetadata keeps device info for the given thin-pool device, it also responsible for
    58  // generating next available device ids and tracking devmapper transaction numbers
    59  type PoolMetadata struct {
    60  	db *bolt.DB
    61  }
    62  
    63  // NewPoolMetadata creates new or open existing pool metadata database
    64  func NewPoolMetadata(dbfile string) (*PoolMetadata, error) {
    65  	db, err := bolt.Open(dbfile, 0600, nil)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	metadata := &PoolMetadata{db: db}
    71  	if err := metadata.ensureDatabaseInitialized(); err != nil {
    72  		return nil, errors.Wrap(err, "failed to initialize database")
    73  	}
    74  
    75  	return metadata, nil
    76  }
    77  
    78  // ensureDatabaseInitialized creates buckets required for metadata store in order
    79  // to avoid bucket existence checks across the code
    80  func (m *PoolMetadata) ensureDatabaseInitialized() error {
    81  	return m.db.Update(func(tx *bolt.Tx) error {
    82  		if _, err := tx.CreateBucketIfNotExists(devicesBucketName); err != nil {
    83  			return err
    84  		}
    85  
    86  		if _, err := tx.CreateBucketIfNotExists(deviceIDBucketName); err != nil {
    87  			return err
    88  		}
    89  
    90  		return nil
    91  	})
    92  }
    93  
    94  // AddDevice saves device info to database.
    95  func (m *PoolMetadata) AddDevice(ctx context.Context, info *DeviceInfo) error {
    96  	err := m.db.Update(func(tx *bolt.Tx) error {
    97  		devicesBucket := tx.Bucket(devicesBucketName)
    98  
    99  		// Make sure device name is unique. If there is already a device with the same name,
   100  		// but in Faulty state, give it a try with another devmapper device ID.
   101  		// See https://github.com/containerd/containerd/pull/3436 for more context.
   102  		var existing DeviceInfo
   103  		if err := getObject(devicesBucket, info.Name, &existing); err == nil && existing.State != Faulty {
   104  			return ErrAlreadyExists
   105  		}
   106  
   107  		// Find next available device ID
   108  		deviceID, err := getNextDeviceID(tx)
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  		info.DeviceID = deviceID
   114  
   115  		return putObject(devicesBucket, info.Name, info, true)
   116  	})
   117  
   118  	if err != nil {
   119  		return errors.Wrapf(err, "failed to save metadata for device %q (parent: %q)", info.Name, info.ParentName)
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // MarkFaulty marks the given device and corresponding devmapper device ID as faulty.
   126  // The snapshotter might attempt to recreate a device in 'Faulty' state with another devmapper ID in
   127  // subsequent calls, and in case of success it's status will be changed to 'Created' or 'Activated'.
   128  // The devmapper dev ID will remain in 'deviceFaulty' state until manually handled by a user.
   129  func (m *PoolMetadata) MarkFaulty(ctx context.Context, name string) error {
   130  	return m.db.Update(func(tx *bolt.Tx) error {
   131  		var (
   132  			device    = DeviceInfo{}
   133  			devBucket = tx.Bucket(devicesBucketName)
   134  		)
   135  
   136  		if err := getObject(devBucket, name, &device); err != nil {
   137  			return err
   138  		}
   139  
   140  		device.State = Faulty
   141  
   142  		if err := putObject(devBucket, name, &device, true); err != nil {
   143  			return err
   144  		}
   145  
   146  		return markDeviceID(tx, device.DeviceID, deviceFaulty)
   147  	})
   148  }
   149  
   150  // getNextDeviceID finds the next free device ID by taking a cursor
   151  // through the deviceIDBucketName bucket and finding the next sequentially
   152  // unassigned ID. Device ID state is marked by a byte deviceFree or
   153  // deviceTaken. Low device IDs will be reused sooner.
   154  func getNextDeviceID(tx *bolt.Tx) (uint32, error) {
   155  	bucket := tx.Bucket(deviceIDBucketName)
   156  	cursor := bucket.Cursor()
   157  
   158  	// Check if any device id can be reused.
   159  	// Bolt stores its keys in byte-sorted order within a bucket.
   160  	// This makes sequential iteration extremely fast.
   161  	for key, taken := cursor.First(); key != nil; key, taken = cursor.Next() {
   162  		isFree := taken[0] == byte(deviceFree)
   163  		if !isFree {
   164  			continue
   165  		}
   166  
   167  		parsedID, err := strconv.ParseUint(string(key), 10, 32)
   168  		if err != nil {
   169  			return 0, err
   170  		}
   171  
   172  		id := uint32(parsedID)
   173  		if err := markDeviceID(tx, id, deviceTaken); err != nil {
   174  			return 0, err
   175  		}
   176  
   177  		return id, nil
   178  	}
   179  
   180  	// Try allocate new device ID
   181  	seq, err := bucket.NextSequence()
   182  	if err != nil {
   183  		return 0, err
   184  	}
   185  
   186  	if seq >= maxDeviceID {
   187  		return 0, errors.Errorf("dm-meta: couldn't find free device key")
   188  	}
   189  
   190  	id := uint32(seq)
   191  	if err := markDeviceID(tx, id, deviceTaken); err != nil {
   192  		return 0, err
   193  	}
   194  
   195  	return id, nil
   196  }
   197  
   198  // markDeviceID marks a device as deviceFree or deviceTaken
   199  func markDeviceID(tx *bolt.Tx, deviceID uint32, state deviceIDState) error {
   200  	var (
   201  		bucket = tx.Bucket(deviceIDBucketName)
   202  		key    = strconv.FormatUint(uint64(deviceID), 10)
   203  		value  = []byte{byte(state)}
   204  	)
   205  
   206  	if err := bucket.Put([]byte(key), value); err != nil {
   207  		return errors.Wrapf(err, "failed to free device id %q", key)
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  // UpdateDevice updates device info in metadata store.
   214  // The callback should be used to indicate whether device info update was successful or not.
   215  // An error returned from the callback will rollback the update transaction in the database.
   216  // Name and Device ID are not allowed to change.
   217  func (m *PoolMetadata) UpdateDevice(ctx context.Context, name string, fn DeviceInfoCallback) error {
   218  	return m.db.Update(func(tx *bolt.Tx) error {
   219  		var (
   220  			device = &DeviceInfo{}
   221  			bucket = tx.Bucket(devicesBucketName)
   222  		)
   223  
   224  		if err := getObject(bucket, name, device); err != nil {
   225  			return err
   226  		}
   227  
   228  		// Don't allow changing these values, keep things in sync with devmapper
   229  		name := device.Name
   230  		devID := device.DeviceID
   231  
   232  		if err := fn(device); err != nil {
   233  			return err
   234  		}
   235  
   236  		if name != device.Name {
   237  			return fmt.Errorf("failed to update device info, name didn't match: %q %q", name, device.Name)
   238  		}
   239  
   240  		if devID != device.DeviceID {
   241  			return fmt.Errorf("failed to update device info, device id didn't match: %d %d", devID, device.DeviceID)
   242  		}
   243  
   244  		return putObject(bucket, name, device, true)
   245  	})
   246  }
   247  
   248  // GetDevice retrieves device info by name from database
   249  func (m *PoolMetadata) GetDevice(ctx context.Context, name string) (*DeviceInfo, error) {
   250  	var (
   251  		dev DeviceInfo
   252  		err error
   253  	)
   254  
   255  	err = m.db.View(func(tx *bolt.Tx) error {
   256  		bucket := tx.Bucket(devicesBucketName)
   257  		return getObject(bucket, name, &dev)
   258  	})
   259  
   260  	return &dev, err
   261  }
   262  
   263  // RemoveDevice removes device info from store.
   264  func (m *PoolMetadata) RemoveDevice(ctx context.Context, name string) error {
   265  	return m.db.Update(func(tx *bolt.Tx) error {
   266  		var (
   267  			device = &DeviceInfo{}
   268  			bucket = tx.Bucket(devicesBucketName)
   269  		)
   270  
   271  		if err := getObject(bucket, name, device); err != nil {
   272  			return err
   273  		}
   274  
   275  		if err := bucket.Delete([]byte(name)); err != nil {
   276  			return errors.Wrapf(err, "failed to delete device info for %q", name)
   277  		}
   278  
   279  		return markDeviceID(tx, device.DeviceID, deviceFree)
   280  	})
   281  }
   282  
   283  // WalkDevices walks all devmapper devices in metadata store and invokes the callback with device info.
   284  // The provided callback function must not modify the bucket.
   285  func (m *PoolMetadata) WalkDevices(ctx context.Context, cb func(info *DeviceInfo) error) error {
   286  	return m.db.View(func(tx *bolt.Tx) error {
   287  		bucket := tx.Bucket(devicesBucketName)
   288  		return bucket.ForEach(func(key, value []byte) error {
   289  			device := &DeviceInfo{}
   290  			if err := json.Unmarshal(value, device); err != nil {
   291  				return errors.Wrapf(err, "failed to unmarshal %s", key)
   292  			}
   293  
   294  			return cb(device)
   295  		})
   296  	})
   297  }
   298  
   299  // GetDeviceNames retrieves the list of device names currently stored in database
   300  func (m *PoolMetadata) GetDeviceNames(ctx context.Context) ([]string, error) {
   301  	var (
   302  		names []string
   303  		err   error
   304  	)
   305  
   306  	err = m.db.View(func(tx *bolt.Tx) error {
   307  		bucket := tx.Bucket(devicesBucketName)
   308  		return bucket.ForEach(func(k, _ []byte) error {
   309  			names = append(names, string(k))
   310  			return nil
   311  		})
   312  	})
   313  
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	return names, nil
   319  }
   320  
   321  // Close closes metadata store
   322  func (m *PoolMetadata) Close() error {
   323  	if err := m.db.Close(); err != nil && err != bolt.ErrDatabaseNotOpen {
   324  		return err
   325  	}
   326  
   327  	return nil
   328  }
   329  
   330  func putObject(bucket *bolt.Bucket, key string, obj interface{}, overwrite bool) error {
   331  	keyBytes := []byte(key)
   332  
   333  	if !overwrite && bucket.Get(keyBytes) != nil {
   334  		return errors.Errorf("object with key %q already exists", key)
   335  	}
   336  
   337  	data, err := json.Marshal(obj)
   338  	if err != nil {
   339  		return errors.Wrapf(err, "failed to marshal object with key %q", key)
   340  	}
   341  
   342  	if err := bucket.Put(keyBytes, data); err != nil {
   343  		return errors.Wrapf(err, "failed to insert object with key %q", key)
   344  	}
   345  
   346  	return nil
   347  }
   348  
   349  func getObject(bucket *bolt.Bucket, key string, obj interface{}) error {
   350  	data := bucket.Get([]byte(key))
   351  	if data == nil {
   352  		return ErrNotFound
   353  	}
   354  
   355  	if obj != nil {
   356  		if err := json.Unmarshal(data, obj); err != nil {
   357  			return errors.Wrapf(err, "failed to unmarshal object with key %q", key)
   358  		}
   359  	}
   360  
   361  	return nil
   362  }