diff --git a/cinder/tests/unit/volume/drivers/test_rbd.py b/cinder/tests/unit/volume/drivers/test_rbd.py index f80f165ef66..83399c4954f 100644 --- a/cinder/tests/unit/volume/drivers/test_rbd.py +++ b/cinder/tests/unit/volume/drivers/test_rbd.py @@ -2006,6 +2006,63 @@ class RBDTestCase(test.TestCase): # Make sure the exception was raised self.assertEqual([self.mock_rbd.ImageExists], RAISED_EXCEPTIONS) + @common_mocks + def test_get_manageable_snapshots(self): + cinder_snaps = [{'id': '00000000-0000-0000-0000-000000000000', + 'volume_id': '11111111-1111-1111-1111-111111111111'}] + vols = ['volume-11111111-1111-1111-1111-111111111111', 'vol1'] + self.mock_rbd.RBD.return_value.list.return_value = vols + image = self.mock_proxy.return_value.__enter__.return_value + image.list_snaps.side_effect = [ + [{'id': 1, 'name': 'snapshot-00000000-0000-0000-0000-000000000000', + 'size': 2 * units.Gi}, + {'id': 2, 'name': 'snap1', 'size': 6 * units.Gi}, + {'id': 3, 'size': 8 * units.Gi, + 'name': 'volume-22222222-2222-2222-2222-222222222222.clone_snap' + }, + {'id': 4, 'size': 5 * units.Gi, + 'name': 'backup.33333333-3333-3333-3333-333333333333.snap.123'}], + [{'id': 1, 'name': 'snap2', 'size': 4 * units.Gi}]] + res = self.driver.get_manageable_snapshots( + cinder_snaps, None, 1000, 0, ['size'], ['desc']) + exp = [ + {'size': 8, 'safe_to_manage': False, 'extra_info': None, + 'reason_not_safe': 'used for clone snap', 'cinder_id': None, + 'reference': { + 'source-name': + 'volume-22222222-2222-2222-2222-222222222222.clone_snap'}, + 'source_reference': { + 'source-name': 'volume-11111111-1111-1111-1111-111111111111'} + }, + {'size': 6, 'safe_to_manage': True, 'extra_info': None, + 'reason_not_safe': None, 'cinder_id': None, + 'reference': {'source-name': 'snap1'}, + 'source_reference': { + 'source-name': 'volume-11111111-1111-1111-1111-111111111111'} + }, + {'size': 5, 'safe_to_manage': False, 'extra_info': None, + 'reason_not_safe': 'used for volume backup', 'cinder_id': None, + 'reference': { + 'source-name': + 'backup.33333333-3333-3333-3333-333333333333.snap.123'}, + 'source_reference': { + 'source-name': 'volume-11111111-1111-1111-1111-111111111111'} + }, + {'size': 4, 'safe_to_manage': True, 'extra_info': None, + 'reason_not_safe': None, 'cinder_id': None, + 'reference': {'source-name': 'snap2'}, + 'source_reference': {'source-name': 'vol1'} + }, + {'size': 2, 'safe_to_manage': False, 'extra_info': None, + 'reason_not_safe': 'already managed', + 'cinder_id': '00000000-0000-0000-0000-000000000000', + 'reference': {'source-name': + 'snapshot-00000000-0000-0000-0000-000000000000'}, + 'source_reference': { + 'source-name': 'volume-11111111-1111-1111-1111-111111111111'} + }] + self.assertEqual(exp, res) + @common_mocks def test_unmanage_snapshot(self): proxy = self.mock_proxy.return_value diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py index ec1ef53c4ee..fa1bd5dab85 100644 --- a/cinder/volume/drivers/rbd.py +++ b/cinder/volume/drivers/rbd.py @@ -1770,6 +1770,57 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD, if not volume.is_protected_snap(snapshot.name): volume.protect_snap(snapshot.name) + def get_manageable_snapshots(self, cinder_snapshots, marker, limit, offset, + sort_keys, sort_dirs): + """List manageable snapshots on RBD backend.""" + manageable_snapshots = [] + cinder_snapshot_ids = [resource['id'] for resource in cinder_snapshots] + + with RADOSClient(self) as client: + for image_name in self.RBDProxy().list(client.ioctx): + with RBDVolumeProxy(self, image_name, read_only=True, + client=client.cluster, + ioctx=client.ioctx) as image: + try: + for snapshot in image.list_snaps(): + snapshot_id = ( + volume_utils.extract_id_from_snapshot_name( + snapshot['name'])) + snapshot_info = { + 'reference': {'source-name': snapshot['name']}, + 'size': int(math.ceil( + float(snapshot['size']) / units.Gi)), + 'cinder_id': None, + 'extra_info': None, + 'safe_to_manage': False, + 'reason_not_safe': None, + 'source_reference': {'source-name': image_name} + } + + if snapshot_id in cinder_snapshot_ids: + # Exclude snapshots already managed. + snapshot_info['reason_not_safe'] = ( + 'already managed') + snapshot_info['cinder_id'] = snapshot_id + elif snapshot['name'].endswith('.clone_snap'): + # Exclude clone snapshot. + snapshot_info['reason_not_safe'] = ( + 'used for clone snap') + elif (snapshot['name'].startswith('backup') + and '.snap.' in snapshot['name']): + # Exclude intermediate snapshots created by the + # Ceph backup driver. + snapshot_info['reason_not_safe'] = ( + 'used for volume backup') + else: + snapshot_info['safe_to_manage'] = True + manageable_snapshots.append(snapshot_info) + except self.rbd.ImageNotFound: + LOG.debug("Image %s is not found.", image_name) + + return volume_utils.paginate_entries_list( + manageable_snapshots, marker, limit, offset, sort_keys, sort_dirs) + def unmanage_snapshot(self, snapshot): """Removes the specified snapshot from Cinder management.""" with RBDVolumeProxy(self, snapshot.volume_name) as volume: diff --git a/releasenotes/notes/rbd-support-list-manageable-snapshots-3474c62ed83fb788.yaml b/releasenotes/notes/rbd-support-list-manageable-snapshots-3474c62ed83fb788.yaml new file mode 100644 index 00000000000..5cece82febe --- /dev/null +++ b/releasenotes/notes/rbd-support-list-manageable-snapshots-3474c62ed83fb788.yaml @@ -0,0 +1,3 @@ +--- +features: + - Allow rbd driver to list manageable snapshots.