github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 errors.Wrapf(ErrAlreadyExists, "device %q is already there %+v", info.Name, existing)
   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  // ChangeDeviceState changes the device state given the device name in devices bucket.
   126  func (m *PoolMetadata) ChangeDeviceState(ctx context.Context, name string, state DeviceState) error {
   127  	return m.UpdateDevice(ctx, name, func(deviceInfo *DeviceInfo) error {
   128  		deviceInfo.State = state
   129  		return nil
   130  	})
   131  }
   132  
   133  // MarkFaulty marks the given device and corresponding devmapper device ID as faulty.
   134  // The snapshotter might attempt to recreate a device in 'Faulty' state with another devmapper ID in
   135  // subsequent calls, and in case of success it's status will be changed to 'Created' or 'Activated'.
   136  // The devmapper dev ID will remain in 'deviceFaulty' state until manually handled by a user.
   137  func (m *PoolMetadata) MarkFaulty(ctx context.Context, name string) error {
   138  	return m.db.Update(func(tx *bolt.Tx) error {
   139  		var (
   140  			device    = DeviceInfo{}
   141  			devBucket = tx.Bucket(devicesBucketName)
   142  		)
   143  
   144  		if err := getObject(devBucket, name, &device); err != nil {
   145  			return err
   146  		}
   147  
   148  		device.State = Faulty
   149  
   150  		if err := putObject(devBucket, name, &device, true); err != nil {
   151  			return err
   152  		}
   153  
   154  		return markDeviceID(tx, device.DeviceID, deviceFaulty)
   155  	})
   156  }
   157  
   158  // getNextDeviceID finds the next free device ID by taking a cursor
   159  // through the deviceIDBucketName bucket and finding the next sequentially
   160  // unassigned ID. Device ID state is marked by a byte deviceFree or
   161  // deviceTaken. Low device IDs will be reused sooner.
   162  func getNextDeviceID(tx *bolt.Tx) (uint32, error) {
   163  	bucket := tx.Bucket(deviceIDBucketName)
   164  	cursor := bucket.Cursor()
   165  
   166  	// Check if any device id can be reused.
   167  	// Bolt stores its keys in byte-sorted order within a bucket.
   168  	// This makes sequential iteration extremely fast.
   169  	for key, taken := cursor.First(); key != nil; key, taken = cursor.Next() {
   170  		isFree := taken[0] == byte(deviceFree)
   171  		if !isFree {
   172  			continue
   173  		}
   174  
   175  		parsedID, err := strconv.ParseUint(string(key), 10, 32)
   176  		if err != nil {
   177  			return 0, err
   178  		}
   179  
   180  		id := uint32(parsedID)
   181  		if err := markDeviceID(tx, id, deviceTaken); err != nil {
   182  			return 0, err
   183  		}
   184  
   185  		return id, nil
   186  	}
   187  
   188  	// Try allocate new device ID
   189  	seq, err := bucket.NextSequence()
   190  	if err != nil {
   191  		return 0, err
   192  	}
   193  
   194  	if seq >= maxDeviceID {
   195  		return 0, errors.Errorf("dm-meta: couldn't find free device key")
   196  	}
   197  
   198  	id := uint32(seq)
   199  	if err := markDeviceID(tx, id, deviceTaken); err != nil {
   200  		return 0, err
   201  	}
   202  
   203  	return id, nil
   204  }
   205  
   206  // markDeviceID marks a device as deviceFree or deviceTaken
   207  func markDeviceID(tx *bolt.Tx, deviceID uint32, state deviceIDState) error {
   208  	var (
   209  		bucket = tx.Bucket(deviceIDBucketName)
   210  		key    = strconv.FormatUint(uint64(deviceID), 10)
   211  		value  = []byte{byte(state)}
   212  	)
   213  
   214  	if err := bucket.Put([]byte(key), value); err != nil {
   215  		return errors.Wrapf(err, "failed to free device id %q", key)
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  // UpdateDevice updates device info in metadata store.
   222  // The callback should be used to indicate whether device info update was successful or not.
   223  // An error returned from the callback will rollback the update transaction in the database.
   224  // Name and Device ID are not allowed to change.
   225  func (m *PoolMetadata) UpdateDevice(ctx context.Context, name string, fn DeviceInfoCallback) error {
   226  	return m.db.Update(func(tx *bolt.Tx) error {
   227  		var (
   228  			device = &DeviceInfo{}
   229  			bucket = tx.Bucket(devicesBucketName)
   230  		)
   231  
   232  		if err := getObject(bucket, name, device); err != nil {
   233  			return err
   234  		}
   235  
   236  		// Don't allow changing these values, keep things in sync with devmapper
   237  		name := device.Name
   238  		devID := device.DeviceID
   239  
   240  		if err := fn(device); err != nil {
   241  			return err
   242  		}
   243  
   244  		if name != device.Name {
   245  			return fmt.Errorf("failed to update device info, name didn't match: %q %q", name, device.Name)
   246  		}
   247  
   248  		if devID != device.DeviceID {
   249  			return fmt.Errorf("failed to update device info, device id didn't match: %d %d", devID, device.DeviceID)
   250  		}
   251  
   252  		return putObject(bucket, name, device, true)
   253  	})
   254  }
   255  
   256  // GetDevice retrieves device info by name from database
   257  func (m *PoolMetadata) GetDevice(ctx context.Context, name string) (*DeviceInfo, error) {
   258  	var (
   259  		dev DeviceInfo
   260  		err error
   261  	)
   262  
   263  	err = m.db.View(func(tx *bolt.Tx) error {
   264  		bucket := tx.Bucket(devicesBucketName)
   265  		return getObject(bucket, name, &dev)
   266  	})
   267  
   268  	return &dev, err
   269  }
   270  
   271  // RemoveDevice removes device info from store.
   272  func (m *PoolMetadata) RemoveDevice(ctx context.Context, name string) error {
   273  	return m.db.Update(func(tx *bolt.Tx) error {
   274  		var (
   275  			device = &DeviceInfo{}
   276  			bucket = tx.Bucket(devicesBucketName)
   277  		)
   278  
   279  		if err := getObject(bucket, name, device); err != nil {
   280  			return err
   281  		}
   282  
   283  		if err := bucket.Delete([]byte(name)); err != nil {
   284  			return errors.Wrapf(err, "failed to delete device info for %q", name)
   285  		}
   286  
   287  		return markDeviceID(tx, device.DeviceID, deviceFree)
   288  	})
   289  }
   290  
   291  // WalkDevices walks all devmapper devices in metadata store and invokes the callback with device info.
   292  // The provided callback function must not modify the bucket.
   293  func (m *PoolMetadata) WalkDevices(ctx context.Context, cb func(info *DeviceInfo) error) error {
   294  	return m.db.View(func(tx *bolt.Tx) error {
   295  		bucket := tx.Bucket(devicesBucketName)
   296  		return bucket.ForEach(func(key, value []byte) error {
   297  			device := &DeviceInfo{}
   298  			if err := json.Unmarshal(value, device); err != nil {
   299  				return errors.Wrapf(err, "failed to unmarshal %s", key)
   300  			}
   301  
   302  			return cb(device)
   303  		})
   304  	})
   305  }
   306  
   307  // GetDeviceNames retrieves the list of device names currently stored in database
   308  func (m *PoolMetadata) GetDeviceNames(ctx context.Context) ([]string, error) {
   309  	var (
   310  		names []string
   311  		err   error
   312  	)
   313  
   314  	err = m.db.View(func(tx *bolt.Tx) error {
   315  		bucket := tx.Bucket(devicesBucketName)
   316  		return bucket.ForEach(func(k, _ []byte) error {
   317  			names = append(names, string(k))
   318  			return nil
   319  		})
   320  	})
   321  
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	return names, nil
   327  }
   328  
   329  // Close closes metadata store
   330  func (m *PoolMetadata) Close() error {
   331  	if err := m.db.Close(); err != nil && err != bolt.ErrDatabaseNotOpen {
   332  		return err
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  func putObject(bucket *bolt.Bucket, key string, obj interface{}, overwrite bool) error {
   339  	keyBytes := []byte(key)
   340  
   341  	if !overwrite && bucket.Get(keyBytes) != nil {
   342  		return errors.Errorf("object with key %q already exists", key)
   343  	}
   344  
   345  	data, err := json.Marshal(obj)
   346  	if err != nil {
   347  		return errors.Wrapf(err, "failed to marshal object with key %q", key)
   348  	}
   349  
   350  	if err := bucket.Put(keyBytes, data); err != nil {
   351  		return errors.Wrapf(err, "failed to insert object with key %q", key)
   352  	}
   353  
   354  	return nil
   355  }
   356  
   357  func getObject(bucket *bolt.Bucket, key string, obj interface{}) error {
   358  	data := bucket.Get([]byte(key))
   359  	if data == nil {
   360  		return ErrNotFound
   361  	}
   362  
   363  	if obj != nil {
   364  		if err := json.Unmarshal(data, obj); err != nil {
   365  			return errors.Wrapf(err, "failed to unmarshal object with key %q", key)
   366  		}
   367  	}
   368  
   369  	return nil
   370  }