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