Implement revert_to_snapshot() for StorPool.

Use the StorPool API to revert a volume to the specified snapshot
immediately, without deleting and recreating the volume. This also
preserves the volume's metadata, such as e.g. SCSI WWN identifier for
the purposes of recognizing it on VM instances that it has been
previously attached to.

Change-Id: I3b8126cf1706921eac9556add11c558d973cf272
Implements: blueprint storpool-revert-to-snapshot
This commit is contained in:
Peter Penchev 2020-09-09 21:03:24 +03:00 committed by Kiran Pawar
parent 7382bea3a7
commit 0867c4d3e1
4 changed files with 86 additions and 2 deletions

View File

@ -140,6 +140,16 @@ class MockAPI(object):
volumes[new_name]['name'] = new_name volumes[new_name]['name'] = new_name
del volumes[name] del volumes[name]
def volumeRevert(self, name, data):
if name not in volumes:
raise MockApiError('No such volume {name}'.format(name=name))
snapname = data['toSnapshot']
if snapname not in snapshots:
raise MockApiError('No such snapshot {name}'.format(name=snapname))
volumes[name] = dict(snapshots[snapname])
class MockAttachDB(object): class MockAttachDB(object):
def __init__(self, log): def __init__(self, log):
@ -155,6 +165,10 @@ class MockAttachDB(object):
return snapshotName(vtype, vid) return snapshotName(vtype, vid)
def MockVolumeRevertDesc(toSnapshot):
return {'toSnapshot': toSnapshot}
def MockVolumeUpdateDesc(size): def MockVolumeUpdateDesc(size):
return {'size': size} return {'size': size}
@ -170,6 +184,7 @@ def MockSPConfig(section = 's01'):
fakeStorPool.spapi.ApiError = MockApiError fakeStorPool.spapi.ApiError = MockApiError
fakeStorPool.spconfig.SPConfig = MockSPConfig fakeStorPool.spconfig.SPConfig = MockSPConfig
fakeStorPool.spopenstack.AttachDB = MockAttachDB fakeStorPool.spopenstack.AttachDB = MockAttachDB
fakeStorPool.sptypes.VolumeRevertDesc = MockVolumeRevertDesc
fakeStorPool.sptypes.VolumeUpdateDesc = MockVolumeUpdateDesc fakeStorPool.sptypes.VolumeUpdateDesc = MockVolumeUpdateDesc
@ -524,3 +539,51 @@ class StorPoolTestCase(test.TestCase):
self.driver.get_pool({ self.driver.get_pool({
'volume_type': volume_type 'volume_type': volume_type
})) }))
def test_volume_revert(self):
vol_id = 'rev1'
vol_name = volumeName(vol_id)
snap_id = 'rev-s1'
snap_name = snapshotName('snap', snap_id)
self.assertVolumeNames([])
self.assertDictEqual({}, volumes)
self.assertDictEqual({}, snapshots)
self.driver.create_volume({'id': vol_id, 'name': 'v1', 'size': 1,
'volume_type': None})
self.assertVolumeNames((vol_id,))
self.assertDictEqual({}, snapshots)
self.driver.create_snapshot({'id': snap_id, 'volume_id': vol_id})
self.assertVolumeNames((vol_id,))
self.assertListEqual([snap_name], sorted(snapshots.keys()))
self.assertDictEqual(volumes[vol_name], snapshots[snap_name])
self.assertIsNot(volumes[vol_name], snapshots[snap_name])
self.driver.extend_volume({'id': vol_id}, 2)
self.assertVolumeNames((vol_id,))
self.assertNotEqual(volumes[vol_name], snapshots[snap_name])
self.driver.revert_to_snapshot(None, {'id': vol_id}, {'id': snap_id})
self.assertVolumeNames((vol_id,))
self.assertDictEqual(volumes[vol_name], snapshots[snap_name])
self.assertIsNot(volumes[vol_name], snapshots[snap_name])
self.driver.delete_snapshot({'id': snap_id})
self.assertVolumeNames((vol_id,))
self.assertDictEqual({}, snapshots)
self.assertRaisesRegex(exception.VolumeBackendAPIException,
'No such snapshot',
self.driver.revert_to_snapshot, None,
{'id': vol_id}, {'id': snap_id})
self.driver.delete_volume({'id': vol_id})
self.assertDictEqual({}, volumes)
self.assertDictEqual({}, snapshots)
self.assertRaisesRegex(exception.VolumeBackendAPIException,
'No such volume',
self.driver.revert_to_snapshot, None,
{'id': vol_id}, {'id': snap_id})

View File

@ -90,9 +90,10 @@ class StorPoolDriver(driver.VolumeDriver):
1.2.2 - Reintroduce the driver into OpenStack Queens, 1.2.2 - Reintroduce the driver into OpenStack Queens,
add ignore_errors to the internal _detach_volume() method add ignore_errors to the internal _detach_volume() method
1.2.3 - Advertise some more driver capabilities. 1.2.3 - Advertise some more driver capabilities.
2.0.0 - Implement revert_to_snapshot().
""" """
VERSION = '1.2.3' VERSION = '2.0.0'
CI_WIKI_NAME = 'StorPool_distributed_storage_CI' CI_WIKI_NAME = 'StorPool_distributed_storage_CI'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -423,3 +424,18 @@ class StorPoolDriver(driver.VolumeDriver):
'%(err)s', '%(err)s',
{'tname': temp_name, 'oname': orig_name, 'err': e}) {'tname': temp_name, 'oname': orig_name, 'err': e})
return {'_name_id': new_volume['_name_id'] or new_volume['id']} return {'_name_id': new_volume['_name_id'] or new_volume['id']}
def revert_to_snapshot(self, context, volume, snapshot):
volname = self._attach.volumeName(volume['id'])
snapname = self._attach.snapshotName('snap', snapshot['id'])
try:
rev = sptypes.VolumeRevertDesc(toSnapshot=snapname)
self._attach.api().volumeRevert(volname, rev)
except spapi.ApiError as e:
LOG.error('StorPool revert_to_snapshot(): could not revert '
'the %(vol_id)s volume to the %(snap_id)s snapshot: '
'%(err)s',
{'vol_id': volume['id'],
'snap_id': snapshot['id'],
'err': e})
raise self._backendException(e)

View File

@ -953,7 +953,7 @@ driver.rbd=complete
driver.rbd_iscsi=complete driver.rbd_iscsi=complete
driver.sandstone=complete driver.sandstone=complete
driver.seagate=missing driver.seagate=missing
driver.storpool=missing driver.storpool=complete
driver.synology=missing driver.synology=missing
driver.toyou_netstor=complete driver.toyou_netstor=complete
driver.toyou_netstor_tyds=missing driver.toyou_netstor_tyds=missing

View File

@ -0,0 +1,5 @@
---
features:
- |
StorPool driver: implemented revert to snapshot, which happens
immediately i.e. without deleting and recreating the volume.