diff -Nru cinder-20.3.1/debian/changelog cinder-20.3.1/debian/changelog --- cinder-20.3.1/debian/changelog 2024-01-26 21:26:48.000000000 +0900 +++ cinder-20.3.1/debian/changelog 2024-03-04 12:44:47.000000000 +0900 @@ -1,3 +1,13 @@ +cinder (2:20.3.1-0ubuntu1.2) jammy; urgency=medium + + * HPE3PAR: Failing to clone a volume having children (LP: #1994521): + - d/p/0001-HPE-3PAR-Fix-umanaged-volumes-snapshots-missing.patch + - d/p/0002-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch + - api 4.0.17 is added as it is in the middle of the main patch + (4.0.18) + + -- Seyeong Kim Mon, 04 Mar 2024 12:44:47 +0900 + cinder (2:20.3.1-0ubuntu1.1) jammy; urgency=medium * Revert driver assisted volume retype (LP: #2019190): diff -Nru cinder-20.3.1/debian/patches/0001-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch cinder-20.3.1/debian/patches/0001-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch --- cinder-20.3.1/debian/patches/0001-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch 1970-01-01 09:00:00.000000000 +0900 +++ cinder-20.3.1/debian/patches/0001-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch 2024-03-04 12:38:08.000000000 +0900 @@ -0,0 +1,259 @@ +From 97d5541b8982923ffa994ef308fe608f2e373f7b Mon Sep 17 00:00:00 2001 +From: Rajat Dhasmana +Date: Wed, 26 Oct 2022 09:55:43 +0000 +Subject: [PATCH] 3PAR: Error out if vol cannot be converted to base + +Consider volume and snapshots as below: + +v1 +| +`-- s1 + | + `-- v2 + | + `-- s2 + +User initiated deletion of snapshot s1. +It failed with some vague message. + +Initially, it was suspected that ... +While copying volume v2 (sometimes an intermediate step to break +volume dependency), we send a request to clone the volume v2 to new +base volume; and the exception [1] isn't handled properly. + +[1] Conflict (HTTP 409) 32 - volume has a child + +However, on further investigation it was found that ... +after a new volume v2 (omv-) is created and +when we try to delete old volume v2 (osv-), +at this point the exception [1] is thrown as error. + +This is now handled gracefully. Appropriate error is thrown +if the volume (v2) has snapshot (s2). + +Co-Authored-By: raghavendrat +Closes-Bug: #1994521 +Change-Id: I5e7fb425c92cdf8c16d5a86a58ca1a52421543d7 +(cherry picked from commit dfd8f99743a29220ca3face5fdf00a1a6071cf48) +--- + .../unit/volume/drivers/hpe/test_hpe3par.py | 29 ++++++++++++++++--- + cinder/volume/drivers/hpe/hpe_3par_common.py | 29 +++++++++++++++---- + ...base-vol-delete-snap-a460a4b1c419804a.yaml | 11 +++++++ + 3 files changed, 60 insertions(+), 9 deletions(-) + create mode 100644 releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml + +diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +index 813ea8845..3a2eff148 100644 +--- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py ++++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +@@ -749,10 +749,7 @@ class HPE3PARBaseDriver(test.TestCase): + configuration.unique_fqdn_network = True + return configuration + +- @mock.patch( +- 'hpe3parclient.client.HPE3ParClient', +- spec=True, +- ) ++ @mock.patch('hpe3parclient.client.HPE3ParClient') + def setup_mock_client(self, _m_client, driver, conf=None, m_conf=None, + is_primera=False, + wsapi_version=wsapi_version_latest): +@@ -3401,6 +3398,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client +@@ -3429,6 +3427,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3452,6 +3451,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + _mock_volume_types.return_value = { + 'name': 'gold', + 'extra_specs': { +@@ -3493,6 +3493,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'comment': comment, + 'readOnly': False}), + mock.call.getCPG(HPE3PAR_CPG), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3609,6 +3610,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'getVolume.return_value': {} + } + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + volume_type_hos = copy.deepcopy(self.volume_type_hos) + volume_type_hos['extra_specs']['convert_to_base'] = True + _mock_volume_types.return_value = volume_type_hos +@@ -3638,6 +3640,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3659,6 +3662,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'getVolume.return_value': {} + } + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + _mock_volume_types.return_value = self.volume_type_hos + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: +@@ -3687,6 +3691,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3709,6 +3714,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'getVolume.return_value': {} + } + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + volume_type_hos = copy.deepcopy(self.volume_type_hos) + volume_type_hos['extra_specs']['convert_to_base'] = True + _mock_volume_types.return_value = volume_type_hos +@@ -3739,6 +3745,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3837,6 +3844,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client +@@ -3860,6 +3868,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client +@@ -3871,6 +3880,18 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + self.volume, + str(new_size)) + ++ def test__convert_to_base_volume_failure(self): ++ mock_client = self.setup_driver() ++ mock_client.getVolumeSnapshots.return_value = ( ++ ['oss-nwJVbXaEQMi0w.xPutFRQw']) ++ with mock.patch.object(hpecommon.HPE3PARCommon, ++ '_create_client') as mock_create_client: ++ mock_create_client.return_value = mock_client ++ common = self.driver._login() ++ self.assertRaises(exception.VolumeIsBusy, ++ common._convert_to_base_volume, ++ self.volume) ++ + @mock.patch.object(volume_types, 'get_volume_type') + def test_extend_volume_replicated(self, _mock_volume_types): + # Managed vs. unmanaged and periodic vs. sync are not relevant when +diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py +index 00c347c7e..7fd747e31 100644 +--- a/cinder/volume/drivers/hpe/hpe_3par_common.py ++++ b/cinder/volume/drivers/hpe/hpe_3par_common.py +@@ -300,11 +300,13 @@ class HPE3PARCommon(object): + 4.0.16 - In multi host env, fix multi-detach operation. Bug #1958122 + 4.0.17 - Added get_manageable_volumes and get_manageable_snapshots. + Bug #1819903 ++ 4.0.18 - During conversion of volume to base volume, ++ error out if it has child snapshot(s). Bug #1994521 + + + """ + +- VERSION = "4.0.17" ++ VERSION = "4.0.18" + + stats = {} + +@@ -3139,6 +3141,21 @@ class HPE3PARCommon(object): + + compression = self.get_compression_policy( + type_info['hpe3par_keys']) ++ ++ # If volume (osv-) has snapshot, while converting the volume ++ # to base volume (omv-), snapshot cannot be transferred to ++ # new base volume (omv-) i.e it remain with volume (osv-). ++ # So error out for such volume. ++ snap_list = self.client.getVolumeSnapshots(volume_name) ++ if snap_list: ++ snap_str = ",".join(snap_list) ++ msg = (_("Volume %(name)s has dependent snapshots: %(snap)s." ++ " Either flatten or remove the dependent snapshots:" ++ " %(snap)s for the conversion of volume %(name)s to" ++ " succeed." % {'name': volume_name, ++ 'snap': snap_str})) ++ raise exception.VolumeIsBusy(message=msg) ++ + # Create a physical copy of the volume + task_id = self._copy_volume(volume_name, temp_vol_name, + cpg, cpg, type_info['tpvv'], +@@ -3162,16 +3179,18 @@ class HPE3PARCommon(object): + comment = self._get_3par_vol_comment(volume_name) + if comment: + self.client.modifyVolume(temp_vol_name, {'comment': comment}) +- LOG.debug('Volume rename completed: convert_to_base_volume: ' +- 'id=%s.', volume['id']) ++ LOG.debug('Assigned the comment: convert_to_base_volume: ' ++ 'id=%s.', volume['id']) + +- # Delete source volume after the copy is complete ++ # Delete source volume (osv-) after the copy is complete + self.client.deleteVolume(volume_name) + LOG.debug('Delete src volume completed: convert_to_base_volume: ' + 'id=%s.', volume['id']) + +- # Rename the new volume to the original name ++ # Rename the new volume (omv-) to the original name (osv-) + self.client.modifyVolume(temp_vol_name, {'newName': volume_name}) ++ LOG.debug('Volume rename completed: convert_to_base_volume: ' ++ 'id=%s.', volume['id']) + + LOG.info('Completed: convert_to_base_volume: ' + 'id=%s.', volume['id']) +diff --git a/releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml b/releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml +new file mode 100644 +index 000000000..e087e3353 +--- /dev/null ++++ b/releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml +@@ -0,0 +1,11 @@ ++--- ++fixes: ++ - | ++ HPE 3PAR driver `Bug #1994521 `_: ++ Fixed: While performing a delete snapshot (s1) operation, the volumes (v2) ++ dependent on the snapshot (s1) are converted to base volumes. This ++ operation fails if these dependent volumes (v2) have their own dependent ++ snapshots (s2). The errors during the failure were vague and not helpful. ++ With this release, we added conditions to fail this operation early and ++ also added useful error message. ++ +-- +2.40.1 + diff -Nru cinder-20.3.1/debian/patches/0001-HPE-3PAR-Fix-umanaged-volumes-snapshots-missing.patch cinder-20.3.1/debian/patches/0001-HPE-3PAR-Fix-umanaged-volumes-snapshots-missing.patch --- cinder-20.3.1/debian/patches/0001-HPE-3PAR-Fix-umanaged-volumes-snapshots-missing.patch 1970-01-01 09:00:00.000000000 +0900 +++ cinder-20.3.1/debian/patches/0001-HPE-3PAR-Fix-umanaged-volumes-snapshots-missing.patch 2024-03-04 12:42:42.000000000 +0900 @@ -0,0 +1,330 @@ +From 063609c84fb65975daf1809eac1ace075c0ffc09 Mon Sep 17 00:00:00 2001 +From: raghavendrat +Date: Mon, 25 Jul 2022 06:59:55 +0000 +Subject: [PATCH 1/2] HPE 3PAR: Fix umanaged volumes & snapshots missing + +The umanaged volumes & snapshots are missing when below commands +are executed: +cinder manageable-list +cinder snapshot-manageable-list + +To address this, added two functions: +get_manageable_volumes() +get_manageable_snapshots() + +Closes-Bug: #1819903 +Change-Id: Ie178e48958a69ef2b6c329e132de2c772d5c1a40 +(cherry picked from commit c112542cf5471641a47a94c89b9b80dfa14d701d) +--- + .../unit/volume/drivers/hpe/test_hpe3par.py | 116 ++++++++++++++++++ + cinder/volume/drivers/hpe/hpe_3par_base.py | 14 +++ + cinder/volume/drivers/hpe/hpe_3par_common.py | 103 +++++++++++++++- + ...r-add-get-manageable-2926f21116c98599.yaml | 5 + + 4 files changed, 237 insertions(+), 1 deletion(-) + create mode 100644 releasenotes/notes/hpe-3par-add-get-manageable-2926f21116c98599.yaml + +diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +index bf48ce037..813ea8845 100644 +--- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py ++++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +@@ -26,6 +26,7 @@ from oslo_utils import uuidutils + from cinder import context + from cinder import exception + from cinder.objects import fields ++from cinder.tests.unit import fake_snapshot + from cinder.tests.unit import fake_volume + from cinder.tests.unit import test + from cinder.tests.unit.volume.drivers.hpe \ +@@ -320,6 +321,14 @@ class HPE3PARBaseDriver(test.TestCase): + 'display_description': 'description', + 'volume_name': 'name'} + ++ snapshot_obj = fake_snapshot.fake_snapshot_obj( ++ context.get_admin_context(), ++ name=SNAPSHOT_NAME, ++ id=SNAPSHOT_ID, ++ display_name='Foo Snapshot', ++ volume_size=2, ++ volume_id=VOLUME_ID_SNAP) ++ + wwn = ["123456789012345", "123456789054321"] + + connector = {'ip': '10.0.0.2', +@@ -4844,6 +4853,113 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + self.driver.unmanage_snapshot, + snapshot=snapshot) + ++ def _test_get_manageable(self, cinder_list, expected_output, vol_name, ++ attached=False, snap_name=None): ++ # common test function for: ++ # [a] get_manageable_volumes ++ # [b] get_manageable_snapshots ++ ++ mock_client = self.setup_driver() ++ ++ mock_client.getVolumes.return_value = { ++ 'members': [ ++ {'name': vol_name, ++ 'sizeMiB': 2048, ++ 'userCPG': 'OpenStackCPG'}]} ++ ++ if attached: ++ mock_client.getVLUN.return_value = { ++ 'hostname': 'cssosbe02-b04', ++ } ++ else: ++ mock_client.getVLUN.side_effect = hpeexceptions.HTTPNotFound ++ ++ if snap_name: ++ mock_client.getSnapshotsOfVolume.return_value = [snap_name] ++ ++ with mock.patch.object(hpecommon.HPE3PARCommon, ++ '_create_client') as mock_create_client: ++ mock_create_client.return_value = mock_client ++ ++ common = self.driver._login() ++ if snap_name: ++ actual_output = common.get_manageable_snapshots( ++ cinder_list, None, 1000, 0, ['size'], ['asc']) ++ else: ++ actual_output = self.driver.get_manageable_volumes( ++ cinder_list, None, 1000, 0, ['size'], ['asc']) ++ ++ expected_calls = [] ++ expected_calls.append(mock.call.getVolumes()) ++ if attached: ++ expected_calls.append(mock.call.getVLUN(vol_name)) ++ if snap_name: ++ expected_calls.append( ++ mock.call.getSnapshotsOfVolume('OpenStackCPG', vol_name)) ++ ++ mock_client.assert_has_calls(expected_calls) ++ self.assertEqual(expected_output, actual_output) ++ ++ # (i) volume already managed ++ # (ii) volume currently not managed; but attached to some other host ++ # (iii) volume currently not managed ++ @ddt.data({'cinder_vol': [HPE3PARBaseDriver.volume], ++ 'vol_name': 'osv-0DM4qZEVSKON-DXN-NwVpw', ++ 'safe': False, ++ 'reason': 'Volume already managed', ++ 'cinder_id': 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7'}, ++ {'cinder_vol': [], ++ 'vol_name': 'volume_2', ++ 'safe': False, ++ 'reason': 'Volume attached to host cssosbe02-b04', ++ 'cinder_id': None, ++ 'attached': True}, ++ {'cinder_vol': [], ++ 'vol_name': 'volume_2', ++ 'safe': True, ++ 'reason': None, ++ 'cinder_id': None}) ++ @ddt.unpack ++ def test_get_manageable_volumes(self, cinder_vol, vol_name, safe, reason, ++ cinder_id, attached=False): ++ expected_output = [ ++ {'reference': {'name': vol_name}, ++ 'size': 2, ++ 'safe_to_manage': safe, ++ 'reason_not_safe': reason, ++ 'cinder_id': cinder_id} ++ ] ++ self._test_get_manageable(cinder_vol, expected_output, vol_name, ++ attached) ++ ++ # (i) snapshot already managed ++ # (ii) snapshot currently not managed ++ @ddt.data({'cinder_snapshot': [HPE3PARBaseDriver.snapshot_obj], ++ 'snap_name': 'oss-L4I73ONuTci9Fd4ceij-MQ', ++ 'vol_name': 'osv-CX7Ilh.dQ2.XdNpmqW408A', ++ 'safe': False, ++ 'reason': 'Snapshot already managed', ++ 'cinder_id': '2f823bdc-e36e-4dc8-bd15-de1c7a28ff31'}, ++ {'cinder_snapshot': [], ++ 'snap_name': 'snap_2', ++ 'vol_name': 'volume_2', ++ 'safe': True, ++ 'reason': None, ++ 'cinder_id': None}) ++ @ddt.unpack ++ def test_get_manageable_snapshots(self, cinder_snapshot, snap_name, ++ vol_name, safe, reason, cinder_id): ++ expected_output = [ ++ {'reference': {'name': snap_name}, ++ 'size': 2, ++ 'safe_to_manage': safe, ++ 'reason_not_safe': reason, ++ 'cinder_id': cinder_id, ++ 'source_reference': {'name': vol_name}} ++ ] ++ self._test_get_manageable(cinder_snapshot, expected_output, vol_name, ++ False, snap_name) ++ + @ddt.data(True, False) + def test__safe_hostname(self, in_shared): + config = self._set_unique_fqdn_override(True, in_shared) +diff --git a/cinder/volume/drivers/hpe/hpe_3par_base.py b/cinder/volume/drivers/hpe/hpe_3par_base.py +index aa0ebf379..497fb7f36 100644 +--- a/cinder/volume/drivers/hpe/hpe_3par_base.py ++++ b/cinder/volume/drivers/hpe/hpe_3par_base.py +@@ -236,6 +236,20 @@ class HPE3PARDriverBase(driver.ManageableVD, + def unmanage_snapshot(self, snapshot): + return self.common.unmanage_snapshot(snapshot) + ++ @volume_utils.trace ++ def get_manageable_volumes(self, cinder_volumes, marker, limit, offset, ++ sort_keys, sort_dirs): ++ return self.common.get_manageable_volumes(cinder_volumes, marker, ++ limit, offset, sort_keys, ++ sort_dirs) ++ ++ @volume_utils.trace ++ def get_manageable_snapshots(self, cinder_snapshots, marker, limit, offset, ++ sort_keys, sort_dirs): ++ return self.common.get_manageable_snapshots(cinder_snapshots, marker, ++ limit, offset, sort_keys, ++ sort_dirs) ++ + @volume_utils.trace + def retype(self, context, volume, new_type, diff, host): + """Convert the volume to be of the new type.""" +diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py +index 002f10bb8..00c347c7e 100644 +--- a/cinder/volume/drivers/hpe/hpe_3par_common.py ++++ b/cinder/volume/drivers/hpe/hpe_3par_common.py +@@ -298,11 +298,13 @@ class HPE3PARCommon(object): + 4.0.14 - Added Peer Persistence feature + 4.0.15 - Support duplicated FQDN in network. Bug #1834695 + 4.0.16 - In multi host env, fix multi-detach operation. Bug #1958122 ++ 4.0.17 - Added get_manageable_volumes and get_manageable_snapshots. ++ Bug #1819903 + + + """ + +- VERSION = "4.0.16" ++ VERSION = "4.0.17" + + stats = {} + +@@ -1223,6 +1225,105 @@ class HPE3PARCommon(object): + 'vol': snap_name, + 'new': new_snap_name}) + ++ def get_manageable_volumes(self, cinder_volumes, marker, limit, offset, ++ sort_keys, sort_dirs): ++ already_managed = {} ++ for vol_obj in cinder_volumes: ++ cinder_id = vol_obj.id ++ volume_name = self._get_3par_vol_name(cinder_id) ++ already_managed[volume_name] = cinder_id ++ ++ cinder_cpg = self._client_conf['hpe3par_cpg'][0] ++ ++ manageable_vols = [] ++ ++ body = self.client.getVolumes() ++ all_volumes = body['members'] ++ for vol in all_volumes: ++ cpg = vol.get('userCPG') ++ if cpg == cinder_cpg: ++ size_gb = int(vol['sizeMiB'] / 1024) ++ vol_name = vol['name'] ++ if vol_name in already_managed: ++ is_safe = False ++ reason_not_safe = _('Volume already managed') ++ cinder_id = already_managed[vol_name] ++ else: ++ is_safe = False ++ hostname = None ++ cinder_id = None ++ # Check if the unmanaged volume is attached to any host ++ try: ++ vlun = self.client.getVLUN(vol_name) ++ hostname = vlun['hostname'] ++ except hpe3parclient.exceptions.HTTPNotFound: ++ # not attached to any host ++ is_safe = True ++ ++ if is_safe: ++ reason_not_safe = None ++ else: ++ reason_not_safe = _('Volume attached to host ' + ++ hostname) ++ ++ manageable_vols.append({ ++ 'reference': {'name': vol_name}, ++ 'size': size_gb, ++ 'safe_to_manage': is_safe, ++ 'reason_not_safe': reason_not_safe, ++ 'cinder_id': cinder_id, ++ }) ++ ++ return volume_utils.paginate_entries_list( ++ manageable_vols, marker, limit, offset, sort_keys, sort_dirs) ++ ++ def get_manageable_snapshots(self, cinder_snapshots, marker, limit, offset, ++ sort_keys, sort_dirs): ++ already_managed = {} ++ for snap_obj in cinder_snapshots: ++ cinder_snap_id = snap_obj.id ++ snap_name = self._get_3par_snap_name(cinder_snap_id) ++ already_managed[snap_name] = cinder_snap_id ++ ++ cinder_cpg = self._client_conf['hpe3par_cpg'][0] ++ ++ cpg_volumes = [] ++ ++ body = self.client.getVolumes() ++ all_volumes = body['members'] ++ for vol in all_volumes: ++ cpg = vol.get('userCPG') ++ if cpg == cinder_cpg: ++ cpg_volumes.append(vol) ++ ++ manageable_snaps = [] ++ ++ for vol in cpg_volumes: ++ size_gb = int(vol['sizeMiB'] / 1024) ++ snapshots = self.client.getSnapshotsOfVolume(cinder_cpg, ++ vol['name']) ++ for snap_name in snapshots: ++ if snap_name in already_managed: ++ is_safe = False ++ reason_not_safe = _('Snapshot already managed') ++ cinder_snap_id = already_managed[snap_name] ++ else: ++ is_safe = True ++ reason_not_safe = None ++ cinder_snap_id = None ++ ++ manageable_snaps.append({ ++ 'reference': {'name': snap_name}, ++ 'size': size_gb, ++ 'safe_to_manage': is_safe, ++ 'reason_not_safe': reason_not_safe, ++ 'cinder_id': cinder_snap_id, ++ 'source_reference': {'name': vol['name']}, ++ }) ++ ++ return volume_utils.paginate_entries_list( ++ manageable_snaps, marker, limit, offset, sort_keys, sort_dirs) ++ + def _get_existing_volume_ref_name(self, existing_ref, is_snapshot=False): + """Returns the volume name of an existing reference. + +diff --git a/releasenotes/notes/hpe-3par-add-get-manageable-2926f21116c98599.yaml b/releasenotes/notes/hpe-3par-add-get-manageable-2926f21116c98599.yaml +new file mode 100644 +index 000000000..c5421b6cb +--- /dev/null ++++ b/releasenotes/notes/hpe-3par-add-get-manageable-2926f21116c98599.yaml +@@ -0,0 +1,5 @@ ++--- ++fixes: ++ - | ++ HPE 3PAR driver `Bug #1819903 `_: ++ Fixed: umanaged volumes & snapshots missing from cinder manageable-list. +-- +2.40.1 + diff -Nru cinder-20.3.1/debian/patches/0002-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch cinder-20.3.1/debian/patches/0002-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch --- cinder-20.3.1/debian/patches/0002-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch 1970-01-01 09:00:00.000000000 +0900 +++ cinder-20.3.1/debian/patches/0002-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch 2024-03-04 12:41:57.000000000 +0900 @@ -0,0 +1,259 @@ +From 97d5541b8982923ffa994ef308fe608f2e373f7b Mon Sep 17 00:00:00 2001 +From: Rajat Dhasmana +Date: Wed, 26 Oct 2022 09:55:43 +0000 +Subject: [PATCH 2/2] 3PAR: Error out if vol cannot be converted to base + +Consider volume and snapshots as below: + +v1 +| +`-- s1 + | + `-- v2 + | + `-- s2 + +User initiated deletion of snapshot s1. +It failed with some vague message. + +Initially, it was suspected that ... +While copying volume v2 (sometimes an intermediate step to break +volume dependency), we send a request to clone the volume v2 to new +base volume; and the exception [1] isn't handled properly. + +[1] Conflict (HTTP 409) 32 - volume has a child + +However, on further investigation it was found that ... +after a new volume v2 (omv-) is created and +when we try to delete old volume v2 (osv-), +at this point the exception [1] is thrown as error. + +This is now handled gracefully. Appropriate error is thrown +if the volume (v2) has snapshot (s2). + +Co-Authored-By: raghavendrat +Closes-Bug: #1994521 +Change-Id: I5e7fb425c92cdf8c16d5a86a58ca1a52421543d7 +(cherry picked from commit dfd8f99743a29220ca3face5fdf00a1a6071cf48) +--- + .../unit/volume/drivers/hpe/test_hpe3par.py | 29 ++++++++++++++++--- + cinder/volume/drivers/hpe/hpe_3par_common.py | 29 +++++++++++++++---- + ...base-vol-delete-snap-a460a4b1c419804a.yaml | 11 +++++++ + 3 files changed, 60 insertions(+), 9 deletions(-) + create mode 100644 releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml + +diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +index 813ea8845..3a2eff148 100644 +--- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py ++++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +@@ -749,10 +749,7 @@ class HPE3PARBaseDriver(test.TestCase): + configuration.unique_fqdn_network = True + return configuration + +- @mock.patch( +- 'hpe3parclient.client.HPE3ParClient', +- spec=True, +- ) ++ @mock.patch('hpe3parclient.client.HPE3ParClient') + def setup_mock_client(self, _m_client, driver, conf=None, m_conf=None, + is_primera=False, + wsapi_version=wsapi_version_latest): +@@ -3401,6 +3398,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client +@@ -3429,6 +3427,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3452,6 +3451,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + _mock_volume_types.return_value = { + 'name': 'gold', + 'extra_specs': { +@@ -3493,6 +3493,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'comment': comment, + 'readOnly': False}), + mock.call.getCPG(HPE3PAR_CPG), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3609,6 +3610,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'getVolume.return_value': {} + } + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + volume_type_hos = copy.deepcopy(self.volume_type_hos) + volume_type_hos['extra_specs']['convert_to_base'] = True + _mock_volume_types.return_value = volume_type_hos +@@ -3638,6 +3640,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3659,6 +3662,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'getVolume.return_value': {} + } + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + _mock_volume_types.return_value = self.volume_type_hos + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: +@@ -3687,6 +3691,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3709,6 +3714,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + 'getVolume.return_value': {} + } + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + volume_type_hos = copy.deepcopy(self.volume_type_hos) + volume_type_hos['extra_specs']['convert_to_base'] = True + _mock_volume_types.return_value = volume_type_hos +@@ -3739,6 +3745,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + { + 'comment': comment, + 'readOnly': False}), ++ mock.call.getVolumeSnapshots(self.VOLUME_3PAR_NAME), + mock.call.copyVolume( + osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY), + mock.call.getTask(mock.ANY), +@@ -3837,6 +3844,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client +@@ -3860,6 +3868,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + } + + mock_client = self.setup_driver(mock_conf=conf) ++ mock_client.getVolumeSnapshots.return_value = [] + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client +@@ -3871,6 +3880,18 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): + self.volume, + str(new_size)) + ++ def test__convert_to_base_volume_failure(self): ++ mock_client = self.setup_driver() ++ mock_client.getVolumeSnapshots.return_value = ( ++ ['oss-nwJVbXaEQMi0w.xPutFRQw']) ++ with mock.patch.object(hpecommon.HPE3PARCommon, ++ '_create_client') as mock_create_client: ++ mock_create_client.return_value = mock_client ++ common = self.driver._login() ++ self.assertRaises(exception.VolumeIsBusy, ++ common._convert_to_base_volume, ++ self.volume) ++ + @mock.patch.object(volume_types, 'get_volume_type') + def test_extend_volume_replicated(self, _mock_volume_types): + # Managed vs. unmanaged and periodic vs. sync are not relevant when +diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py +index 00c347c7e..7fd747e31 100644 +--- a/cinder/volume/drivers/hpe/hpe_3par_common.py ++++ b/cinder/volume/drivers/hpe/hpe_3par_common.py +@@ -300,11 +300,13 @@ class HPE3PARCommon(object): + 4.0.16 - In multi host env, fix multi-detach operation. Bug #1958122 + 4.0.17 - Added get_manageable_volumes and get_manageable_snapshots. + Bug #1819903 ++ 4.0.18 - During conversion of volume to base volume, ++ error out if it has child snapshot(s). Bug #1994521 + + + """ + +- VERSION = "4.0.17" ++ VERSION = "4.0.18" + + stats = {} + +@@ -3139,6 +3141,21 @@ class HPE3PARCommon(object): + + compression = self.get_compression_policy( + type_info['hpe3par_keys']) ++ ++ # If volume (osv-) has snapshot, while converting the volume ++ # to base volume (omv-), snapshot cannot be transferred to ++ # new base volume (omv-) i.e it remain with volume (osv-). ++ # So error out for such volume. ++ snap_list = self.client.getVolumeSnapshots(volume_name) ++ if snap_list: ++ snap_str = ",".join(snap_list) ++ msg = (_("Volume %(name)s has dependent snapshots: %(snap)s." ++ " Either flatten or remove the dependent snapshots:" ++ " %(snap)s for the conversion of volume %(name)s to" ++ " succeed." % {'name': volume_name, ++ 'snap': snap_str})) ++ raise exception.VolumeIsBusy(message=msg) ++ + # Create a physical copy of the volume + task_id = self._copy_volume(volume_name, temp_vol_name, + cpg, cpg, type_info['tpvv'], +@@ -3162,16 +3179,18 @@ class HPE3PARCommon(object): + comment = self._get_3par_vol_comment(volume_name) + if comment: + self.client.modifyVolume(temp_vol_name, {'comment': comment}) +- LOG.debug('Volume rename completed: convert_to_base_volume: ' +- 'id=%s.', volume['id']) ++ LOG.debug('Assigned the comment: convert_to_base_volume: ' ++ 'id=%s.', volume['id']) + +- # Delete source volume after the copy is complete ++ # Delete source volume (osv-) after the copy is complete + self.client.deleteVolume(volume_name) + LOG.debug('Delete src volume completed: convert_to_base_volume: ' + 'id=%s.', volume['id']) + +- # Rename the new volume to the original name ++ # Rename the new volume (omv-) to the original name (osv-) + self.client.modifyVolume(temp_vol_name, {'newName': volume_name}) ++ LOG.debug('Volume rename completed: convert_to_base_volume: ' ++ 'id=%s.', volume['id']) + + LOG.info('Completed: convert_to_base_volume: ' + 'id=%s.', volume['id']) +diff --git a/releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml b/releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml +new file mode 100644 +index 000000000..e087e3353 +--- /dev/null ++++ b/releasenotes/notes/hpe-3par-convert-to-base-vol-delete-snap-a460a4b1c419804a.yaml +@@ -0,0 +1,11 @@ ++--- ++fixes: ++ - | ++ HPE 3PAR driver `Bug #1994521 `_: ++ Fixed: While performing a delete snapshot (s1) operation, the volumes (v2) ++ dependent on the snapshot (s1) are converted to base volumes. This ++ operation fails if these dependent volumes (v2) have their own dependent ++ snapshots (s2). The errors during the failure were vague and not helpful. ++ With this release, we added conditions to fail this operation early and ++ also added useful error message. ++ +-- +2.40.1 + diff -Nru cinder-20.3.1/debian/patches/series cinder-20.3.1/debian/patches/series --- cinder-20.3.1/debian/patches/series 2024-01-26 21:26:48.000000000 +0900 +++ cinder-20.3.1/debian/patches/series 2024-03-04 12:42:42.000000000 +0900 @@ -3,3 +3,5 @@ skip-victoria-failures.patch fix-qos-computation.patch 0001-Revert-Driver-assisted-migration-on-retype-when-it-s.patch +0001-HPE-3PAR-Fix-umanaged-volumes-snapshots-missing.patch +0002-3PAR-Error-out-if-vol-cannot-be-converted-to-base.patch