Skip to content

Commit 0a4bf1b

Browse files
committed
Mark faulty devices
Signed-off-by: Maksym Pavlenko <[email protected]>
1 parent 3741fd8 commit 0a4bf1b

4 files changed

Lines changed: 122 additions & 2 deletions

File tree

snapshots/devmapper/metadata.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,22 @@ func (m *PoolMetadata) RemoveDevice(ctx context.Context, name string) error {
280280
})
281281
}
282282

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+
283299
// GetDeviceNames retrieves the list of device names currently stored in database
284300
func (m *PoolMetadata) GetDeviceNames(ctx context.Context) ([]string, error) {
285301
var (

snapshots/devmapper/metadata_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,38 @@ func TestPoolMetadata_MarkFaulty(t *testing.T) {
181181
assert.NilError(t, err)
182182
}
183183

184+
func TestPoolMetadata_WalkDevices(t *testing.T) {
185+
tempDir, store := createStore(t)
186+
defer cleanupStore(t, tempDir, store)
187+
188+
err := store.AddDevice(testCtx, &DeviceInfo{Name: "device1", DeviceID: 1, State: Created})
189+
assert.NilError(t, err)
190+
191+
err = store.AddDevice(testCtx, &DeviceInfo{Name: "device2", DeviceID: 2, State: Faulty})
192+
assert.NilError(t, err)
193+
194+
called := 0
195+
err = store.WalkDevices(testCtx, func(info *DeviceInfo) error {
196+
called++
197+
switch called {
198+
case 1:
199+
assert.Equal(t, "device1", info.Name)
200+
assert.Equal(t, uint32(1), info.DeviceID)
201+
assert.Equal(t, Created, info.State)
202+
case 2:
203+
assert.Equal(t, "device2", info.Name)
204+
assert.Equal(t, uint32(2), info.DeviceID)
205+
assert.Equal(t, Faulty, info.State)
206+
default:
207+
t.Error("unexpected walk call")
208+
}
209+
210+
return nil
211+
})
212+
assert.NilError(t, err)
213+
assert.Equal(t, called, 2)
214+
}
215+
184216
func TestPoolMetadata_GetDeviceNames(t *testing.T) {
185217
tempDir, store := createStore(t)
186218
defer cleanupStore(t, tempDir, store)

snapshots/devmapper/pool_device.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,47 @@ func NewPoolDevice(ctx context.Context, config *Config) (*PoolDevice, error) {
6161
return nil, errors.Wrapf(err, "failed to query pool %q", poolPath)
6262
}
6363

64-
return &PoolDevice{
64+
poolDevice := &PoolDevice{
6565
poolName: config.PoolName,
6666
metadata: poolMetaStore,
67-
}, nil
67+
}
68+
69+
if err := poolDevice.ensureDeviceStates(ctx); err != nil {
70+
return nil, errors.Wrap(err, "failed to check devices state")
71+
}
72+
73+
return poolDevice, nil
74+
}
75+
76+
// ensureDeviceStates marks devices with incomplete states (after crash) as 'Faulty'
77+
func (p *PoolDevice) ensureDeviceStates(ctx context.Context) error {
78+
var devices []*DeviceInfo
79+
80+
if err := p.metadata.WalkDevices(ctx, func(info *DeviceInfo) error {
81+
switch info.State {
82+
case Activated, Suspended, Resumed, Deactivated, Removed, Faulty:
83+
return nil
84+
}
85+
devices = append(devices, info)
86+
return nil
87+
}); err != nil {
88+
return errors.Wrap(err, "failed to query devices from metastore")
89+
}
90+
91+
var result *multierror.Error
92+
for _, dev := range devices {
93+
log.G(ctx).
94+
WithField("dev_id", dev.DeviceID).
95+
WithField("parent", dev.ParentName).
96+
WithField("error", dev.Error).
97+
Warnf("devmapper device %q has invalid state %q, marking as faulty", dev.Name, dev.State)
98+
99+
if err := p.metadata.MarkFaulty(ctx, dev.Name); err != nil {
100+
result = multierror.Append(result, err)
101+
}
102+
}
103+
104+
return multierror.Prefix(result.ErrorOrNil(), "devmapper:")
68105
}
69106

70107
// transition invokes 'updateStateFn' callback to perform devmapper operation and reflects device state changes/errors in meta store.

snapshots/devmapper/pool_device_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,41 @@ func TestPoolDevice(t *testing.T) {
154154
})
155155
}
156156

157+
func TestPoolDeviceMarkFaulty(t *testing.T) {
158+
tempDir, store := createStore(t)
159+
defer cleanupStore(t, tempDir, store)
160+
161+
err := store.AddDevice(testCtx, &DeviceInfo{Name: "1", State: Unknown})
162+
assert.NilError(t, err)
163+
164+
err = store.AddDevice(testCtx, &DeviceInfo{Name: "2", State: Activated})
165+
assert.NilError(t, err)
166+
167+
pool := &PoolDevice{metadata: store}
168+
err = pool.ensureDeviceStates(testCtx)
169+
assert.NilError(t, err)
170+
171+
called := 0
172+
err = pool.metadata.WalkDevices(testCtx, func(info *DeviceInfo) error {
173+
called++
174+
175+
switch called {
176+
case 1:
177+
assert.Equal(t, Faulty, info.State)
178+
assert.Equal(t, "1", info.Name)
179+
case 2:
180+
assert.Equal(t, Activated, info.State)
181+
assert.Equal(t, "2", info.Name)
182+
default:
183+
t.Error("unexpected walk call")
184+
}
185+
186+
return nil
187+
})
188+
assert.NilError(t, err)
189+
assert.Equal(t, 2, called)
190+
}
191+
157192
func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
158193
ctx := context.Background()
159194

0 commit comments

Comments
 (0)