Skip to content

Commit

Permalink
Merge pull request #4496 from kzys/backport-1.4-4437
Browse files Browse the repository at this point in the history
[release/1.4 backport] snapshots/devmapper: fix rollback
  • Loading branch information
estesp committed Aug 24, 2020
2 parents 09814d4 + 23e0ea2 commit 94c8bd9
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 33 deletions.
2 changes: 1 addition & 1 deletion snapshots/devmapper/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (m *PoolMetadata) AddDevice(ctx context.Context, info *DeviceInfo) error {
// 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
return errors.Wrapf(ErrAlreadyExists, "device %q is already there %+v", info.Name, existing)
}

// Find next available device ID
Expand Down
74 changes: 42 additions & 32 deletions snapshots/devmapper/pool_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,22 @@ func (p *PoolDevice) transition(ctx context.Context, deviceName string, tryingSt
result = multierror.Append(result, uerr)
}

return result.ErrorOrNil()
return unwrapError(result)
}

// unwrapError converts multierror.Error to the original error when it is possible.
// multierror 1.1.0 has the similar function named Unwrap, but it requires Go 1.14.
func unwrapError(e *multierror.Error) error {
if e == nil {
return nil
}

// If the error can be expressed without multierror, return the original error.
if len(e.Errors) == 1 {
return e.Errors[0]
}

return e.ErrorOrNil()
}

// CreateThinDevice creates new devmapper thin-device with given name and size.
Expand All @@ -184,21 +199,7 @@ func (p *PoolDevice) CreateThinDevice(ctx context.Context, deviceName string, vi
defer func() {
// We've created a devmapper device, but failed to activate it, try rollback everything
if activeErr != nil {
// Delete the device first.
delErr := p.deleteDevice(ctx, info)
if delErr != nil {
// Failed to rollback, mark the device as faulty and keep metadata in order to
// preserve the faulty device ID
retErr = multierror.Append(retErr, delErr, p.metadata.MarkFaulty(ctx, info.Name))
return
}

// The devmapper device has been successfully deleted, deallocate device ID
if err := p.RemoveDevice(ctx, info.Name); err != nil {
retErr = multierror.Append(retErr, err)
return
}

retErr = p.rollbackActivate(ctx, info, activeErr)
return
}

Expand Down Expand Up @@ -230,6 +231,23 @@ func (p *PoolDevice) CreateThinDevice(ctx context.Context, deviceName string, vi
return nil
}

func (p *PoolDevice) rollbackActivate(ctx context.Context, info *DeviceInfo, activateErr error) error {
// Delete the device first.
delErr := p.deleteDevice(ctx, info)
if delErr != nil {
// Failed to rollback, mark the device as faulty and keep metadata in order to
// preserve the faulty device ID
return multierror.Append(activateErr, delErr, p.metadata.MarkFaulty(ctx, info.Name))
}

// The devmapper device has been successfully deleted, deallocate device ID
if err := p.RemoveDevice(ctx, info.Name); err != nil {
return multierror.Append(activateErr, err)
}

return activateErr
}

// createDevice creates thin device
func (p *PoolDevice) createDevice(ctx context.Context, info *DeviceInfo) error {
if err := p.transition(ctx, info.Name, Creating, Created, func() error {
Expand Down Expand Up @@ -275,21 +293,7 @@ func (p *PoolDevice) CreateSnapshotDevice(ctx context.Context, deviceName string
defer func() {
// We've created a devmapper device, but failed to activate it, try rollback everything
if activeErr != nil {
// Delete the device first.
delErr := p.deleteDevice(ctx, snapInfo)
if delErr != nil {
// Failed to rollback, mark the device as faulty and keep metadata in order to
// preserve the faulty device ID
retErr = multierror.Append(retErr, delErr, p.metadata.MarkFaulty(ctx, snapInfo.Name))
return
}

// The devmapper device has been successfully deleted, deallocate device ID
if err := p.RemoveDevice(ctx, snapInfo.Name); err != nil {
retErr = multierror.Append(retErr, err)
return
}

retErr = p.rollbackActivate(ctx, snapInfo, activeErr)
return
}

Expand Down Expand Up @@ -490,7 +494,13 @@ func (p *PoolDevice) RemoveDevice(ctx context.Context, deviceName string) error
func (p *PoolDevice) deleteDevice(ctx context.Context, info *DeviceInfo) error {
if err := p.transition(ctx, info.Name, Removing, Removed, func() error {
// Send 'delete' message to thin-pool
return dmsetup.DeleteDevice(p.poolName, info.DeviceID)
e := dmsetup.DeleteDevice(p.poolName, info.DeviceID)

// Ignores the error if the device has been deleted already.
if e != nil && !errors.Is(e, unix.ENODATA) {
return e
}
return nil
}); err != nil {
return errors.Wrapf(err, "failed to delete device %q (dev id: %d)", info.Name, info.DeviceID)
}
Expand Down
24 changes: 24 additions & 0 deletions snapshots/devmapper/pool_device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,27 @@ func TestPoolDevice(t *testing.T) {
t.Run("RemoveDevice", func(t *testing.T) {
testRemoveThinDevice(t, pool)
})

t.Run("rollbackActivate", func(t *testing.T) {
testCreateThinDevice(t, pool)

ctx := context.Background()

snapDevice := "snap2"

err := pool.CreateSnapshotDevice(ctx, thinDevice1, snapDevice, device1Size)
assert.NilError(t, err)

info, err := pool.metadata.GetDevice(ctx, snapDevice)
assert.NilError(t, err)

// Simulate a case that the device cannot be activated.
err = pool.DeactivateDevice(ctx, info.Name, false, false)
assert.NilError(t, err)

err = pool.rollbackActivate(ctx, info, err)
assert.NilError(t, err)
})
}

func TestPoolDeviceMarkFaulty(t *testing.T) {
Expand Down Expand Up @@ -256,6 +277,9 @@ func testDeactivateThinDevice(t *testing.T, pool *PoolDevice) {
func testRemoveThinDevice(t *testing.T, pool *PoolDevice) {
err := pool.RemoveDevice(testCtx, thinDevice1)
assert.NilError(t, err, "should delete thin device from pool")

err = pool.RemoveDevice(testCtx, thinDevice2)
assert.NilError(t, err, "should delete thin device from pool")
}

func getMounts(thinDeviceName string) []mount.Mount {
Expand Down

0 comments on commit 94c8bd9

Please sign in to comment.