github.com/containerd/Containerd@v1.4.13/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 }