diff --git a/drivers/cinder/openstack/antelope/patches/17_971297_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch b/drivers/cinder/openstack/antelope/patches/17_971297_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch deleted file mode 100644 index 821990a..0000000 --- a/drivers/cinder/openstack/antelope/patches/17_971297_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch +++ /dev/null @@ -1,484 +0,0 @@ -From e604d766b23316b90bbe2d624a9c1bf512447a75 Mon Sep 17 00:00:00 2001 -From: Biser Milanov -Date: Mon, 5 Aug 2024 17:24:23 +0300 -Subject: StorPool: Propagate a 'storpool:qos_class' extra spec to the StorPool - API - -Change-Id: I9c28b4f3106eb8b75664cc489eaeb09e56080831 -Signed-off-by: Biser Milanov ---- - cinder/tests/unit/fake_constants.py | 1 + - .../unit/volume/drivers/test_storpool.py | 170 +++++++++++++++--- - cinder/volume/drivers/storpool.py | 68 ++++--- - .../storpool-qos-via-qc-17fa862d94d1accb.yaml | 8 + - 4 files changed, 202 insertions(+), 45 deletions(-) - create mode 100644 releasenotes/notes/storpool-qos-via-qc-17fa862d94d1accb.yaml - -diff --git a/cinder/tests/unit/fake_constants.py b/cinder/tests/unit/fake_constants.py -index ebdf7b903..fa7499513 100644 ---- a/cinder/tests/unit/fake_constants.py -+++ b/cinder/tests/unit/fake_constants.py -@@ -82,6 +82,7 @@ VOLUME_TYPE2_ID = 'c4daaf47-c530-4901-b28e-f5f0a359c4e6' - VOLUME_TYPE3_ID = 'a3d55d15-eeb1-4816-ada9-bf82decc09b3' - VOLUME_TYPE4_ID = '69943076-754d-4da8-8718-0b0117e9cab1' - VOLUME_TYPE5_ID = '1c450d81-8aab-459e-b338-a6569139b835' -+VOLUME_TYPE6_ID = '6f9037f4-f232-49f1-94a7-f5e377ba9a96' - WILL_NOT_BE_FOUND_ID = 'ce816f65-c5aa-46d6-bd62-5272752d584a' - GROUP_TYPE_ID = '29514915-5208-46ab-9ece-1cc4688ad0c1' - GROUP_TYPE2_ID = 'f8645498-1323-47a2-9442-5c57724d2e3c' -diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py -index b21f1582d..e6f4e191d 100644 ---- a/cinder/tests/unit/volume/drivers/test_storpool.py -+++ b/cinder/tests/unit/volume/drivers/test_storpool.py -@@ -44,7 +44,12 @@ ISCSI_PORTAL_GROUP = 'openstack_pg' - volume_types = { - fake_constants.VOLUME_TYPE_ID: {}, - fake_constants.VOLUME_TYPE2_ID: {'storpool_template': 'ssd'}, -- fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'} -+ fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'}, -+ fake_constants.VOLUME_TYPE4_ID: -+ {'storpool_template': 'ssd2', 'storpool:qos_class': 'tier0'}, -+ fake_constants.VOLUME_TYPE5_ID: -+ {'storpool_template': 'hdd2', 'storpool:qos_class': 'tier1'}, -+ fake_constants.VOLUME_TYPE6_ID: {'storpool:qos_class': 'tier1'} - } - volumes = {} - snapshots = {} -@@ -153,6 +158,12 @@ class MockAPI(object): - if 'size' in data: - volumes[name]['size'] = data['size'] - -+ if 'tags' in data: -+ if 'tags' not in volumes[name]: -+ volumes[name]['tags'] = {} -+ for tag_name, tag_value in data['tags'].items(): -+ volumes[name]['tags'][tag_name] = tag_value -+ - if 'rename' in data and data['rename'] != name: - new_name = data['rename'] - volumes[new_name] = volumes[name] -@@ -405,6 +416,15 @@ class MockIscsiAPI: - self._configs.append(new_cfg) - - -+def MockVolumeUpdateDesc(size = None, tags = None): -+ volume_update = {} -+ if size is not None: -+ volume_update['size'] = size -+ if tags is not None: -+ volume_update['tags'] = tags -+ return volume_update -+ -+ - _ISCSI_TEST_CASES = [ - IscsiTestCase(None, None, False, 4), - IscsiTestCase(ISCSI_IQN_OURS, None, False, 2), -@@ -519,6 +539,20 @@ class StorPoolTestCase(test.TestCase): - self.driver.create_export(None, None, {}) - self.driver.remove_export(None, None) - -+ @ddt.data(*[{'name': 'volume-' + str(key), -+ 'volume_type': {'id': key, 'extra_specs': val}} -+ for key, val in sorted(volume_types.items())]) -+ @mock_volume_types -+ def test_get_qos_from_volume(self, volume): -+ expected = None -+ if volume['volume_type']['extra_specs']: -+ expected = (volume['volume_type']['extra_specs'] -+ .get('storpool:qos_class', None)) -+ -+ actual = driver.StorPoolDriver.qos_from_volume(volume) -+ -+ self.assertEqual(expected, actual) -+ - def test_stats(self): - stats = self.driver.get_volume_stats(refresh=True) - self.assertEqual('StorPool', stats['vendor_name']) -@@ -554,25 +588,27 @@ class StorPoolTestCase(test.TestCase): - - @mock_volume_types - def test_create_delete_volume(self): -+ volume_types_list = [{'id': key, 'extra_specs': val} -+ for key, val in volume_types.items()] -+ - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, -- 'volume_type': -- {'id': fake_constants.VOLUME_TYPE_ID}}) -+ 'volume_type': volume_types_list[0]}) - self.assertCountEqual([volumeName('1')], volumes.keys()) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] - self.assertEqual(1 * units.Gi, v['size']) - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) -+ self.assertIsNone(v.get('tags')) - - caught = False - try: -- self.driver.create_volume( -- {'id': '1', 'name': 'v1', 'size': 0, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -+ self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 0, -+ 'volume_type': volume_types_list[0]}) - except exception.VolumeBackendAPIException: - caught = True - self.assertTrue(caught) -@@ -581,41 +617,74 @@ class StorPoolTestCase(test.TestCase): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - -- self.driver.create_volume( -- {'id': '1', 'name': 'v1', 'size': 2, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -+ self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 2, -+ 'volume_type': volume_types_list[0]}) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] - self.assertEqual(2 * units.Gi, v['size']) - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) -+ self.assertIsNone(v.get('tags')) - - self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, -- 'volume_type': -- {'id': fake_constants.VOLUME_TYPE_ID}}) -+ 'volume_type': volume_types_list[0]}) - self.assertVolumeNames(('1', '2')) - v = volumes[volumeName('2')] - self.assertEqual(3 * units.Gi, v['size']) - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) -+ self.assertIsNone(v.get('tags')) - -- self.driver.create_volume( -- {'id': '3', 'name': 'v2', 'size': 4, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) -+ self.driver.create_volume({'id': '3', 'name': 'v2', 'size': 4, -+ 'volume_type': volume_types_list[1]}) - self.assertVolumeNames(('1', '2', '3')) - v = volumes[volumeName('3')] - self.assertEqual(4 * units.Gi, v['size']) - self.assertEqual('ssd', v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertIsNone(v.get('tags')) - -- self.driver.create_volume( -- {'id': '4', 'name': 'v2', 'size': 5, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE3_ID}}) -+ self.driver.create_volume({'id': '4', 'name': 'v2', 'size': 5, -+ 'volume_type': volume_types_list[2]}) - self.assertVolumeNames(('1', '2', '3', '4')) - v = volumes[volumeName('4')] - self.assertEqual(5 * units.Gi, v['size']) - self.assertEqual('hdd', v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertIsNone(v.get('tags')) -+ -+ self.driver.create_volume({'id': '5', 'name': 'v5', 'size': 6, -+ 'volume_type': volume_types_list[3]}) -+ self.assertVolumeNames(('1', '2', '3', '4', '5')) -+ v = volumes[volumeName('5')] -+ self.assertEqual(6 * units.Gi, v['size']) -+ self.assertEqual('ssd2', v['template']) -+ self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types_list[3]['extra_specs']['storpool:qos_class'], -+ v['tags']['qc']) -+ -+ self.driver.create_volume({'id': '6', 'name': 'v6', 'size': 7, -+ 'volume_type': volume_types_list[4]}) -+ self.assertVolumeNames(('1', '2', '3', '4', '5', '6')) -+ v = volumes[volumeName('6')] -+ self.assertEqual(7 * units.Gi, v['size']) -+ self.assertEqual('hdd2', v['template']) -+ self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types_list[4]['extra_specs']['storpool:qos_class'], -+ v['tags']['qc']) -+ -+ self.driver.create_volume({'id': '7', 'name': 'v7', 'size': 8, -+ 'volume_type': volume_types_list[5]}) -+ self.assertVolumeNames(('1', '2', '3', '4', '5', '6', '7')) -+ v = volumes[volumeName('7')] -+ self.assertEqual(8 * units.Gi, v['size']) -+ self.assertIsNone(v['template']) -+ self.assertEqual(3, v['replication']) -+ self.assertEqual( -+ volume_types_list[5]['extra_specs']['storpool:qos_class'], -+ v['tags']['qc']) - - # Make sure the dictionary is not corrupted somehow... - v = volumes[volumeName('1')] -@@ -623,7 +692,7 @@ class StorPoolTestCase(test.TestCase): - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) - -- for vid in ('1', '2', '3', '4'): -+ for vid in ('1', '2', '3', '4', '5', '6', '7'): - self.driver.delete_volume({'id': vid}) - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) -@@ -713,16 +782,26 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, snapshots) - - @ddt.data(*itertools.product( -- [{'id': key} for key in sorted(volume_types.keys())], -- [{'id': key} for key in sorted(volume_types.keys())])) -+ [ -+ { -+ 'id': key, -+ 'extra_specs': val -+ } for key, val in sorted(volume_types.items())], -+ [ -+ { -+ 'id': key, -+ 'extra_specs': val -+ } for key, val in sorted(volume_types.items()) -+ ] -+ )) - @ddt.unpack - @mock_volume_types - def test_create_cloned_volume(self, src_type, dst_type): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -- src_template = volume_types[src_type['id']].get('storpool_template') -- dst_template = volume_types[dst_type['id']].get('storpool_template') -+ src_template = src_type['extra_specs'].get('storpool_template') -+ dst_template = dst_type['extra_specs'].get('storpool_template') - src_name = 's-none' if src_template is None else 's-' + src_template - dst_name = 'd-none' if dst_template is None else 'd-' + dst_template - -@@ -739,6 +818,13 @@ class StorPoolTestCase(test.TestCase): - src_template) - self.driver.create_volume(vdata1) - self.assertVolumeNames(('1',)) -+ v = volumes[volumeName('1')] -+ src_qos_class_expected = ( -+ src_type['extra_specs'].get('storpool:qos_class')) -+ if src_qos_class_expected is None: -+ self.assertIsNone(v.get('tags')) -+ else: -+ self.assertEqual(src_qos_class_expected, v['tags']['qc']) - - vdata2 = { - 'id': 2, -@@ -755,6 +841,12 @@ class StorPoolTestCase(test.TestCase): - self.assertVolumeNames(('1', '2')) - vol2 = volumes[volumeName('2')] - self.assertEqual(vol2['template'], dst_template) -+ dst_qos_class_expected = ( -+ dst_type['extra_specs'].get('storpool:qos_class')) -+ if dst_qos_class_expected is None: -+ self.assertIsNone(vol2.get('tags')) -+ else: -+ self.assertEqual(dst_qos_class_expected, vol2['tags']['qc']) - - if src_template == dst_template: - self.assertEqual(vol2['baseOn'], volumeName('1')) -@@ -1125,3 +1217,37 @@ class StorPoolTestCase(test.TestCase): - _target_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) - self.assertFalse( - _export_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) -+ -+ @mock_volume_types -+ def test_volume_retype(self): -+ volume_types_list = [{'id': key, 'extra_specs': val} -+ for key, val in volume_types.items()] -+ -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, -+ 'volume_type': volume_types_list[0]}) -+ self.assertNotIn('tags', volumes[volumeName('1')]) -+ -+ volume = {'id': '1'} -+ diff = { -+ 'encryption': None, -+ 'extra_specs': { -+ 'storpool:qos_class': [ -+ None, -+ 'tier1' -+ ] -+ } -+ } -+ self.driver.retype(None, volume, None, diff, None) -+ self.assertEqual('tier1', volumes[volumeName('1')]['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', 'tier2'] -+ self.driver.retype(None, volume, None, diff, None) -+ self.assertEqual('tier2', volumes[volumeName('1')]['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', None] -+ self.driver.retype(None, volume, None, diff, None) -+ self.assertEqual('', volumes[volumeName('1')]['tags']['qc']) -diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py -index acfabccd6..1ea4034c5 100644 ---- a/cinder/volume/drivers/storpool.py -+++ b/cinder/volume/drivers/storpool.py -@@ -77,6 +77,10 @@ storpool_opts = [ - CONF = cfg.CONF - CONF.register_opts(storpool_opts, group=configuration.SHARED_CONF_GROUP) - -+EXTRA_SPECS_NAMESPACE = 'storpool' -+EXTRA_SPECS_QOS = 'qos_class' -+ES_QOS = EXTRA_SPECS_NAMESPACE + ":" + EXTRA_SPECS_QOS -+ - - class StorPoolConfigurationInvalid(exception.CinderException): - message = _("Invalid parameter %(param)s in the %(section)s section " -@@ -117,9 +121,10 @@ class StorPoolDriver(driver.VolumeDriver): - StorPool API instead of packages `storpool` and - `storpool.spopenstack` - 2.2.0 - Add iSCSI export support. -+ 2.3.0 - Introduce 'storpool:qos_class' extra spec - """ - -- VERSION = '2.2.0' -+ VERSION = '2.3.0' - CI_WIKI_NAME = 'StorPool_distributed_storage_CI' - - def __init__(self, *args, **kwargs): -@@ -136,6 +141,15 @@ class StorPoolDriver(driver.VolumeDriver): - def get_driver_options(): - return storpool_opts - -+ @staticmethod -+ def qos_from_volume(volume): -+ volume_type = volume['volume_type'] -+ extra_specs = \ -+ volume_types.get_volume_type_extra_specs(volume_type['id']) -+ if extra_specs: -+ return extra_specs.get(ES_QOS) -+ return None -+ - def _backendException(self, e): - return exception.VolumeBackendAPIException(data=str(e)) - -@@ -159,14 +173,12 @@ class StorPoolDriver(driver.VolumeDriver): - size = int(volume['size']) * units.Gi - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -- template = self._template_from_volume(volume) -+ qos_class = StorPoolDriver.qos_from_volume(volume) - - create_request = {'name': name, 'size': size} -- if template is not None: -- create_request['template'] = template -- else: -- create_request['replication'] = \ -- self.configuration.storpool_replication -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} - - try: - self._sp_api.volume_create(create_request) -@@ -536,12 +548,15 @@ class StorPoolDriver(driver.VolumeDriver): - self._volume_prefix, volume['id']) - name = storpool_utils.os_to_sp_snapshot_name( - self._volume_prefix, 'snap', snapshot['id']) -+ qos_class = StorPoolDriver.qos_from_volume(volume) -+ -+ create_request = {'name': volname, 'size': size, 'parent': name} -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} -+ - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'parent': name -- }) -+ self._sp_api.volume_create(create_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -552,6 +567,13 @@ class StorPoolDriver(driver.VolumeDriver): - volname = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - -+ qos_class = StorPoolDriver.qos_from_volume(volume) -+ -+ clone_request = {'name': volname, 'size': size} -+ -+ if qos_class: -+ clone_request['tags'] = {'qc': qos_class} -+ - src_volume = self.db.volume_get( - context.get_admin_context(), - src_vref['id'], -@@ -565,12 +587,9 @@ class StorPoolDriver(driver.VolumeDriver): - }) - if template == src_template: - LOG.info('Using baseOn to clone a volume into the same template') -+ clone_request['baseOn'] = refname - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'baseOn': refname, -- }) -+ self._sp_api.volume_create(clone_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -597,11 +616,8 @@ class StorPoolDriver(driver.VolumeDriver): - raise self._backendException(e) - - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'parent': snapname -- }) -+ clone_request['parent'] = snapname -+ self._sp_api.volume_create(clone_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -785,12 +801,18 @@ class StorPoolDriver(driver.VolumeDriver): - update['template'] = templ - else: - update['replication'] = repl -+ if diff['extra_specs'].get(ES_QOS): -+ v = diff['extra_specs'].get(ES_QOS) -+ if v[1] is None: -+ update['tags'] = {'qc': ''} -+ elif v[0] != v[1]: -+ update['tags'] = {'qc': v[1]} - - if update: - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - try: -- self._sp_api.volume_update(name, **update) -+ self._sp_api.volume_update(name, update) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -diff --git a/releasenotes/notes/storpool-qos-via-qc-17fa862d94d1accb.yaml b/releasenotes/notes/storpool-qos-via-qc-17fa862d94d1accb.yaml -new file mode 100644 -index 000000000..42e63a364 ---- /dev/null -+++ b/releasenotes/notes/storpool-qos-via-qc-17fa862d94d1accb.yaml -@@ -0,0 +1,8 @@ -+--- -+features: -+ - | -+ StorPool driver: Added support for using `StorPool's QoS -+ `__ based on the -+ `qc` StorPool volume tag. The driver will now propagate a -+ 'storpool:qos_class' extra spec (if it exists) to the StorPool API -+ when creating and retyping OpenStack volumes. -\ No newline at end of file --- -2.43.0 - diff --git a/drivers/cinder/openstack/antelope/patches/17_971297_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch b/drivers/cinder/openstack/antelope/patches/17_971297_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch deleted file mode 100644 index e2c5953..0000000 --- a/drivers/cinder/openstack/antelope/patches/17_971297_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch +++ /dev/null @@ -1,467 +0,0 @@ -From e604d766b23316b90bbe2d624a9c1bf512447a75 Mon Sep 17 00:00:00 2001 -From: Biser Milanov -Date: Mon, 5 Aug 2024 17:24:23 +0300 -Subject: StorPool: Propagate a 'storpool:qos_class' extra spec to the StorPool - API - -Change-Id: I9c28b4f3106eb8b75664cc489eaeb09e56080831 -Signed-off-by: Biser Milanov ---- - cinder/tests/unit/fake_constants.py | 1 + - .../unit/volume/drivers/test_storpool.py | 170 +++++++++++++++--- - cinder/volume/drivers/storpool.py | 68 ++++--- - 3 files changed, 194 insertions(+), 45 deletions(-) - -diff --git a/cinder/tests/unit/fake_constants.py b/cinder/tests/unit/fake_constants.py -index ebdf7b903..fa7499513 100644 ---- a/cinder/tests/unit/fake_constants.py -+++ b/cinder/tests/unit/fake_constants.py -@@ -82,6 +82,7 @@ VOLUME_TYPE2_ID = 'c4daaf47-c530-4901-b28e-f5f0a359c4e6' - VOLUME_TYPE3_ID = 'a3d55d15-eeb1-4816-ada9-bf82decc09b3' - VOLUME_TYPE4_ID = '69943076-754d-4da8-8718-0b0117e9cab1' - VOLUME_TYPE5_ID = '1c450d81-8aab-459e-b338-a6569139b835' -+VOLUME_TYPE6_ID = '6f9037f4-f232-49f1-94a7-f5e377ba9a96' - WILL_NOT_BE_FOUND_ID = 'ce816f65-c5aa-46d6-bd62-5272752d584a' - GROUP_TYPE_ID = '29514915-5208-46ab-9ece-1cc4688ad0c1' - GROUP_TYPE2_ID = 'f8645498-1323-47a2-9442-5c57724d2e3c' -diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py -index b21f1582d..e6f4e191d 100644 ---- a/cinder/tests/unit/volume/drivers/test_storpool.py -+++ b/cinder/tests/unit/volume/drivers/test_storpool.py -@@ -44,7 +44,12 @@ ISCSI_PORTAL_GROUP = 'openstack_pg' - volume_types = { - fake_constants.VOLUME_TYPE_ID: {}, - fake_constants.VOLUME_TYPE2_ID: {'storpool_template': 'ssd'}, -- fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'} -+ fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'}, -+ fake_constants.VOLUME_TYPE4_ID: -+ {'storpool_template': 'ssd2', 'storpool:qos_class': 'tier0'}, -+ fake_constants.VOLUME_TYPE5_ID: -+ {'storpool_template': 'hdd2', 'storpool:qos_class': 'tier1'}, -+ fake_constants.VOLUME_TYPE6_ID: {'storpool:qos_class': 'tier1'} - } - volumes = {} - snapshots = {} -@@ -153,6 +158,12 @@ class MockAPI(object): - if 'size' in data: - volumes[name]['size'] = data['size'] - -+ if 'tags' in data: -+ if 'tags' not in volumes[name]: -+ volumes[name]['tags'] = {} -+ for tag_name, tag_value in data['tags'].items(): -+ volumes[name]['tags'][tag_name] = tag_value -+ - if 'rename' in data and data['rename'] != name: - new_name = data['rename'] - volumes[new_name] = volumes[name] -@@ -405,6 +416,15 @@ class MockIscsiAPI: - self._configs.append(new_cfg) - - -+def MockVolumeUpdateDesc(size = None, tags = None): -+ volume_update = {} -+ if size is not None: -+ volume_update['size'] = size -+ if tags is not None: -+ volume_update['tags'] = tags -+ return volume_update -+ -+ - _ISCSI_TEST_CASES = [ - IscsiTestCase(None, None, False, 4), - IscsiTestCase(ISCSI_IQN_OURS, None, False, 2), -@@ -519,6 +539,20 @@ class StorPoolTestCase(test.TestCase): - self.driver.create_export(None, None, {}) - self.driver.remove_export(None, None) - -+ @ddt.data(*[{'name': 'volume-' + str(key), -+ 'volume_type': {'id': key, 'extra_specs': val}} -+ for key, val in sorted(volume_types.items())]) -+ @mock_volume_types -+ def test_get_qos_from_volume(self, volume): -+ expected = None -+ if volume['volume_type']['extra_specs']: -+ expected = (volume['volume_type']['extra_specs'] -+ .get('storpool:qos_class', None)) -+ -+ actual = driver.StorPoolDriver.qos_from_volume(volume) -+ -+ self.assertEqual(expected, actual) -+ - def test_stats(self): - stats = self.driver.get_volume_stats(refresh=True) - self.assertEqual('StorPool', stats['vendor_name']) -@@ -554,25 +588,27 @@ class StorPoolTestCase(test.TestCase): - - @mock_volume_types - def test_create_delete_volume(self): -+ volume_types_list = [{'id': key, 'extra_specs': val} -+ for key, val in volume_types.items()] -+ - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, -- 'volume_type': -- {'id': fake_constants.VOLUME_TYPE_ID}}) -+ 'volume_type': volume_types_list[0]}) - self.assertCountEqual([volumeName('1')], volumes.keys()) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] - self.assertEqual(1 * units.Gi, v['size']) - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) -+ self.assertIsNone(v.get('tags')) - - caught = False - try: -- self.driver.create_volume( -- {'id': '1', 'name': 'v1', 'size': 0, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -+ self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 0, -+ 'volume_type': volume_types_list[0]}) - except exception.VolumeBackendAPIException: - caught = True - self.assertTrue(caught) -@@ -581,41 +617,74 @@ class StorPoolTestCase(test.TestCase): - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - -- self.driver.create_volume( -- {'id': '1', 'name': 'v1', 'size': 2, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -+ self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 2, -+ 'volume_type': volume_types_list[0]}) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] - self.assertEqual(2 * units.Gi, v['size']) - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) -+ self.assertIsNone(v.get('tags')) - - self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, -- 'volume_type': -- {'id': fake_constants.VOLUME_TYPE_ID}}) -+ 'volume_type': volume_types_list[0]}) - self.assertVolumeNames(('1', '2')) - v = volumes[volumeName('2')] - self.assertEqual(3 * units.Gi, v['size']) - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) -+ self.assertIsNone(v.get('tags')) - -- self.driver.create_volume( -- {'id': '3', 'name': 'v2', 'size': 4, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) -+ self.driver.create_volume({'id': '3', 'name': 'v2', 'size': 4, -+ 'volume_type': volume_types_list[1]}) - self.assertVolumeNames(('1', '2', '3')) - v = volumes[volumeName('3')] - self.assertEqual(4 * units.Gi, v['size']) - self.assertEqual('ssd', v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertIsNone(v.get('tags')) - -- self.driver.create_volume( -- {'id': '4', 'name': 'v2', 'size': 5, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE3_ID}}) -+ self.driver.create_volume({'id': '4', 'name': 'v2', 'size': 5, -+ 'volume_type': volume_types_list[2]}) - self.assertVolumeNames(('1', '2', '3', '4')) - v = volumes[volumeName('4')] - self.assertEqual(5 * units.Gi, v['size']) - self.assertEqual('hdd', v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertIsNone(v.get('tags')) -+ -+ self.driver.create_volume({'id': '5', 'name': 'v5', 'size': 6, -+ 'volume_type': volume_types_list[3]}) -+ self.assertVolumeNames(('1', '2', '3', '4', '5')) -+ v = volumes[volumeName('5')] -+ self.assertEqual(6 * units.Gi, v['size']) -+ self.assertEqual('ssd2', v['template']) -+ self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types_list[3]['extra_specs']['storpool:qos_class'], -+ v['tags']['qc']) -+ -+ self.driver.create_volume({'id': '6', 'name': 'v6', 'size': 7, -+ 'volume_type': volume_types_list[4]}) -+ self.assertVolumeNames(('1', '2', '3', '4', '5', '6')) -+ v = volumes[volumeName('6')] -+ self.assertEqual(7 * units.Gi, v['size']) -+ self.assertEqual('hdd2', v['template']) -+ self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types_list[4]['extra_specs']['storpool:qos_class'], -+ v['tags']['qc']) -+ -+ self.driver.create_volume({'id': '7', 'name': 'v7', 'size': 8, -+ 'volume_type': volume_types_list[5]}) -+ self.assertVolumeNames(('1', '2', '3', '4', '5', '6', '7')) -+ v = volumes[volumeName('7')] -+ self.assertEqual(8 * units.Gi, v['size']) -+ self.assertIsNone(v['template']) -+ self.assertEqual(3, v['replication']) -+ self.assertEqual( -+ volume_types_list[5]['extra_specs']['storpool:qos_class'], -+ v['tags']['qc']) - - # Make sure the dictionary is not corrupted somehow... - v = volumes[volumeName('1')] -@@ -623,7 +692,7 @@ class StorPoolTestCase(test.TestCase): - self.assertIsNone(v['template']) - self.assertEqual(3, v['replication']) - -- for vid in ('1', '2', '3', '4'): -+ for vid in ('1', '2', '3', '4', '5', '6', '7'): - self.driver.delete_volume({'id': vid}) - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) -@@ -713,16 +782,26 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, snapshots) - - @ddt.data(*itertools.product( -- [{'id': key} for key in sorted(volume_types.keys())], -- [{'id': key} for key in sorted(volume_types.keys())])) -+ [ -+ { -+ 'id': key, -+ 'extra_specs': val -+ } for key, val in sorted(volume_types.items())], -+ [ -+ { -+ 'id': key, -+ 'extra_specs': val -+ } for key, val in sorted(volume_types.items()) -+ ] -+ )) - @ddt.unpack - @mock_volume_types - def test_create_cloned_volume(self, src_type, dst_type): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -- src_template = volume_types[src_type['id']].get('storpool_template') -- dst_template = volume_types[dst_type['id']].get('storpool_template') -+ src_template = src_type['extra_specs'].get('storpool_template') -+ dst_template = dst_type['extra_specs'].get('storpool_template') - src_name = 's-none' if src_template is None else 's-' + src_template - dst_name = 'd-none' if dst_template is None else 'd-' + dst_template - -@@ -739,6 +818,13 @@ class StorPoolTestCase(test.TestCase): - src_template) - self.driver.create_volume(vdata1) - self.assertVolumeNames(('1',)) -+ v = volumes[volumeName('1')] -+ src_qos_class_expected = ( -+ src_type['extra_specs'].get('storpool:qos_class')) -+ if src_qos_class_expected is None: -+ self.assertIsNone(v.get('tags')) -+ else: -+ self.assertEqual(src_qos_class_expected, v['tags']['qc']) - - vdata2 = { - 'id': 2, -@@ -755,6 +841,12 @@ class StorPoolTestCase(test.TestCase): - self.assertVolumeNames(('1', '2')) - vol2 = volumes[volumeName('2')] - self.assertEqual(vol2['template'], dst_template) -+ dst_qos_class_expected = ( -+ dst_type['extra_specs'].get('storpool:qos_class')) -+ if dst_qos_class_expected is None: -+ self.assertIsNone(vol2.get('tags')) -+ else: -+ self.assertEqual(dst_qos_class_expected, vol2['tags']['qc']) - - if src_template == dst_template: - self.assertEqual(vol2['baseOn'], volumeName('1')) -@@ -1125,3 +1217,37 @@ class StorPoolTestCase(test.TestCase): - _target_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) - self.assertFalse( - _export_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) -+ -+ @mock_volume_types -+ def test_volume_retype(self): -+ volume_types_list = [{'id': key, 'extra_specs': val} -+ for key, val in volume_types.items()] -+ -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({'id': '1', 'name': 'v1', 'size': 1, -+ 'volume_type': volume_types_list[0]}) -+ self.assertNotIn('tags', volumes[volumeName('1')]) -+ -+ volume = {'id': '1'} -+ diff = { -+ 'encryption': None, -+ 'extra_specs': { -+ 'storpool:qos_class': [ -+ None, -+ 'tier1' -+ ] -+ } -+ } -+ self.driver.retype(None, volume, None, diff, None) -+ self.assertEqual('tier1', volumes[volumeName('1')]['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', 'tier2'] -+ self.driver.retype(None, volume, None, diff, None) -+ self.assertEqual('tier2', volumes[volumeName('1')]['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', None] -+ self.driver.retype(None, volume, None, diff, None) -+ self.assertEqual('', volumes[volumeName('1')]['tags']['qc']) -diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py -index acfabccd6..1ea4034c5 100644 ---- a/cinder/volume/drivers/storpool.py -+++ b/cinder/volume/drivers/storpool.py -@@ -77,6 +77,10 @@ storpool_opts = [ - CONF = cfg.CONF - CONF.register_opts(storpool_opts, group=configuration.SHARED_CONF_GROUP) - -+EXTRA_SPECS_NAMESPACE = 'storpool' -+EXTRA_SPECS_QOS = 'qos_class' -+ES_QOS = EXTRA_SPECS_NAMESPACE + ":" + EXTRA_SPECS_QOS -+ - - class StorPoolConfigurationInvalid(exception.CinderException): - message = _("Invalid parameter %(param)s in the %(section)s section " -@@ -117,9 +121,10 @@ class StorPoolDriver(driver.VolumeDriver): - StorPool API instead of packages `storpool` and - `storpool.spopenstack` - 2.2.0 - Add iSCSI export support. -+ 2.3.0 - Introduce 'storpool:qos_class' extra spec - """ - -- VERSION = '2.2.0' -+ VERSION = '2.3.0' - CI_WIKI_NAME = 'StorPool_distributed_storage_CI' - - def __init__(self, *args, **kwargs): -@@ -136,6 +141,15 @@ class StorPoolDriver(driver.VolumeDriver): - def get_driver_options(): - return storpool_opts - -+ @staticmethod -+ def qos_from_volume(volume): -+ volume_type = volume['volume_type'] -+ extra_specs = \ -+ volume_types.get_volume_type_extra_specs(volume_type['id']) -+ if extra_specs: -+ return extra_specs.get(ES_QOS) -+ return None -+ - def _backendException(self, e): - return exception.VolumeBackendAPIException(data=str(e)) - -@@ -159,14 +173,12 @@ class StorPoolDriver(driver.VolumeDriver): - size = int(volume['size']) * units.Gi - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -- template = self._template_from_volume(volume) -+ qos_class = StorPoolDriver.qos_from_volume(volume) - - create_request = {'name': name, 'size': size} -- if template is not None: -- create_request['template'] = template -- else: -- create_request['replication'] = \ -- self.configuration.storpool_replication -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} - - try: - self._sp_api.volume_create(create_request) -@@ -536,12 +548,15 @@ class StorPoolDriver(driver.VolumeDriver): - self._volume_prefix, volume['id']) - name = storpool_utils.os_to_sp_snapshot_name( - self._volume_prefix, 'snap', snapshot['id']) -+ qos_class = StorPoolDriver.qos_from_volume(volume) -+ -+ create_request = {'name': volname, 'size': size, 'parent': name} -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} -+ - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'parent': name -- }) -+ self._sp_api.volume_create(create_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -552,6 +567,13 @@ class StorPoolDriver(driver.VolumeDriver): - volname = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - -+ qos_class = StorPoolDriver.qos_from_volume(volume) -+ -+ clone_request = {'name': volname, 'size': size} -+ -+ if qos_class: -+ clone_request['tags'] = {'qc': qos_class} -+ - src_volume = self.db.volume_get( - context.get_admin_context(), - src_vref['id'], -@@ -565,12 +587,9 @@ class StorPoolDriver(driver.VolumeDriver): - }) - if template == src_template: - LOG.info('Using baseOn to clone a volume into the same template') -+ clone_request['baseOn'] = refname - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'baseOn': refname, -- }) -+ self._sp_api.volume_create(clone_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -597,11 +616,8 @@ class StorPoolDriver(driver.VolumeDriver): - raise self._backendException(e) - - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'parent': snapname -- }) -+ clone_request['parent'] = snapname -+ self._sp_api.volume_create(clone_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -785,12 +801,18 @@ class StorPoolDriver(driver.VolumeDriver): - update['template'] = templ - else: - update['replication'] = repl -+ if diff['extra_specs'].get(ES_QOS): -+ v = diff['extra_specs'].get(ES_QOS) -+ if v[1] is None: -+ update['tags'] = {'qc': ''} -+ elif v[0] != v[1]: -+ update['tags'] = {'qc': v[1]} - - if update: - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - try: -- self._sp_api.volume_update(name, **update) -+ self._sp_api.volume_update(name, update) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - --- -2.43.0 - diff --git a/drivers/cinder/openstack/antelope/patches/17_983033_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch b/drivers/cinder/openstack/antelope/patches/17_983033_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch new file mode 100644 index 0000000..5abef6e --- /dev/null +++ b/drivers/cinder/openstack/antelope/patches/17_983033_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch @@ -0,0 +1,297 @@ +From fade40cb281e5fe2f7327ce9c228f75bc134e887 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Thu, 8 Jan 2026 16:55:49 +0200 +Subject: StorPool: Do not use the option 'storpool_replication' when creating + or retyping volumes + +Change-Id: I45d32892e7712cb3bb4e52cf4547691505b01d01 +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 101 +++++++++--------- + cinder/volume/drivers/storpool.py | 25 +++-- + 2 files changed, 66 insertions(+), 60 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index b21f1582d..af2e22ef8 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -33,6 +33,7 @@ from cinder.tests.unit import test + from cinder.volume import configuration as conf + from cinder.volume.drivers import storpool as driver + ++DEFAULT_STORPOOL_TEMPLATE = 'default' + + ISCSI_IQN_OURS = 'beleriand' + ISCSI_IQN_OTHER = 'rohan' +@@ -42,7 +43,7 @@ ISCSI_PAT_BOTH = '*riand roh*' + ISCSI_PORTAL_GROUP = 'openstack_pg' + + volume_types = { +- fake_constants.VOLUME_TYPE_ID: {}, ++ fake_constants.VOLUME_TYPE_ID: {'storpool_template': 'nvme'}, + fake_constants.VOLUME_TYPE2_ID: {'storpool_template': 'ssd'}, + fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'} + } +@@ -447,7 +448,7 @@ class StorPoolTestCase(test.TestCase): + + self.cfg = mock.Mock(spec=conf.Configuration) + self.cfg.volume_backend_name = 'storpool_test' +- self.cfg.storpool_template = None ++ self.cfg.storpool_template = DEFAULT_STORPOOL_TEMPLATE + self.cfg.storpool_replication = 3 + self.cfg.storpool_iscsi_cinder_volume = False + self.cfg.storpool_iscsi_export_to = '' +@@ -565,8 +566,8 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(1 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + caught = False + try: +@@ -587,8 +588,8 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, + 'volume_type': +@@ -596,8 +597,8 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2')) + v = volumes[volumeName('2')] + self.assertEqual(3 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( + {'id': '3', 'name': 'v2', 'size': 4, +@@ -620,8 +621,8 @@ class StorPoolTestCase(test.TestCase): + # Make sure the dictionary is not corrupted somehow... + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + for vid in ('1', '2', '3', '4'): + self.driver.delete_volume({'id': vid}) +@@ -689,7 +690,13 @@ class StorPoolTestCase(test.TestCase): + self.driver.extend_volume({'id': '1'}, 2) + self.assertEqual(2 * units.Gi, volumes[volumeName('1')]['size']) + +- with mock.patch.object(self.driver, 'db', new=MockVolumeDB()): ++ vtype1 = {'id': fake_constants.VOLUME_TYPE_ID} ++ vtype1.update(volume_types[fake_constants.VOLUME_TYPE_ID]) ++ vtype2 = {'id': fake_constants.VOLUME_TYPE_ID} ++ vtype2.update(volume_types[fake_constants.VOLUME_TYPE_ID]) ++ vtypes = {1: vtype1, 2: vtype2} ++ ++ with mock.patch.object(self.driver, 'db', new=MockVolumeDB(vtypes)): + self.driver.create_cloned_volume( + { + 'id': '2', +@@ -790,39 +797,12 @@ class StorPoolTestCase(test.TestCase): + self.assertEqual(21, pool['total_capacity_gb']) + self.assertEqual(5, int(pool['free_capacity_gb'])) + +- self.driver.create_volume( +- {'id': 'cfgrepl1', 'name': 'v1', 'size': 1, +- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) +- self.assertVolumeNames(('cfgrepl1',)) +- v = volumes[volumeName('cfgrepl1')] +- self.assertEqual(3, v['replication']) +- self.assertIsNone(v['template']) +- self.driver.delete_volume({'id': 'cfgrepl1'}) +- + self.driver.configuration.storpool_replication = 2 + stats = self.driver.get_volume_stats(refresh=True) + pool = stats['pools'][0] + self.assertEqual(21, pool['total_capacity_gb']) + self.assertEqual(8, int(pool['free_capacity_gb'])) + +- self.driver.create_volume( +- {'id': 'cfgrepl2', 'name': 'v1', 'size': 1, +- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) +- self.assertVolumeNames(('cfgrepl2',)) +- v = volumes[volumeName('cfgrepl2')] +- self.assertEqual(2, v['replication']) +- self.assertIsNone(v['template']) +- self.driver.delete_volume({'id': 'cfgrepl2'}) +- +- self.driver.create_volume( +- {'id': 'cfgrepl3', 'name': 'v1', 'size': 1, +- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) +- self.assertVolumeNames(('cfgrepl3',)) +- v = volumes[volumeName('cfgrepl3')] +- self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) +- self.driver.delete_volume({'id': 'cfgrepl3'}) +- + self.driver.configuration.storpool_replication = save_repl + + self.assertVolumeNames([]) +@@ -835,17 +815,13 @@ class StorPoolTestCase(test.TestCase): + self.assertDictEqual({}, volumes) + self.assertDictEqual({}, snapshots) + +- save_template = self.driver.configuration.storpool_template +- +- self.driver.configuration.storpool_template = None +- + self.driver.create_volume( + {'id': 'cfgtempl1', 'name': 'v1', 'size': 1, + 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) + self.assertVolumeNames(('cfgtempl1',)) + v = volumes[volumeName('cfgtempl1')] +- self.assertEqual(3, v['replication']) +- self.assertIsNone(v['template']) ++ self.assertNotIn(v, 'replication') ++ self.assertEqual('nvme', v['template']) + self.driver.delete_volume({'id': 'cfgtempl1'}) + + self.driver.create_volume( +@@ -857,7 +833,12 @@ class StorPoolTestCase(test.TestCase): + self.assertEqual('ssd', v['template']) + self.driver.delete_volume({'id': 'cfgtempl2'}) + ++ save_template = self.driver.configuration.storpool_template ++ + self.driver.configuration.storpool_template = 'hdd' ++ save_volume_template = \ ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] ++ del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] + + self.driver.create_volume( + {'id': 'cfgtempl3', 'name': 'v1', 'size': 1, +@@ -867,6 +848,8 @@ class StorPoolTestCase(test.TestCase): + self.assertNotIn('replication', v) + self.assertEqual('hdd', v['template']) + self.driver.delete_volume({'id': 'cfgtempl3'}) ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ ++ save_volume_template + + self.driver.create_volume( + {'id': 'cfgtempl4', 'name': 'v1', 'size': 1, +@@ -884,11 +867,7 @@ class StorPoolTestCase(test.TestCase): + self.assertDictEqual({}, snapshots) + + @ddt.data( +- # No volume type at all: 'default' +- ('default', None), +- # No storpool_template in the type extra specs: 'default' +- ('default', {'id': fake_constants.VOLUME_TYPE_ID}), +- # An actual template specified: 'template_*' ++ ('template_nvme', {'id': fake_constants.VOLUME_TYPE_ID}), + ('template_ssd', {'id': fake_constants.VOLUME_TYPE2_ID}), + ('template_hdd', {'id': fake_constants.VOLUME_TYPE3_ID}), + ) +@@ -900,6 +879,30 @@ class StorPoolTestCase(test.TestCase): + 'volume_type': volume_type + })) + ++ @mock_volume_types ++ def test_get_pool_no_extra_spec(self): ++ # No storpool_template in the type extra specs, default to config ++ save_template = \ ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] ++ del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] ++ self.assertEqual('template_default', ++ self.driver.get_pool({ ++ 'volume_type': { ++ 'id': fake_constants.VOLUME_TYPE_ID} ++ })) ++ ++ save_default_template = self.driver.configuration.storpool_template ++ self.driver.configuration.storpool_template = None ++ self.assertEqual('default', ++ self.driver.get_pool({ ++ 'volume_type': { ++ 'id': fake_constants.VOLUME_TYPE_ID} ++ })) ++ ++ self.driver.configuration.storpool_template = save_default_template ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ ++ save_template ++ + @mock_volume_types + def test_volume_revert(self): + vol_id = 'rev1' +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index acfabccd6..bb15e7a29 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -69,9 +69,8 @@ storpool_opts = [ + cfg.IntOpt('storpool_replication', + default=3, + help='The default StorPool chain replication value. ' +- 'Used when creating a volume with no specified type if ' +- 'storpool_template is not set. Also used for calculating ' +- 'the apparent free space reported in the stats.'), ++ 'Used for calculating the apparent free space reported in ' ++ 'the stats.'), + ] + + CONF = cfg.CONF +@@ -117,9 +116,11 @@ class StorPoolDriver(driver.VolumeDriver): + StorPool API instead of packages `storpool` and + `storpool.spopenstack` + 2.2.0 - Add iSCSI export support. ++ 2.3.0 - Do not use the option 'storpool_replication' when creating or ++ retyping volumes. + """ + +- VERSION = '2.2.0' ++ VERSION = '2.3.0' + CI_WIKI_NAME = 'StorPool_distributed_storage_CI' + + def __init__(self, *args, **kwargs): +@@ -162,11 +163,12 @@ class StorPoolDriver(driver.VolumeDriver): + template = self._template_from_volume(volume) + + create_request = {'name': name, 'size': size} +- if template is not None: +- create_request['template'] = template +- else: +- create_request['replication'] = \ +- self.configuration.storpool_replication ++ if not template: ++ raise self._backendException( ++ "Cannot create a volume without a configured StorPool" ++ " template.") ++ ++ create_request['template'] = template + + try: + self._sp_api.volume_create(create_request) +@@ -765,7 +767,6 @@ class StorPoolDriver(driver.VolumeDriver): + return False + + templ = self.configuration.storpool_template +- repl = self.configuration.storpool_replication + if diff['extra_specs']: + # Check for the StorPool extra specs. We intentionally ignore any + # other extra_specs because the cinder scheduler should not even +@@ -784,7 +785,9 @@ class StorPoolDriver(driver.VolumeDriver): + elif templ is not None: + update['template'] = templ + else: +- update['replication'] = repl ++ raise self._backendException( ++ "Cannot retype a volume to a type that is missing" ++ " a configured StorPool template.") + + if update: + name = storpool_utils.os_to_sp_volume_name( +-- +2.43.0 + diff --git a/drivers/cinder/openstack/antelope/patches/17_983033_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch b/drivers/cinder/openstack/antelope/patches/17_983033_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch new file mode 100644 index 0000000..5abef6e --- /dev/null +++ b/drivers/cinder/openstack/antelope/patches/17_983033_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch @@ -0,0 +1,297 @@ +From fade40cb281e5fe2f7327ce9c228f75bc134e887 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Thu, 8 Jan 2026 16:55:49 +0200 +Subject: StorPool: Do not use the option 'storpool_replication' when creating + or retyping volumes + +Change-Id: I45d32892e7712cb3bb4e52cf4547691505b01d01 +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 101 +++++++++--------- + cinder/volume/drivers/storpool.py | 25 +++-- + 2 files changed, 66 insertions(+), 60 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index b21f1582d..af2e22ef8 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -33,6 +33,7 @@ from cinder.tests.unit import test + from cinder.volume import configuration as conf + from cinder.volume.drivers import storpool as driver + ++DEFAULT_STORPOOL_TEMPLATE = 'default' + + ISCSI_IQN_OURS = 'beleriand' + ISCSI_IQN_OTHER = 'rohan' +@@ -42,7 +43,7 @@ ISCSI_PAT_BOTH = '*riand roh*' + ISCSI_PORTAL_GROUP = 'openstack_pg' + + volume_types = { +- fake_constants.VOLUME_TYPE_ID: {}, ++ fake_constants.VOLUME_TYPE_ID: {'storpool_template': 'nvme'}, + fake_constants.VOLUME_TYPE2_ID: {'storpool_template': 'ssd'}, + fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'} + } +@@ -447,7 +448,7 @@ class StorPoolTestCase(test.TestCase): + + self.cfg = mock.Mock(spec=conf.Configuration) + self.cfg.volume_backend_name = 'storpool_test' +- self.cfg.storpool_template = None ++ self.cfg.storpool_template = DEFAULT_STORPOOL_TEMPLATE + self.cfg.storpool_replication = 3 + self.cfg.storpool_iscsi_cinder_volume = False + self.cfg.storpool_iscsi_export_to = '' +@@ -565,8 +566,8 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(1 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + caught = False + try: +@@ -587,8 +588,8 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, + 'volume_type': +@@ -596,8 +597,8 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2')) + v = volumes[volumeName('2')] + self.assertEqual(3 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( + {'id': '3', 'name': 'v2', 'size': 4, +@@ -620,8 +621,8 @@ class StorPoolTestCase(test.TestCase): + # Make sure the dictionary is not corrupted somehow... + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertIsNone(v['template']) +- self.assertEqual(3, v['replication']) ++ self.assertEqual('nvme', v['template']) ++ self.assertNotIn('replication', v.keys()) + + for vid in ('1', '2', '3', '4'): + self.driver.delete_volume({'id': vid}) +@@ -689,7 +690,13 @@ class StorPoolTestCase(test.TestCase): + self.driver.extend_volume({'id': '1'}, 2) + self.assertEqual(2 * units.Gi, volumes[volumeName('1')]['size']) + +- with mock.patch.object(self.driver, 'db', new=MockVolumeDB()): ++ vtype1 = {'id': fake_constants.VOLUME_TYPE_ID} ++ vtype1.update(volume_types[fake_constants.VOLUME_TYPE_ID]) ++ vtype2 = {'id': fake_constants.VOLUME_TYPE_ID} ++ vtype2.update(volume_types[fake_constants.VOLUME_TYPE_ID]) ++ vtypes = {1: vtype1, 2: vtype2} ++ ++ with mock.patch.object(self.driver, 'db', new=MockVolumeDB(vtypes)): + self.driver.create_cloned_volume( + { + 'id': '2', +@@ -790,39 +797,12 @@ class StorPoolTestCase(test.TestCase): + self.assertEqual(21, pool['total_capacity_gb']) + self.assertEqual(5, int(pool['free_capacity_gb'])) + +- self.driver.create_volume( +- {'id': 'cfgrepl1', 'name': 'v1', 'size': 1, +- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) +- self.assertVolumeNames(('cfgrepl1',)) +- v = volumes[volumeName('cfgrepl1')] +- self.assertEqual(3, v['replication']) +- self.assertIsNone(v['template']) +- self.driver.delete_volume({'id': 'cfgrepl1'}) +- + self.driver.configuration.storpool_replication = 2 + stats = self.driver.get_volume_stats(refresh=True) + pool = stats['pools'][0] + self.assertEqual(21, pool['total_capacity_gb']) + self.assertEqual(8, int(pool['free_capacity_gb'])) + +- self.driver.create_volume( +- {'id': 'cfgrepl2', 'name': 'v1', 'size': 1, +- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) +- self.assertVolumeNames(('cfgrepl2',)) +- v = volumes[volumeName('cfgrepl2')] +- self.assertEqual(2, v['replication']) +- self.assertIsNone(v['template']) +- self.driver.delete_volume({'id': 'cfgrepl2'}) +- +- self.driver.create_volume( +- {'id': 'cfgrepl3', 'name': 'v1', 'size': 1, +- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) +- self.assertVolumeNames(('cfgrepl3',)) +- v = volumes[volumeName('cfgrepl3')] +- self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) +- self.driver.delete_volume({'id': 'cfgrepl3'}) +- + self.driver.configuration.storpool_replication = save_repl + + self.assertVolumeNames([]) +@@ -835,17 +815,13 @@ class StorPoolTestCase(test.TestCase): + self.assertDictEqual({}, volumes) + self.assertDictEqual({}, snapshots) + +- save_template = self.driver.configuration.storpool_template +- +- self.driver.configuration.storpool_template = None +- + self.driver.create_volume( + {'id': 'cfgtempl1', 'name': 'v1', 'size': 1, + 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) + self.assertVolumeNames(('cfgtempl1',)) + v = volumes[volumeName('cfgtempl1')] +- self.assertEqual(3, v['replication']) +- self.assertIsNone(v['template']) ++ self.assertNotIn(v, 'replication') ++ self.assertEqual('nvme', v['template']) + self.driver.delete_volume({'id': 'cfgtempl1'}) + + self.driver.create_volume( +@@ -857,7 +833,12 @@ class StorPoolTestCase(test.TestCase): + self.assertEqual('ssd', v['template']) + self.driver.delete_volume({'id': 'cfgtempl2'}) + ++ save_template = self.driver.configuration.storpool_template ++ + self.driver.configuration.storpool_template = 'hdd' ++ save_volume_template = \ ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] ++ del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] + + self.driver.create_volume( + {'id': 'cfgtempl3', 'name': 'v1', 'size': 1, +@@ -867,6 +848,8 @@ class StorPoolTestCase(test.TestCase): + self.assertNotIn('replication', v) + self.assertEqual('hdd', v['template']) + self.driver.delete_volume({'id': 'cfgtempl3'}) ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ ++ save_volume_template + + self.driver.create_volume( + {'id': 'cfgtempl4', 'name': 'v1', 'size': 1, +@@ -884,11 +867,7 @@ class StorPoolTestCase(test.TestCase): + self.assertDictEqual({}, snapshots) + + @ddt.data( +- # No volume type at all: 'default' +- ('default', None), +- # No storpool_template in the type extra specs: 'default' +- ('default', {'id': fake_constants.VOLUME_TYPE_ID}), +- # An actual template specified: 'template_*' ++ ('template_nvme', {'id': fake_constants.VOLUME_TYPE_ID}), + ('template_ssd', {'id': fake_constants.VOLUME_TYPE2_ID}), + ('template_hdd', {'id': fake_constants.VOLUME_TYPE3_ID}), + ) +@@ -900,6 +879,30 @@ class StorPoolTestCase(test.TestCase): + 'volume_type': volume_type + })) + ++ @mock_volume_types ++ def test_get_pool_no_extra_spec(self): ++ # No storpool_template in the type extra specs, default to config ++ save_template = \ ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] ++ del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] ++ self.assertEqual('template_default', ++ self.driver.get_pool({ ++ 'volume_type': { ++ 'id': fake_constants.VOLUME_TYPE_ID} ++ })) ++ ++ save_default_template = self.driver.configuration.storpool_template ++ self.driver.configuration.storpool_template = None ++ self.assertEqual('default', ++ self.driver.get_pool({ ++ 'volume_type': { ++ 'id': fake_constants.VOLUME_TYPE_ID} ++ })) ++ ++ self.driver.configuration.storpool_template = save_default_template ++ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ ++ save_template ++ + @mock_volume_types + def test_volume_revert(self): + vol_id = 'rev1' +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index acfabccd6..bb15e7a29 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -69,9 +69,8 @@ storpool_opts = [ + cfg.IntOpt('storpool_replication', + default=3, + help='The default StorPool chain replication value. ' +- 'Used when creating a volume with no specified type if ' +- 'storpool_template is not set. Also used for calculating ' +- 'the apparent free space reported in the stats.'), ++ 'Used for calculating the apparent free space reported in ' ++ 'the stats.'), + ] + + CONF = cfg.CONF +@@ -117,9 +116,11 @@ class StorPoolDriver(driver.VolumeDriver): + StorPool API instead of packages `storpool` and + `storpool.spopenstack` + 2.2.0 - Add iSCSI export support. ++ 2.3.0 - Do not use the option 'storpool_replication' when creating or ++ retyping volumes. + """ + +- VERSION = '2.2.0' ++ VERSION = '2.3.0' + CI_WIKI_NAME = 'StorPool_distributed_storage_CI' + + def __init__(self, *args, **kwargs): +@@ -162,11 +163,12 @@ class StorPoolDriver(driver.VolumeDriver): + template = self._template_from_volume(volume) + + create_request = {'name': name, 'size': size} +- if template is not None: +- create_request['template'] = template +- else: +- create_request['replication'] = \ +- self.configuration.storpool_replication ++ if not template: ++ raise self._backendException( ++ "Cannot create a volume without a configured StorPool" ++ " template.") ++ ++ create_request['template'] = template + + try: + self._sp_api.volume_create(create_request) +@@ -765,7 +767,6 @@ class StorPoolDriver(driver.VolumeDriver): + return False + + templ = self.configuration.storpool_template +- repl = self.configuration.storpool_replication + if diff['extra_specs']: + # Check for the StorPool extra specs. We intentionally ignore any + # other extra_specs because the cinder scheduler should not even +@@ -784,7 +785,9 @@ class StorPoolDriver(driver.VolumeDriver): + elif templ is not None: + update['template'] = templ + else: +- update['replication'] = repl ++ raise self._backendException( ++ "Cannot retype a volume to a type that is missing" ++ " a configured StorPool template.") + + if update: + name = storpool_utils.os_to_sp_volume_name( +-- +2.43.0 + diff --git a/drivers/cinder/openstack/antelope/patches/18_983034_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch b/drivers/cinder/openstack/antelope/patches/18_983034_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch new file mode 100644 index 0000000..aff60c0 --- /dev/null +++ b/drivers/cinder/openstack/antelope/patches/18_983034_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch @@ -0,0 +1,161 @@ +From 3f6b01afd682733a13763314644631877714b401 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Fri, 19 Dec 2025 22:52:16 +0200 +Subject: StorPool: Use only baseOn when cloning a volume + +Stop creating a transient snapshot when cloning a volume to a different +placement group. + +Change-Id: I6e53df78bcd9226898fdb40d2ae420cbe1f6c9aa +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 17 +--- + cinder/volume/drivers/storpool.py | 79 ++----------------- + 2 files changed, 11 insertions(+), 85 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index af2e22ef8..d2e250353 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -733,8 +733,6 @@ class StorPoolTestCase(test.TestCase): + src_name = 's-none' if src_template is None else 's-' + src_template + dst_name = 'd-none' if dst_template is None else 'd-' + dst_template + +- snap_name = snapshotName('clone', '2') +- + vdata1 = { + 'id': '1', + 'name': src_name, +@@ -763,22 +761,13 @@ class StorPoolTestCase(test.TestCase): + vol2 = volumes[volumeName('2')] + self.assertEqual(vol2['template'], dst_template) + +- if src_template == dst_template: +- self.assertEqual(vol2['baseOn'], volumeName('1')) +- self.assertNotIn('parent', vol2) +- +- self.assertDictEqual({}, snapshots) +- else: +- self.assertNotIn('baseOn', vol2) +- self.assertEqual(vol2['parent'], snap_name) ++ self.assertEqual(vol2['baseOn'], volumeName('1')) ++ self.assertNotIn('parent', vol2) + +- self.assertSnapshotNames((('clone', '2'),)) +- self.assertEqual(snapshots[snap_name]['template'], dst_template) ++ self.assertDictEqual({}, snapshots) + + self.driver.delete_volume({'id': '1'}) + self.driver.delete_volume({'id': '2'}) +- if src_template != dst_template: +- del snapshots[snap_name] + + self.assertDictEqual({}, volumes) + self.assertDictEqual({}, snapshots) +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index bb15e7a29..67b6d419d 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -21,11 +21,9 @@ import platform + from os_brick.initiator import storpool_utils + from oslo_config import cfg + from oslo_log import log as logging +-from oslo_utils import excutils + from oslo_utils import units + + from cinder.common import constants +-from cinder import context + from cinder import exception + from cinder.i18n import _ + from cinder import interface +@@ -553,79 +551,18 @@ class StorPoolDriver(driver.VolumeDriver): + size = int(volume['size']) * units.Gi + volname = storpool_utils.os_to_sp_volume_name( + self._volume_prefix, volume['id']) +- +- src_volume = self.db.volume_get( +- context.get_admin_context(), +- src_vref['id'], +- ) +- src_template = self._template_from_volume(src_volume) +- + template = self._template_from_volume(volume) +- LOG.debug('clone volume id %(vol_id)r template %(template)r', { +- 'vol_id': volume['id'], +- 'template': template, +- }) +- if template == src_template: +- LOG.info('Using baseOn to clone a volume into the same template') +- try: +- self._sp_api.volume_create({ +- 'name': volname, +- 'size': size, +- 'baseOn': refname, +- }) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) + +- return None +- +- snapname = storpool_utils.os_to_sp_snapshot_name( +- self._volume_prefix, 'clone', volume['id']) +- LOG.info( +- 'A transient snapshot for a %(src)s -> %(dst)s template change', +- {'src': src_template, 'dst': template}) ++ LOG.info('Using baseOn to clone the volume %s', refname) + try: +- self._sp_api.snapshot_create(refname, {'name': snapname}) ++ self._sp_api.volume_create({ ++ 'name': volname, ++ 'size': size, ++ 'baseOn': refname, ++ 'template': template ++ }) + except storpool_utils.StorPoolAPIError as e: +- if e.name != 'objectExists': +- raise self._backendException(e) +- +- try: +- try: +- self._sp_api.snapshot_update( +- snapname, +- {'template': template}, +- ) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) +- +- try: +- self._sp_api.volume_create({ +- 'name': volname, +- 'size': size, +- 'parent': snapname +- }) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) +- +- try: +- self._sp_api.snapshot_update( +- snapname, +- {'tags': {'transient': '1.0'}}, +- ) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) +- except Exception: +- with excutils.save_and_reraise_exception(): +- try: +- LOG.warning( +- 'Something went wrong, removing the transient snapshot' +- ) +- self._sp_api.snapshot_delete(snapname) +- except storpool_utils.StorPoolAPIError as e: +- LOG.error( +- 'Could not delete the %(name)s snapshot: %(err)s', +- {'name': snapname, 'err': str(e)} +- ) ++ raise self._backendException(e) + + def create_export(self, context, volume, connector): + if self._connector_wants_iscsi(connector): +-- +2.43.0 + diff --git a/drivers/cinder/openstack/antelope/patches/18_983034_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch b/drivers/cinder/openstack/antelope/patches/18_983034_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch new file mode 100644 index 0000000..aff60c0 --- /dev/null +++ b/drivers/cinder/openstack/antelope/patches/18_983034_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch @@ -0,0 +1,161 @@ +From 3f6b01afd682733a13763314644631877714b401 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Fri, 19 Dec 2025 22:52:16 +0200 +Subject: StorPool: Use only baseOn when cloning a volume + +Stop creating a transient snapshot when cloning a volume to a different +placement group. + +Change-Id: I6e53df78bcd9226898fdb40d2ae420cbe1f6c9aa +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 17 +--- + cinder/volume/drivers/storpool.py | 79 ++----------------- + 2 files changed, 11 insertions(+), 85 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index af2e22ef8..d2e250353 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -733,8 +733,6 @@ class StorPoolTestCase(test.TestCase): + src_name = 's-none' if src_template is None else 's-' + src_template + dst_name = 'd-none' if dst_template is None else 'd-' + dst_template + +- snap_name = snapshotName('clone', '2') +- + vdata1 = { + 'id': '1', + 'name': src_name, +@@ -763,22 +761,13 @@ class StorPoolTestCase(test.TestCase): + vol2 = volumes[volumeName('2')] + self.assertEqual(vol2['template'], dst_template) + +- if src_template == dst_template: +- self.assertEqual(vol2['baseOn'], volumeName('1')) +- self.assertNotIn('parent', vol2) +- +- self.assertDictEqual({}, snapshots) +- else: +- self.assertNotIn('baseOn', vol2) +- self.assertEqual(vol2['parent'], snap_name) ++ self.assertEqual(vol2['baseOn'], volumeName('1')) ++ self.assertNotIn('parent', vol2) + +- self.assertSnapshotNames((('clone', '2'),)) +- self.assertEqual(snapshots[snap_name]['template'], dst_template) ++ self.assertDictEqual({}, snapshots) + + self.driver.delete_volume({'id': '1'}) + self.driver.delete_volume({'id': '2'}) +- if src_template != dst_template: +- del snapshots[snap_name] + + self.assertDictEqual({}, volumes) + self.assertDictEqual({}, snapshots) +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index bb15e7a29..67b6d419d 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -21,11 +21,9 @@ import platform + from os_brick.initiator import storpool_utils + from oslo_config import cfg + from oslo_log import log as logging +-from oslo_utils import excutils + from oslo_utils import units + + from cinder.common import constants +-from cinder import context + from cinder import exception + from cinder.i18n import _ + from cinder import interface +@@ -553,79 +551,18 @@ class StorPoolDriver(driver.VolumeDriver): + size = int(volume['size']) * units.Gi + volname = storpool_utils.os_to_sp_volume_name( + self._volume_prefix, volume['id']) +- +- src_volume = self.db.volume_get( +- context.get_admin_context(), +- src_vref['id'], +- ) +- src_template = self._template_from_volume(src_volume) +- + template = self._template_from_volume(volume) +- LOG.debug('clone volume id %(vol_id)r template %(template)r', { +- 'vol_id': volume['id'], +- 'template': template, +- }) +- if template == src_template: +- LOG.info('Using baseOn to clone a volume into the same template') +- try: +- self._sp_api.volume_create({ +- 'name': volname, +- 'size': size, +- 'baseOn': refname, +- }) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) + +- return None +- +- snapname = storpool_utils.os_to_sp_snapshot_name( +- self._volume_prefix, 'clone', volume['id']) +- LOG.info( +- 'A transient snapshot for a %(src)s -> %(dst)s template change', +- {'src': src_template, 'dst': template}) ++ LOG.info('Using baseOn to clone the volume %s', refname) + try: +- self._sp_api.snapshot_create(refname, {'name': snapname}) ++ self._sp_api.volume_create({ ++ 'name': volname, ++ 'size': size, ++ 'baseOn': refname, ++ 'template': template ++ }) + except storpool_utils.StorPoolAPIError as e: +- if e.name != 'objectExists': +- raise self._backendException(e) +- +- try: +- try: +- self._sp_api.snapshot_update( +- snapname, +- {'template': template}, +- ) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) +- +- try: +- self._sp_api.volume_create({ +- 'name': volname, +- 'size': size, +- 'parent': snapname +- }) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) +- +- try: +- self._sp_api.snapshot_update( +- snapname, +- {'tags': {'transient': '1.0'}}, +- ) +- except storpool_utils.StorPoolAPIError as e: +- raise self._backendException(e) +- except Exception: +- with excutils.save_and_reraise_exception(): +- try: +- LOG.warning( +- 'Something went wrong, removing the transient snapshot' +- ) +- self._sp_api.snapshot_delete(snapname) +- except storpool_utils.StorPoolAPIError as e: +- LOG.error( +- 'Could not delete the %(name)s snapshot: %(err)s', +- {'name': snapname, 'err': str(e)} +- ) ++ raise self._backendException(e) + + def create_export(self, context, volume, connector): + if self._connector_wants_iscsi(connector): +-- +2.43.0 + diff --git a/drivers/cinder/openstack/antelope/patches/19_983071_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.patch b/drivers/cinder/openstack/antelope/patches/19_983071_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.patch new file mode 100644 index 0000000..77dc627 --- /dev/null +++ b/drivers/cinder/openstack/antelope/patches/19_983071_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.patch @@ -0,0 +1,152 @@ +From 680ad2216a479737d9c2115b6e0541bada29fae7 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Wed, 1 Apr 2026 15:15:48 +0300 +Subject: StorPool: Use template to set tag 'qc' instead of template + +Change-Id: Ie5431980ddec9e897cd04be9f72b016eece8c3c9 +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 22 +++++++++---------- + cinder/volume/drivers/storpool.py | 10 +++++---- + 2 files changed, 17 insertions(+), 15 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index d2e250353..ecbf55b77 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -566,7 +566,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(1 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + caught = False +@@ -588,7 +588,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, +@@ -597,7 +597,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2')) + v = volumes[volumeName('2')] + self.assertEqual(3 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -606,7 +606,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3')) + v = volumes[volumeName('3')] + self.assertEqual(4 * units.Gi, v['size']) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -615,13 +615,13 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3', '4')) + v = volumes[volumeName('4')] + self.assertEqual(5 * units.Gi, v['size']) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + # Make sure the dictionary is not corrupted somehow... + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + for vid in ('1', '2', '3', '4'): +@@ -759,7 +759,7 @@ class StorPoolTestCase(test.TestCase): + self.driver.create_cloned_volume(vdata2, {'id': '1'}) + self.assertVolumeNames(('1', '2')) + vol2 = volumes[volumeName('2')] +- self.assertEqual(vol2['template'], dst_template) ++ self.assertEqual(vol2['tags']['qc'], dst_template) + + self.assertEqual(vol2['baseOn'], volumeName('1')) + self.assertNotIn('parent', vol2) +@@ -810,7 +810,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl1',)) + v = volumes[volumeName('cfgtempl1')] + self.assertNotIn(v, 'replication') +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl1'}) + + self.driver.create_volume( +@@ -819,7 +819,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl2',)) + v = volumes[volumeName('cfgtempl2')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl2'}) + + save_template = self.driver.configuration.storpool_template +@@ -835,7 +835,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl3',)) + v = volumes[volumeName('cfgtempl3')] + self.assertNotIn('replication', v) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl3'}) + volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ + save_volume_template +@@ -846,7 +846,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl4',)) + v = volumes[volumeName('cfgtempl4')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl4'}) + + self.driver.configuration.storpool_template = save_template +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index 67b6d419d..9001fee54 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -166,7 +166,7 @@ class StorPoolDriver(driver.VolumeDriver): + "Cannot create a volume without a configured StorPool" + " template.") + +- create_request['template'] = template ++ create_request['tags'] = {'qc': template} + + try: + self._sp_api.volume_create(create_request) +@@ -559,7 +559,9 @@ class StorPoolDriver(driver.VolumeDriver): + 'name': volname, + 'size': size, + 'baseOn': refname, +- 'template': template ++ 'tags': { ++ 'qc': template ++ } + }) + except storpool_utils.StorPoolAPIError as e: + raise self._backendException(e) +@@ -718,9 +720,9 @@ class StorPoolDriver(driver.VolumeDriver): + v = diff['extra_specs'].get('storpool_template') + if v[0] != v[1]: + if v[1] is not None: +- update['template'] = v[1] ++ update['tags'] = {'qc': v[1]} + elif templ is not None: +- update['template'] = templ ++ update['tags'] = {'qc': templ} + else: + raise self._backendException( + "Cannot retype a volume to a type that is missing" +-- +2.43.0 + diff --git a/drivers/cinder/openstack/antelope/patches/19_983071_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.stripped.patch b/drivers/cinder/openstack/antelope/patches/19_983071_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.stripped.patch new file mode 100644 index 0000000..77dc627 --- /dev/null +++ b/drivers/cinder/openstack/antelope/patches/19_983071_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.stripped.patch @@ -0,0 +1,152 @@ +From 680ad2216a479737d9c2115b6e0541bada29fae7 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Wed, 1 Apr 2026 15:15:48 +0300 +Subject: StorPool: Use template to set tag 'qc' instead of template + +Change-Id: Ie5431980ddec9e897cd04be9f72b016eece8c3c9 +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 22 +++++++++---------- + cinder/volume/drivers/storpool.py | 10 +++++---- + 2 files changed, 17 insertions(+), 15 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index d2e250353..ecbf55b77 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -566,7 +566,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(1 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + caught = False +@@ -588,7 +588,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, +@@ -597,7 +597,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2')) + v = volumes[volumeName('2')] + self.assertEqual(3 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -606,7 +606,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3')) + v = volumes[volumeName('3')] + self.assertEqual(4 * units.Gi, v['size']) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -615,13 +615,13 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3', '4')) + v = volumes[volumeName('4')] + self.assertEqual(5 * units.Gi, v['size']) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + # Make sure the dictionary is not corrupted somehow... + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + for vid in ('1', '2', '3', '4'): +@@ -759,7 +759,7 @@ class StorPoolTestCase(test.TestCase): + self.driver.create_cloned_volume(vdata2, {'id': '1'}) + self.assertVolumeNames(('1', '2')) + vol2 = volumes[volumeName('2')] +- self.assertEqual(vol2['template'], dst_template) ++ self.assertEqual(vol2['tags']['qc'], dst_template) + + self.assertEqual(vol2['baseOn'], volumeName('1')) + self.assertNotIn('parent', vol2) +@@ -810,7 +810,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl1',)) + v = volumes[volumeName('cfgtempl1')] + self.assertNotIn(v, 'replication') +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl1'}) + + self.driver.create_volume( +@@ -819,7 +819,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl2',)) + v = volumes[volumeName('cfgtempl2')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl2'}) + + save_template = self.driver.configuration.storpool_template +@@ -835,7 +835,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl3',)) + v = volumes[volumeName('cfgtempl3')] + self.assertNotIn('replication', v) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl3'}) + volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ + save_volume_template +@@ -846,7 +846,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl4',)) + v = volumes[volumeName('cfgtempl4')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl4'}) + + self.driver.configuration.storpool_template = save_template +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index 67b6d419d..9001fee54 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -166,7 +166,7 @@ class StorPoolDriver(driver.VolumeDriver): + "Cannot create a volume without a configured StorPool" + " template.") + +- create_request['template'] = template ++ create_request['tags'] = {'qc': template} + + try: + self._sp_api.volume_create(create_request) +@@ -559,7 +559,9 @@ class StorPoolDriver(driver.VolumeDriver): + 'name': volname, + 'size': size, + 'baseOn': refname, +- 'template': template ++ 'tags': { ++ 'qc': template ++ } + }) + except storpool_utils.StorPoolAPIError as e: + raise self._backendException(e) +@@ -718,9 +720,9 @@ class StorPoolDriver(driver.VolumeDriver): + v = diff['extra_specs'].get('storpool_template') + if v[0] != v[1]: + if v[1] is not None: +- update['template'] = v[1] ++ update['tags'] = {'qc': v[1]} + elif templ is not None: +- update['template'] = templ ++ update['tags'] = {'qc': templ} + else: + raise self._backendException( + "Cannot retype a volume to a type that is missing" +-- +2.43.0 + diff --git a/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.patch b/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.patch deleted file mode 100644 index eb30556..0000000 --- a/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.patch +++ /dev/null @@ -1,615 +0,0 @@ -From 3c81e39d86d961adccf9371f79b1245e93231a14 Mon Sep 17 00:00:00 2001 -From: Biser Milanov -Date: Sat, 20 Dec 2025 00:10:13 +0200 -Subject: StorPool: Add the storpool_qos option and deprecate the - storpool_template one - -Change-Id: I691e8f7a3e25d347ed774f9ffe918cc62870cd2c -Signed-off-by: Biser Milanov ---- - .../unit/volume/drivers/test_storpool.py | 308 +++++++++++++++--- - cinder/volume/drivers/storpool.py | 94 ++++-- - 2 files changed, 343 insertions(+), 59 deletions(-) - -diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py -index d2e250353..afad57cb5 100644 ---- a/cinder/tests/unit/volume/drivers/test_storpool.py -+++ b/cinder/tests/unit/volume/drivers/test_storpool.py -@@ -43,9 +43,33 @@ ISCSI_PAT_BOTH = '*riand roh*' - ISCSI_PORTAL_GROUP = 'openstack_pg' - - volume_types = { -- fake_constants.VOLUME_TYPE_ID: {'storpool_template': 'nvme'}, -- fake_constants.VOLUME_TYPE2_ID: {'storpool_template': 'ssd'}, -- fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'} -+ fake_constants.VOLUME_TYPE_ID: { -+ 'id': fake_constants.VOLUME_TYPE_ID, -+ 'storpool_template': 'nvme' -+ }, -+ fake_constants.VOLUME_TYPE2_ID: { -+ 'id': fake_constants.VOLUME_TYPE2_ID, -+ 'storpool_template': 'ssd' -+ }, -+ fake_constants.VOLUME_TYPE3_ID: { -+ 'id': fake_constants.VOLUME_TYPE3_ID, -+ 'storpool_template': 'hdd' -+ }, -+ fake_constants.VOLUME_TYPE4_ID: { -+ 'id': fake_constants.VOLUME_TYPE4_ID, -+ 'storpool_template': 'ssd', -+ 'storpool:qos_class': 'tier1' -+ }, -+ fake_constants.VOLUME_TYPE5_ID: { -+ 'id': fake_constants.VOLUME_TYPE5_ID, -+ 'storpool:qos_class': 'tier2' -+ } -+} -+tier_to_template = { -+ 'tier0': 'nvme', -+ 'tier1': 'ssd', -+ 'tier2': 'hdd', -+ 'tier99': 'hdd' - } - volumes = {} - snapshots = {} -@@ -111,6 +135,12 @@ class MockAPI(object): - def snapshot_delete(self, name): - del snapshots[name] - -+ def volume(self, name): -+ for vol_name, vol in volumes.items(): -+ if vol_name == name: -+ return vol -+ return {} -+ - def volume_create(self, vol): - name = vol['name'] - if name in volumes: -@@ -130,9 +160,14 @@ class MockAPI(object): - if 'template' in vdata: - data['template'] = vdata['template'] - -+ if 'tags' in vol and 'qc' in vol['tags']: -+ data['template'] = tier_to_template[vol['tags']['qc']] -+ - if 'template' not in data: - data['template'] = None - -+ data['templateName'] = data['template'] -+ - volumes[name] = data - - def volume_delete(self, name): -@@ -154,6 +189,12 @@ class MockAPI(object): - if 'size' in data: - volumes[name]['size'] = data['size'] - -+ if 'tags' in data: -+ if 'tags' not in volumes[name]: -+ volumes[name]['tags'] = {} -+ for tag_name, tag_value in data['tags'].items(): -+ volumes[name]['tags'][tag_name] = tag_value -+ - if 'rename' in data and data['rename'] != name: - new_name = data['rename'] - volumes[new_name] = volumes[name] -@@ -449,6 +490,7 @@ class StorPoolTestCase(test.TestCase): - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.volume_backend_name = 'storpool_test' - self.cfg.storpool_template = DEFAULT_STORPOOL_TEMPLATE -+ self.cfg.storpool_qos_class = None - self.cfg.storpool_replication = 3 - self.cfg.storpool_iscsi_cinder_volume = False - self.cfg.storpool_iscsi_export_to = '' -@@ -475,6 +517,17 @@ class StorPoolTestCase(test.TestCase): - get_conf.return_value = test_storpool_utils.SP_CONF - self.driver.check_for_setup_error() - -+ @staticmethod -+ def get_template(volume_type, config): -+ template = None -+ if volume_type.get('storpool:qos_class'): -+ pass -+ elif volume_type.get('storpool_template'): -+ template = volume_type.get('storpool_template') -+ elif config.storpool_template: -+ template = config.storpool_template -+ return template -+ - @ddt.data( - (5, (TypeError, AttributeError)), - ({'no-host': None}, KeyError), -@@ -520,6 +573,19 @@ class StorPoolTestCase(test.TestCase): - self.driver.create_export(None, None, {}) - self.driver.remove_export(None, None) - -+ @ddt.data(*[ -+ { -+ 'name': 'volume-' + str(key), -+ 'id': 'volume-id-' + str(key), -+ 'volume_type': val -+ } -+ for key, val in sorted(volume_types.items())]) -+ @mock_volume_types -+ def test_get_qos_class(self, volume): -+ expected = volume['volume_type'].get('storpool:qos_class', None) -+ actual = self.driver._get_qos_class(volume) -+ self.assertEqual(expected, actual) -+ - def test_stats(self): - stats = self.driver.get_volume_stats(refresh=True) - self.assertEqual('StorPool', stats['vendor_name']) -@@ -728,46 +794,70 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -- src_template = volume_types[src_type['id']].get('storpool_template') -- dst_template = volume_types[dst_type['id']].get('storpool_template') -- src_name = 's-none' if src_template is None else 's-' + src_template -- dst_name = 'd-none' if dst_template is None else 'd-' + dst_template -+ src_type = volume_types[src_type['id']] -+ dst_type = volume_types[dst_type['id']] -+ -+ src_template = StorPoolTestCase.get_template(src_type, self.cfg) -+ dst_template = StorPoolTestCase.get_template(dst_type, self.cfg) - - vdata1 = { -- 'id': '1', -- 'name': src_name, -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, - 'size': 1, - 'volume_type': src_type, - } -- self.assertEqual( -- self.driver._template_from_volume(vdata1), -- src_template) -+ if not src_template: -+ self.assertEqual( -+ self.driver._get_qos_class(vdata1), -+ src_type['storpool:qos_class'] -+ ) -+ else: -+ self.assertEqual( -+ self.driver._template_from_volume(vdata1), -+ src_template) - self.driver.create_volume(vdata1) -- self.assertVolumeNames(('1',)) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) - - vdata2 = { -- 'id': 2, -- 'name': dst_name, -+ 'id': fake_constants.VOLUME2_ID, -+ 'name': fake_constants.VOLUME2_NAME, - 'size': 1, - 'volume_type': dst_type, - } -- self.assertEqual( -- self.driver._template_from_volume(vdata2), -- dst_template) -+ if not dst_template: -+ self.assertEqual( -+ self.driver._get_qos_class(vdata2), -+ dst_type['storpool:qos_class'] -+ ) -+ else: -+ self.assertEqual( -+ self.driver._template_from_volume(vdata2), -+ dst_template) - with mock.patch.object(self.driver, 'db', -- new=MockVolumeDB(vol_types={'1': src_type})): -- self.driver.create_cloned_volume(vdata2, {'id': '1'}) -- self.assertVolumeNames(('1', '2')) -- vol2 = volumes[volumeName('2')] -- self.assertEqual(vol2['template'], dst_template) -+ new=MockVolumeDB( -+ vol_types={ -+ fake_constants.VOLUME_ID: src_type})): -+ self.driver.create_cloned_volume( -+ vdata2, -+ {'id': vdata1['id'], 'volume_type': src_type} -+ ) -+ self.assertVolumeNames( -+ (fake_constants.VOLUME_ID, fake_constants.VOLUME2_ID)) -+ vol2 = volumes[volumeName(fake_constants.VOLUME2_ID)] -+ if not dst_template: -+ self.assertEqual( -+ vol2['tags']['qc'], -+ dst_type['storpool:qos_class']) -+ else: -+ self.assertEqual(vol2['template'], dst_template) - -- self.assertEqual(vol2['baseOn'], volumeName('1')) -+ self.assertEqual(vol2['baseOn'], volumeName(fake_constants.VOLUME_ID)) - self.assertNotIn('parent', vol2) - - self.assertDictEqual({}, snapshots) - -- self.driver.delete_volume({'id': '1'}) -- self.driver.delete_volume({'id': '2'}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME2_ID}) - - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) -@@ -855,42 +945,139 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -+ @mock_volume_types -+ def test_config_qos(self): -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME4_ID, -+ 'name': fake_constants.VOLUME4_NAME, -+ 'size': 1, -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE4_ID} -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME4_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME4_ID)] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE4_ID][ -+ 'storpool:qos_class']] -+ self.assertNotIn(v, 'replication') -+ self.assertEqual(template, v['template']) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE4_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) -+ self.driver.delete_volume({'id': fake_constants.VOLUME4_ID}) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME5_ID, -+ 'name': fake_constants.VOLUME5_NAME, -+ 'size': 1, -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE5_ID} -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME5_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE5_ID][ -+ 'storpool:qos_class']] -+ self.assertNotIn('replication', v) -+ self.assertEqual(template, v['template']) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) -+ self.driver.delete_volume({'id': fake_constants.VOLUME5_ID}) -+ -+ save_default_qos = self.driver.configuration.storpool_qos_class -+ self.driver.configuration.storpool_qos_class = 'tier99' -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, -+ 'size': 1, -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID} -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] -+ self.assertNotIn('replication', v) -+ self.assertEqual(tier_to_template['tier99'], v['template']) -+ self.assertEqual('tier99', v['tags']['qc']) -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) -+ -+ self.driver.configuration.storpool_qos_class = save_default_qos -+ -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ - @ddt.data( -- ('template_nvme', {'id': fake_constants.VOLUME_TYPE_ID}), -- ('template_ssd', {'id': fake_constants.VOLUME_TYPE2_ID}), -- ('template_hdd', {'id': fake_constants.VOLUME_TYPE3_ID}), -+ ('nvme', {'id': fake_constants.VOLUME_TYPE_ID}), -+ ('ssd', {'id': fake_constants.VOLUME_TYPE2_ID}), -+ ('hdd', {'id': fake_constants.VOLUME_TYPE3_ID}), - ) - @ddt.unpack - @mock_volume_types -- def test_get_pool(self, pool, volume_type): -- self.assertEqual(pool, -+ def test_get_pool(self, template, volume_type): -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, -+ 'size': 1, -+ 'volume_type': { -+ 'id': volume_type['id'] -+ } -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] -+ self.assertNotIn(v, 'replication') -+ self.assertEqual(template, v['template']) -+ -+ self.assertEqual(f"template_{template}", - self.driver.get_pool({ -+ 'id': fake_constants.VOLUME_ID, - 'volume_type': volume_type - })) - -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) -+ - @mock_volume_types - def test_get_pool_no_extra_spec(self): - # No storpool_template in the type extra specs, default to config - save_template = \ - volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] - del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- self.assertEqual('template_default', -- self.driver.get_pool({ -- 'volume_type': { -- 'id': fake_constants.VOLUME_TYPE_ID} -- })) - -- save_default_template = self.driver.configuration.storpool_template -- self.driver.configuration.storpool_template = None -- self.assertEqual('default', -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, -+ 'size': 1, -+ 'volume_type': { -+ 'id': fake_constants.VOLUME_TYPE_ID -+ } -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] -+ self.assertNotIn(v, 'replication') -+ self.assertEqual(DEFAULT_STORPOOL_TEMPLATE, v['template']) -+ -+ self.assertEqual('template_default', - self.driver.get_pool({ -+ 'id': fake_constants.VOLUME_ID, - 'volume_type': { - 'id': fake_constants.VOLUME_TYPE_ID} - })) - -- self.driver.configuration.storpool_template = save_default_template - volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ - save_template -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - - @mock_volume_types - def test_volume_revert(self): -@@ -1117,3 +1304,44 @@ class StorPoolTestCase(test.TestCase): - _target_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) - self.assertFalse( - _export_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) -+ -+ @mock_volume_types -+ def test_volume_retype(self): -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME5_ID, -+ 'name': fake_constants.VOLUME5_NAME, -+ 'size': 1, -+ 'volume_type': volume_types[fake_constants.VOLUME_TYPE5_ID] -+ }) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -+ volumes[volumeName(fake_constants.VOLUME5_ID)]['tags']['qc'] -+ ) -+ -+ volume = {'id': fake_constants.VOLUME5_ID} -+ diff = { -+ 'encryption': None, -+ 'extra_specs': { -+ 'storpool:qos_class': [ -+ None, -+ 'tier1' -+ ] -+ } -+ } -+ self.driver.retype(None, volume, None, diff, None) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertEqual('tier1', v['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', 'tier2'] -+ self.driver.retype(None, volume, None, diff, None) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertEqual('tier2', v['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', None] -+ self.driver.retype(None, volume, None, diff, None) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertEqual('', v['tags']['qc']) -diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py -index 67b6d419d..b738baa0e 100644 ---- a/cinder/volume/drivers/storpool.py -+++ b/cinder/volume/drivers/storpool.py -@@ -63,7 +63,13 @@ storpool_opts = [ - help='The portal group to export volumes via iSCSI in.'), - cfg.StrOpt('storpool_template', - default=None, -- help='The StorPool template for volumes with no type.'), -+ help='The StorPool template for volumes with no type.', -+ deprecated_for_removal=True, -+ deprecated_reason='Operators should use the' -+ ' "storpool_qos_class" option instead.'), -+ cfg.StrOpt('storpool_qos_class', -+ default=None, -+ help='The StorPool QoS class for volumes with no QoS class.'), - cfg.IntOpt('storpool_replication', - default=3, - help='The default StorPool chain replication value. ' -@@ -74,6 +80,10 @@ storpool_opts = [ - CONF = cfg.CONF - CONF.register_opts(storpool_opts, group=configuration.SHARED_CONF_GROUP) - -+EXTRA_SPECS_NAMESPACE = 'storpool' -+EXTRA_SPECS_QOS = 'qos_class' -+ES_QOS = EXTRA_SPECS_NAMESPACE + ":" + EXTRA_SPECS_QOS -+ - - class StorPoolConfigurationInvalid(exception.CinderException): - message = _("Invalid parameter %(param)s in the %(section)s section " -@@ -116,9 +126,12 @@ class StorPoolDriver(driver.VolumeDriver): - 2.2.0 - Add iSCSI export support. - 2.3.0 - Do not use the option 'storpool_replication' when creating or - retyping volumes. -+ 2.4.0 - Introduce 'storpool:qos_class' extra spec and the -+ storpool_qos_class option -+ Deprecate the storpool_template option - """ - -- VERSION = '2.3.0' -+ VERSION = '2.4.0' - CI_WIKI_NAME = 'StorPool_distributed_storage_CI' - - def __init__(self, *args, **kwargs): -@@ -138,6 +151,22 @@ class StorPoolDriver(driver.VolumeDriver): - def _backendException(self, e): - return exception.VolumeBackendAPIException(data=str(e)) - -+ def _get_qos_class(self, volume): -+ default = self.configuration.storpool_qos_class -+ volume_type = volume['volume_type'] -+ extra_specs = \ -+ volume_types.get_volume_type_extra_specs(volume_type['id']) -+ if extra_specs and ES_QOS in extra_specs: -+ qos = extra_specs[ES_QOS] -+ LOG.debug( -+ "QoS class extra spec for volume %s exists: %s", -+ volume['id'], qos) -+ return qos -+ LOG.debug( -+ "Extra specs or QoS class setting not found" -+ "; returning default QoS class %s", default) -+ return default -+ - def _template_from_volume(self, volume): - default = self.configuration.storpool_template - vtype = volume['volume_type'] -@@ -147,8 +176,14 @@ class StorPoolDriver(driver.VolumeDriver): - return specs.get('storpool_template', default) - return default - -+ def _template_from_storpool_volume(self, volume): -+ storpool_volume_name = storpool_utils.os_to_sp_volume_name( -+ self._volume_prefix, volume['id']) -+ storpool_volume = self._sp_api.volume(storpool_volume_name) -+ return storpool_volume['templateName'] -+ - def get_pool(self, volume): -- template = self._template_from_volume(volume) -+ template = self._template_from_storpool_volume(volume) - if template is None: - return 'default' - else: -@@ -159,14 +194,18 @@ class StorPoolDriver(driver.VolumeDriver): - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - template = self._template_from_volume(volume) -+ qos_class = self._get_qos_class(volume) - - create_request = {'name': name, 'size': size} -- if not template: -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} -+ elif template: -+ create_request['template'] = template -+ else: - raise self._backendException( - "Cannot create a volume without a configured StorPool" -- " template.") -- -- create_request['template'] = template -+ " qos_class or template.") - - try: - self._sp_api.volume_create(create_request) -@@ -536,12 +575,15 @@ class StorPoolDriver(driver.VolumeDriver): - self._volume_prefix, volume['id']) - name = storpool_utils.os_to_sp_snapshot_name( - self._volume_prefix, 'snap', snapshot['id']) -+ qos_class = self._get_qos_class(volume) -+ -+ create_request = {'name': volname, 'size': size, 'parent': name} -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} -+ - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'parent': name -- }) -+ self._sp_api.volume_create(create_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -551,16 +593,24 @@ class StorPoolDriver(driver.VolumeDriver): - size = int(volume['size']) * units.Gi - volname = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -+ - template = self._template_from_volume(volume) -+ qos_class = self._get_qos_class(volume) -+ -+ clone_request = {'name': volname, 'size': size, 'baseOn': refname} -+ -+ if qos_class: -+ clone_request['tags'] = {'qc': qos_class} -+ elif template: -+ clone_request['template'] = template -+ else: -+ raise self._backendException( -+ "Cannot clone a volume to a type that has no StorPool QoS" -+ " class nor StorPool template configured.") - - LOG.info('Using baseOn to clone the volume %s', refname) - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'baseOn': refname, -- 'template': template -- }) -+ self._sp_api.volume_create(clone_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -725,12 +775,18 @@ class StorPoolDriver(driver.VolumeDriver): - raise self._backendException( - "Cannot retype a volume to a type that is missing" - " a configured StorPool template.") -+ if diff['extra_specs'].get(ES_QOS): -+ v = diff['extra_specs'].get(ES_QOS) -+ if v[1] is None: -+ update['tags'] = {'qc': ''} -+ elif v[0] != v[1]: -+ update['tags'] = {'qc': v[1]} - - if update: - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - try: -- self._sp_api.volume_update(name, **update) -+ self._sp_api.volume_update(name, update) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - --- -2.43.0 - diff --git a/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.stripped.patch b/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.stripped.patch deleted file mode 100644 index eb30556..0000000 --- a/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.stripped.patch +++ /dev/null @@ -1,615 +0,0 @@ -From 3c81e39d86d961adccf9371f79b1245e93231a14 Mon Sep 17 00:00:00 2001 -From: Biser Milanov -Date: Sat, 20 Dec 2025 00:10:13 +0200 -Subject: StorPool: Add the storpool_qos option and deprecate the - storpool_template one - -Change-Id: I691e8f7a3e25d347ed774f9ffe918cc62870cd2c -Signed-off-by: Biser Milanov ---- - .../unit/volume/drivers/test_storpool.py | 308 +++++++++++++++--- - cinder/volume/drivers/storpool.py | 94 ++++-- - 2 files changed, 343 insertions(+), 59 deletions(-) - -diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py -index d2e250353..afad57cb5 100644 ---- a/cinder/tests/unit/volume/drivers/test_storpool.py -+++ b/cinder/tests/unit/volume/drivers/test_storpool.py -@@ -43,9 +43,33 @@ ISCSI_PAT_BOTH = '*riand roh*' - ISCSI_PORTAL_GROUP = 'openstack_pg' - - volume_types = { -- fake_constants.VOLUME_TYPE_ID: {'storpool_template': 'nvme'}, -- fake_constants.VOLUME_TYPE2_ID: {'storpool_template': 'ssd'}, -- fake_constants.VOLUME_TYPE3_ID: {'storpool_template': 'hdd'} -+ fake_constants.VOLUME_TYPE_ID: { -+ 'id': fake_constants.VOLUME_TYPE_ID, -+ 'storpool_template': 'nvme' -+ }, -+ fake_constants.VOLUME_TYPE2_ID: { -+ 'id': fake_constants.VOLUME_TYPE2_ID, -+ 'storpool_template': 'ssd' -+ }, -+ fake_constants.VOLUME_TYPE3_ID: { -+ 'id': fake_constants.VOLUME_TYPE3_ID, -+ 'storpool_template': 'hdd' -+ }, -+ fake_constants.VOLUME_TYPE4_ID: { -+ 'id': fake_constants.VOLUME_TYPE4_ID, -+ 'storpool_template': 'ssd', -+ 'storpool:qos_class': 'tier1' -+ }, -+ fake_constants.VOLUME_TYPE5_ID: { -+ 'id': fake_constants.VOLUME_TYPE5_ID, -+ 'storpool:qos_class': 'tier2' -+ } -+} -+tier_to_template = { -+ 'tier0': 'nvme', -+ 'tier1': 'ssd', -+ 'tier2': 'hdd', -+ 'tier99': 'hdd' - } - volumes = {} - snapshots = {} -@@ -111,6 +135,12 @@ class MockAPI(object): - def snapshot_delete(self, name): - del snapshots[name] - -+ def volume(self, name): -+ for vol_name, vol in volumes.items(): -+ if vol_name == name: -+ return vol -+ return {} -+ - def volume_create(self, vol): - name = vol['name'] - if name in volumes: -@@ -130,9 +160,14 @@ class MockAPI(object): - if 'template' in vdata: - data['template'] = vdata['template'] - -+ if 'tags' in vol and 'qc' in vol['tags']: -+ data['template'] = tier_to_template[vol['tags']['qc']] -+ - if 'template' not in data: - data['template'] = None - -+ data['templateName'] = data['template'] -+ - volumes[name] = data - - def volume_delete(self, name): -@@ -154,6 +189,12 @@ class MockAPI(object): - if 'size' in data: - volumes[name]['size'] = data['size'] - -+ if 'tags' in data: -+ if 'tags' not in volumes[name]: -+ volumes[name]['tags'] = {} -+ for tag_name, tag_value in data['tags'].items(): -+ volumes[name]['tags'][tag_name] = tag_value -+ - if 'rename' in data and data['rename'] != name: - new_name = data['rename'] - volumes[new_name] = volumes[name] -@@ -449,6 +490,7 @@ class StorPoolTestCase(test.TestCase): - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.volume_backend_name = 'storpool_test' - self.cfg.storpool_template = DEFAULT_STORPOOL_TEMPLATE -+ self.cfg.storpool_qos_class = None - self.cfg.storpool_replication = 3 - self.cfg.storpool_iscsi_cinder_volume = False - self.cfg.storpool_iscsi_export_to = '' -@@ -475,6 +517,17 @@ class StorPoolTestCase(test.TestCase): - get_conf.return_value = test_storpool_utils.SP_CONF - self.driver.check_for_setup_error() - -+ @staticmethod -+ def get_template(volume_type, config): -+ template = None -+ if volume_type.get('storpool:qos_class'): -+ pass -+ elif volume_type.get('storpool_template'): -+ template = volume_type.get('storpool_template') -+ elif config.storpool_template: -+ template = config.storpool_template -+ return template -+ - @ddt.data( - (5, (TypeError, AttributeError)), - ({'no-host': None}, KeyError), -@@ -520,6 +573,19 @@ class StorPoolTestCase(test.TestCase): - self.driver.create_export(None, None, {}) - self.driver.remove_export(None, None) - -+ @ddt.data(*[ -+ { -+ 'name': 'volume-' + str(key), -+ 'id': 'volume-id-' + str(key), -+ 'volume_type': val -+ } -+ for key, val in sorted(volume_types.items())]) -+ @mock_volume_types -+ def test_get_qos_class(self, volume): -+ expected = volume['volume_type'].get('storpool:qos_class', None) -+ actual = self.driver._get_qos_class(volume) -+ self.assertEqual(expected, actual) -+ - def test_stats(self): - stats = self.driver.get_volume_stats(refresh=True) - self.assertEqual('StorPool', stats['vendor_name']) -@@ -728,46 +794,70 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -- src_template = volume_types[src_type['id']].get('storpool_template') -- dst_template = volume_types[dst_type['id']].get('storpool_template') -- src_name = 's-none' if src_template is None else 's-' + src_template -- dst_name = 'd-none' if dst_template is None else 'd-' + dst_template -+ src_type = volume_types[src_type['id']] -+ dst_type = volume_types[dst_type['id']] -+ -+ src_template = StorPoolTestCase.get_template(src_type, self.cfg) -+ dst_template = StorPoolTestCase.get_template(dst_type, self.cfg) - - vdata1 = { -- 'id': '1', -- 'name': src_name, -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, - 'size': 1, - 'volume_type': src_type, - } -- self.assertEqual( -- self.driver._template_from_volume(vdata1), -- src_template) -+ if not src_template: -+ self.assertEqual( -+ self.driver._get_qos_class(vdata1), -+ src_type['storpool:qos_class'] -+ ) -+ else: -+ self.assertEqual( -+ self.driver._template_from_volume(vdata1), -+ src_template) - self.driver.create_volume(vdata1) -- self.assertVolumeNames(('1',)) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) - - vdata2 = { -- 'id': 2, -- 'name': dst_name, -+ 'id': fake_constants.VOLUME2_ID, -+ 'name': fake_constants.VOLUME2_NAME, - 'size': 1, - 'volume_type': dst_type, - } -- self.assertEqual( -- self.driver._template_from_volume(vdata2), -- dst_template) -+ if not dst_template: -+ self.assertEqual( -+ self.driver._get_qos_class(vdata2), -+ dst_type['storpool:qos_class'] -+ ) -+ else: -+ self.assertEqual( -+ self.driver._template_from_volume(vdata2), -+ dst_template) - with mock.patch.object(self.driver, 'db', -- new=MockVolumeDB(vol_types={'1': src_type})): -- self.driver.create_cloned_volume(vdata2, {'id': '1'}) -- self.assertVolumeNames(('1', '2')) -- vol2 = volumes[volumeName('2')] -- self.assertEqual(vol2['template'], dst_template) -+ new=MockVolumeDB( -+ vol_types={ -+ fake_constants.VOLUME_ID: src_type})): -+ self.driver.create_cloned_volume( -+ vdata2, -+ {'id': vdata1['id'], 'volume_type': src_type} -+ ) -+ self.assertVolumeNames( -+ (fake_constants.VOLUME_ID, fake_constants.VOLUME2_ID)) -+ vol2 = volumes[volumeName(fake_constants.VOLUME2_ID)] -+ if not dst_template: -+ self.assertEqual( -+ vol2['tags']['qc'], -+ dst_type['storpool:qos_class']) -+ else: -+ self.assertEqual(vol2['template'], dst_template) - -- self.assertEqual(vol2['baseOn'], volumeName('1')) -+ self.assertEqual(vol2['baseOn'], volumeName(fake_constants.VOLUME_ID)) - self.assertNotIn('parent', vol2) - - self.assertDictEqual({}, snapshots) - -- self.driver.delete_volume({'id': '1'}) -- self.driver.delete_volume({'id': '2'}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME2_ID}) - - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) -@@ -855,42 +945,139 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -+ @mock_volume_types -+ def test_config_qos(self): -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME4_ID, -+ 'name': fake_constants.VOLUME4_NAME, -+ 'size': 1, -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE4_ID} -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME4_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME4_ID)] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE4_ID][ -+ 'storpool:qos_class']] -+ self.assertNotIn(v, 'replication') -+ self.assertEqual(template, v['template']) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE4_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) -+ self.driver.delete_volume({'id': fake_constants.VOLUME4_ID}) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME5_ID, -+ 'name': fake_constants.VOLUME5_NAME, -+ 'size': 1, -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE5_ID} -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME5_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE5_ID][ -+ 'storpool:qos_class']] -+ self.assertNotIn('replication', v) -+ self.assertEqual(template, v['template']) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) -+ self.driver.delete_volume({'id': fake_constants.VOLUME5_ID}) -+ -+ save_default_qos = self.driver.configuration.storpool_qos_class -+ self.driver.configuration.storpool_qos_class = 'tier99' -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, -+ 'size': 1, -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID} -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] -+ self.assertNotIn('replication', v) -+ self.assertEqual(tier_to_template['tier99'], v['template']) -+ self.assertEqual('tier99', v['tags']['qc']) -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) -+ -+ self.driver.configuration.storpool_qos_class = save_default_qos -+ -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ - @ddt.data( -- ('template_nvme', {'id': fake_constants.VOLUME_TYPE_ID}), -- ('template_ssd', {'id': fake_constants.VOLUME_TYPE2_ID}), -- ('template_hdd', {'id': fake_constants.VOLUME_TYPE3_ID}), -+ ('nvme', {'id': fake_constants.VOLUME_TYPE_ID}), -+ ('ssd', {'id': fake_constants.VOLUME_TYPE2_ID}), -+ ('hdd', {'id': fake_constants.VOLUME_TYPE3_ID}), - ) - @ddt.unpack - @mock_volume_types -- def test_get_pool(self, pool, volume_type): -- self.assertEqual(pool, -+ def test_get_pool(self, template, volume_type): -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, -+ 'size': 1, -+ 'volume_type': { -+ 'id': volume_type['id'] -+ } -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] -+ self.assertNotIn(v, 'replication') -+ self.assertEqual(template, v['template']) -+ -+ self.assertEqual(f"template_{template}", - self.driver.get_pool({ -+ 'id': fake_constants.VOLUME_ID, - 'volume_type': volume_type - })) - -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) -+ - @mock_volume_types - def test_get_pool_no_extra_spec(self): - # No storpool_template in the type extra specs, default to config - save_template = \ - volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] - del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- self.assertEqual('template_default', -- self.driver.get_pool({ -- 'volume_type': { -- 'id': fake_constants.VOLUME_TYPE_ID} -- })) - -- save_default_template = self.driver.configuration.storpool_template -- self.driver.configuration.storpool_template = None -- self.assertEqual('default', -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, -+ 'size': 1, -+ 'volume_type': { -+ 'id': fake_constants.VOLUME_TYPE_ID -+ } -+ }) -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] -+ self.assertNotIn(v, 'replication') -+ self.assertEqual(DEFAULT_STORPOOL_TEMPLATE, v['template']) -+ -+ self.assertEqual('template_default', - self.driver.get_pool({ -+ 'id': fake_constants.VOLUME_ID, - 'volume_type': { - 'id': fake_constants.VOLUME_TYPE_ID} - })) - -- self.driver.configuration.storpool_template = save_default_template - volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ - save_template -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - - @mock_volume_types - def test_volume_revert(self): -@@ -1117,3 +1304,44 @@ class StorPoolTestCase(test.TestCase): - _target_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) - self.assertFalse( - _export_exists(iapi.get_iscsi_config()['iscsi'], tcase.volume)) -+ -+ @mock_volume_types -+ def test_volume_retype(self): -+ self.assertVolumeNames([]) -+ self.assertDictEqual({}, volumes) -+ self.assertDictEqual({}, snapshots) -+ -+ self.driver.create_volume({ -+ 'id': fake_constants.VOLUME5_ID, -+ 'name': fake_constants.VOLUME5_NAME, -+ 'size': 1, -+ 'volume_type': volume_types[fake_constants.VOLUME_TYPE5_ID] -+ }) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -+ volumes[volumeName(fake_constants.VOLUME5_ID)]['tags']['qc'] -+ ) -+ -+ volume = {'id': fake_constants.VOLUME5_ID} -+ diff = { -+ 'encryption': None, -+ 'extra_specs': { -+ 'storpool:qos_class': [ -+ None, -+ 'tier1' -+ ] -+ } -+ } -+ self.driver.retype(None, volume, None, diff, None) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertEqual('tier1', v['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', 'tier2'] -+ self.driver.retype(None, volume, None, diff, None) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertEqual('tier2', v['tags']['qc']) -+ -+ diff['extra_specs']['storpool:qos_class'] = ['tier1', None] -+ self.driver.retype(None, volume, None, diff, None) -+ v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertEqual('', v['tags']['qc']) -diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py -index 67b6d419d..b738baa0e 100644 ---- a/cinder/volume/drivers/storpool.py -+++ b/cinder/volume/drivers/storpool.py -@@ -63,7 +63,13 @@ storpool_opts = [ - help='The portal group to export volumes via iSCSI in.'), - cfg.StrOpt('storpool_template', - default=None, -- help='The StorPool template for volumes with no type.'), -+ help='The StorPool template for volumes with no type.', -+ deprecated_for_removal=True, -+ deprecated_reason='Operators should use the' -+ ' "storpool_qos_class" option instead.'), -+ cfg.StrOpt('storpool_qos_class', -+ default=None, -+ help='The StorPool QoS class for volumes with no QoS class.'), - cfg.IntOpt('storpool_replication', - default=3, - help='The default StorPool chain replication value. ' -@@ -74,6 +80,10 @@ storpool_opts = [ - CONF = cfg.CONF - CONF.register_opts(storpool_opts, group=configuration.SHARED_CONF_GROUP) - -+EXTRA_SPECS_NAMESPACE = 'storpool' -+EXTRA_SPECS_QOS = 'qos_class' -+ES_QOS = EXTRA_SPECS_NAMESPACE + ":" + EXTRA_SPECS_QOS -+ - - class StorPoolConfigurationInvalid(exception.CinderException): - message = _("Invalid parameter %(param)s in the %(section)s section " -@@ -116,9 +126,12 @@ class StorPoolDriver(driver.VolumeDriver): - 2.2.0 - Add iSCSI export support. - 2.3.0 - Do not use the option 'storpool_replication' when creating or - retyping volumes. -+ 2.4.0 - Introduce 'storpool:qos_class' extra spec and the -+ storpool_qos_class option -+ Deprecate the storpool_template option - """ - -- VERSION = '2.3.0' -+ VERSION = '2.4.0' - CI_WIKI_NAME = 'StorPool_distributed_storage_CI' - - def __init__(self, *args, **kwargs): -@@ -138,6 +151,22 @@ class StorPoolDriver(driver.VolumeDriver): - def _backendException(self, e): - return exception.VolumeBackendAPIException(data=str(e)) - -+ def _get_qos_class(self, volume): -+ default = self.configuration.storpool_qos_class -+ volume_type = volume['volume_type'] -+ extra_specs = \ -+ volume_types.get_volume_type_extra_specs(volume_type['id']) -+ if extra_specs and ES_QOS in extra_specs: -+ qos = extra_specs[ES_QOS] -+ LOG.debug( -+ "QoS class extra spec for volume %s exists: %s", -+ volume['id'], qos) -+ return qos -+ LOG.debug( -+ "Extra specs or QoS class setting not found" -+ "; returning default QoS class %s", default) -+ return default -+ - def _template_from_volume(self, volume): - default = self.configuration.storpool_template - vtype = volume['volume_type'] -@@ -147,8 +176,14 @@ class StorPoolDriver(driver.VolumeDriver): - return specs.get('storpool_template', default) - return default - -+ def _template_from_storpool_volume(self, volume): -+ storpool_volume_name = storpool_utils.os_to_sp_volume_name( -+ self._volume_prefix, volume['id']) -+ storpool_volume = self._sp_api.volume(storpool_volume_name) -+ return storpool_volume['templateName'] -+ - def get_pool(self, volume): -- template = self._template_from_volume(volume) -+ template = self._template_from_storpool_volume(volume) - if template is None: - return 'default' - else: -@@ -159,14 +194,18 @@ class StorPoolDriver(driver.VolumeDriver): - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - template = self._template_from_volume(volume) -+ qos_class = self._get_qos_class(volume) - - create_request = {'name': name, 'size': size} -- if not template: -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} -+ elif template: -+ create_request['template'] = template -+ else: - raise self._backendException( - "Cannot create a volume without a configured StorPool" -- " template.") -- -- create_request['template'] = template -+ " qos_class or template.") - - try: - self._sp_api.volume_create(create_request) -@@ -536,12 +575,15 @@ class StorPoolDriver(driver.VolumeDriver): - self._volume_prefix, volume['id']) - name = storpool_utils.os_to_sp_snapshot_name( - self._volume_prefix, 'snap', snapshot['id']) -+ qos_class = self._get_qos_class(volume) -+ -+ create_request = {'name': volname, 'size': size, 'parent': name} -+ -+ if qos_class: -+ create_request['tags'] = {'qc': qos_class} -+ - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'parent': name -- }) -+ self._sp_api.volume_create(create_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -551,16 +593,24 @@ class StorPoolDriver(driver.VolumeDriver): - size = int(volume['size']) * units.Gi - volname = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -+ - template = self._template_from_volume(volume) -+ qos_class = self._get_qos_class(volume) -+ -+ clone_request = {'name': volname, 'size': size, 'baseOn': refname} -+ -+ if qos_class: -+ clone_request['tags'] = {'qc': qos_class} -+ elif template: -+ clone_request['template'] = template -+ else: -+ raise self._backendException( -+ "Cannot clone a volume to a type that has no StorPool QoS" -+ " class nor StorPool template configured.") - - LOG.info('Using baseOn to clone the volume %s', refname) - try: -- self._sp_api.volume_create({ -- 'name': volname, -- 'size': size, -- 'baseOn': refname, -- 'template': template -- }) -+ self._sp_api.volume_create(clone_request) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - -@@ -725,12 +775,18 @@ class StorPoolDriver(driver.VolumeDriver): - raise self._backendException( - "Cannot retype a volume to a type that is missing" - " a configured StorPool template.") -+ if diff['extra_specs'].get(ES_QOS): -+ v = diff['extra_specs'].get(ES_QOS) -+ if v[1] is None: -+ update['tags'] = {'qc': ''} -+ elif v[0] != v[1]: -+ update['tags'] = {'qc': v[1]} - - if update: - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - try: -- self._sp_api.volume_update(name, **update) -+ self._sp_api.volume_update(name, update) - except storpool_utils.StorPoolAPIError as e: - raise self._backendException(e) - --- -2.43.0 - diff --git a/drivers/cinder/openstack/caracal/patches/17_983406_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.patch b/drivers/cinder/openstack/caracal/patches/17_983406_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.patch new file mode 100644 index 0000000..cfbfa34 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/17_983406_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.patch @@ -0,0 +1,152 @@ +From e4609384e8be7b519972fc660de3a42871cc0957 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Wed, 1 Apr 2026 17:40:42 +0300 +Subject: StorPool: Use template to set tag 'qc' instead of template + +Change-Id: I63591291a39bd28837119cc36fcc16424881829c +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 22 +++++++++---------- + cinder/volume/drivers/storpool.py | 10 +++++---- + 2 files changed, 17 insertions(+), 15 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index d2e250353..ecbf55b77 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -566,7 +566,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(1 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + caught = False +@@ -588,7 +588,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, +@@ -597,7 +597,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2')) + v = volumes[volumeName('2')] + self.assertEqual(3 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -606,7 +606,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3')) + v = volumes[volumeName('3')] + self.assertEqual(4 * units.Gi, v['size']) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -615,13 +615,13 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3', '4')) + v = volumes[volumeName('4')] + self.assertEqual(5 * units.Gi, v['size']) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + # Make sure the dictionary is not corrupted somehow... + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + for vid in ('1', '2', '3', '4'): +@@ -759,7 +759,7 @@ class StorPoolTestCase(test.TestCase): + self.driver.create_cloned_volume(vdata2, {'id': '1'}) + self.assertVolumeNames(('1', '2')) + vol2 = volumes[volumeName('2')] +- self.assertEqual(vol2['template'], dst_template) ++ self.assertEqual(vol2['tags']['qc'], dst_template) + + self.assertEqual(vol2['baseOn'], volumeName('1')) + self.assertNotIn('parent', vol2) +@@ -810,7 +810,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl1',)) + v = volumes[volumeName('cfgtempl1')] + self.assertNotIn(v, 'replication') +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl1'}) + + self.driver.create_volume( +@@ -819,7 +819,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl2',)) + v = volumes[volumeName('cfgtempl2')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl2'}) + + save_template = self.driver.configuration.storpool_template +@@ -835,7 +835,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl3',)) + v = volumes[volumeName('cfgtempl3')] + self.assertNotIn('replication', v) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl3'}) + volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ + save_volume_template +@@ -846,7 +846,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl4',)) + v = volumes[volumeName('cfgtempl4')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl4'}) + + self.driver.configuration.storpool_template = save_template +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index 67b6d419d..9001fee54 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -166,7 +166,7 @@ class StorPoolDriver(driver.VolumeDriver): + "Cannot create a volume without a configured StorPool" + " template.") + +- create_request['template'] = template ++ create_request['tags'] = {'qc': template} + + try: + self._sp_api.volume_create(create_request) +@@ -559,7 +559,9 @@ class StorPoolDriver(driver.VolumeDriver): + 'name': volname, + 'size': size, + 'baseOn': refname, +- 'template': template ++ 'tags': { ++ 'qc': template ++ } + }) + except storpool_utils.StorPoolAPIError as e: + raise self._backendException(e) +@@ -718,9 +720,9 @@ class StorPoolDriver(driver.VolumeDriver): + v = diff['extra_specs'].get('storpool_template') + if v[0] != v[1]: + if v[1] is not None: +- update['template'] = v[1] ++ update['tags'] = {'qc': v[1]} + elif templ is not None: +- update['template'] = templ ++ update['tags'] = {'qc': templ} + else: + raise self._backendException( + "Cannot retype a volume to a type that is missing" +-- +2.43.0 + diff --git a/drivers/cinder/openstack/caracal/patches/17_983406_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.stripped.patch b/drivers/cinder/openstack/caracal/patches/17_983406_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.stripped.patch new file mode 100644 index 0000000..cfbfa34 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/17_983406_1_StorPool-Use-template-to-set-tag-qc-instead-of-template.stripped.patch @@ -0,0 +1,152 @@ +From e4609384e8be7b519972fc660de3a42871cc0957 Mon Sep 17 00:00:00 2001 +From: Biser Milanov +Date: Wed, 1 Apr 2026 17:40:42 +0300 +Subject: StorPool: Use template to set tag 'qc' instead of template + +Change-Id: I63591291a39bd28837119cc36fcc16424881829c +Signed-off-by: Biser Milanov +--- + .../unit/volume/drivers/test_storpool.py | 22 +++++++++---------- + cinder/volume/drivers/storpool.py | 10 +++++---- + 2 files changed, 17 insertions(+), 15 deletions(-) + +diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py +index d2e250353..ecbf55b77 100644 +--- a/cinder/tests/unit/volume/drivers/test_storpool.py ++++ b/cinder/tests/unit/volume/drivers/test_storpool.py +@@ -566,7 +566,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(1 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + caught = False +@@ -588,7 +588,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1',)) + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, +@@ -597,7 +597,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2')) + v = volumes[volumeName('2')] + self.assertEqual(3 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -606,7 +606,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3')) + v = volumes[volumeName('3')] + self.assertEqual(4 * units.Gi, v['size']) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + self.driver.create_volume( +@@ -615,13 +615,13 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('1', '2', '3', '4')) + v = volumes[volumeName('4')] + self.assertEqual(5 * units.Gi, v['size']) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + # Make sure the dictionary is not corrupted somehow... + v = volumes[volumeName('1')] + self.assertEqual(2 * units.Gi, v['size']) +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.assertNotIn('replication', v.keys()) + + for vid in ('1', '2', '3', '4'): +@@ -759,7 +759,7 @@ class StorPoolTestCase(test.TestCase): + self.driver.create_cloned_volume(vdata2, {'id': '1'}) + self.assertVolumeNames(('1', '2')) + vol2 = volumes[volumeName('2')] +- self.assertEqual(vol2['template'], dst_template) ++ self.assertEqual(vol2['tags']['qc'], dst_template) + + self.assertEqual(vol2['baseOn'], volumeName('1')) + self.assertNotIn('parent', vol2) +@@ -810,7 +810,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl1',)) + v = volumes[volumeName('cfgtempl1')] + self.assertNotIn(v, 'replication') +- self.assertEqual('nvme', v['template']) ++ self.assertEqual('nvme', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl1'}) + + self.driver.create_volume( +@@ -819,7 +819,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl2',)) + v = volumes[volumeName('cfgtempl2')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl2'}) + + save_template = self.driver.configuration.storpool_template +@@ -835,7 +835,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl3',)) + v = volumes[volumeName('cfgtempl3')] + self.assertNotIn('replication', v) +- self.assertEqual('hdd', v['template']) ++ self.assertEqual('hdd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl3'}) + volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ + save_volume_template +@@ -846,7 +846,7 @@ class StorPoolTestCase(test.TestCase): + self.assertVolumeNames(('cfgtempl4',)) + v = volumes[volumeName('cfgtempl4')] + self.assertNotIn('replication', v) +- self.assertEqual('ssd', v['template']) ++ self.assertEqual('ssd', v['tags']['qc']) + self.driver.delete_volume({'id': 'cfgtempl4'}) + + self.driver.configuration.storpool_template = save_template +diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py +index 67b6d419d..9001fee54 100644 +--- a/cinder/volume/drivers/storpool.py ++++ b/cinder/volume/drivers/storpool.py +@@ -166,7 +166,7 @@ class StorPoolDriver(driver.VolumeDriver): + "Cannot create a volume without a configured StorPool" + " template.") + +- create_request['template'] = template ++ create_request['tags'] = {'qc': template} + + try: + self._sp_api.volume_create(create_request) +@@ -559,7 +559,9 @@ class StorPoolDriver(driver.VolumeDriver): + 'name': volname, + 'size': size, + 'baseOn': refname, +- 'template': template ++ 'tags': { ++ 'qc': template ++ } + }) + except storpool_utils.StorPoolAPIError as e: + raise self._backendException(e) +@@ -718,9 +720,9 @@ class StorPoolDriver(driver.VolumeDriver): + v = diff['extra_specs'].get('storpool_template') + if v[0] != v[1]: + if v[1] is not None: +- update['template'] = v[1] ++ update['tags'] = {'qc': v[1]} + elif templ is not None: +- update['template'] = templ ++ update['tags'] = {'qc': templ} + else: + raise self._backendException( + "Cannot retype a volume to a type that is missing" +-- +2.43.0 + diff --git a/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.patch b/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.patch deleted file mode 100644 index 6925481..0000000 --- a/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.patch +++ /dev/null @@ -1,597 +0,0 @@ -From 1b0c97225cc91f21ee285e395bf6783325143f32 Mon Sep 17 00:00:00 2001 -From: Biser Milanov -Date: Sat, 20 Dec 2025 00:10:41 +0200 -Subject: StorPool: Remove the storpool_template option - -Change-Id: I827c23136b6ae826b7c64b7fcc1b6248d55e1c2d -Signed-off-by: Biser Milanov ---- - .../unit/volume/drivers/test_storpool.py | 235 +++++++----------- - cinder/volume/drivers/storpool.py | 70 ++---- - 2 files changed, 112 insertions(+), 193 deletions(-) - -diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py -index afad57cb5..06608429d 100644 ---- a/cinder/tests/unit/volume/drivers/test_storpool.py -+++ b/cinder/tests/unit/volume/drivers/test_storpool.py -@@ -33,8 +33,6 @@ from cinder.tests.unit import test - from cinder.volume import configuration as conf - from cinder.volume.drivers import storpool as driver - --DEFAULT_STORPOOL_TEMPLATE = 'default' -- - ISCSI_IQN_OURS = 'beleriand' - ISCSI_IQN_OTHER = 'rohan' - ISCSI_IQN_THIRD = 'gondor' -@@ -45,25 +43,16 @@ ISCSI_PORTAL_GROUP = 'openstack_pg' - volume_types = { - fake_constants.VOLUME_TYPE_ID: { - 'id': fake_constants.VOLUME_TYPE_ID, -- 'storpool_template': 'nvme' -+ 'storpool:qos_class': 'tier0' - }, - fake_constants.VOLUME_TYPE2_ID: { - 'id': fake_constants.VOLUME_TYPE2_ID, -- 'storpool_template': 'ssd' -+ 'storpool:qos_class': 'tier1' - }, - fake_constants.VOLUME_TYPE3_ID: { - 'id': fake_constants.VOLUME_TYPE3_ID, -- 'storpool_template': 'hdd' -- }, -- fake_constants.VOLUME_TYPE4_ID: { -- 'id': fake_constants.VOLUME_TYPE4_ID, -- 'storpool_template': 'ssd', -- 'storpool:qos_class': 'tier1' -- }, -- fake_constants.VOLUME_TYPE5_ID: { -- 'id': fake_constants.VOLUME_TYPE5_ID, - 'storpool:qos_class': 'tier2' -- } -+ }, - } - tier_to_template = { - 'tier0': 'nvme', -@@ -489,7 +478,6 @@ class StorPoolTestCase(test.TestCase): - - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.volume_backend_name = 'storpool_test' -- self.cfg.storpool_template = DEFAULT_STORPOOL_TEMPLATE - self.cfg.storpool_qos_class = None - self.cfg.storpool_replication = 3 - self.cfg.storpool_iscsi_cinder_volume = False -@@ -582,7 +570,7 @@ class StorPoolTestCase(test.TestCase): - for key, val in sorted(volume_types.items())]) - @mock_volume_types - def test_get_qos_class(self, volume): -- expected = volume['volume_type'].get('storpool:qos_class', None) -+ expected = volume['volume_type']['storpool:qos_class'] - actual = self.driver._get_qos_class(volume) - self.assertEqual(expected, actual) - -@@ -631,9 +619,15 @@ class StorPoolTestCase(test.TestCase): - self.assertCountEqual([volumeName('1')], volumes.keys()) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(1 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - caught = False - try: -@@ -653,42 +647,72 @@ class StorPoolTestCase(test.TestCase): - 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(2 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, - 'volume_type': - {'id': fake_constants.VOLUME_TYPE_ID}}) - self.assertVolumeNames(('1', '2')) - v = volumes[volumeName('2')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(3 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - self.driver.create_volume( - {'id': '3', 'name': 'v2', 'size': 4, - 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) - self.assertVolumeNames(('1', '2', '3')) - v = volumes[volumeName('3')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE2_ID]['storpool:qos_class']] - self.assertEqual(4 * units.Gi, v['size']) -- self.assertEqual('ssd', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE2_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - self.driver.create_volume( - {'id': '4', 'name': 'v2', 'size': 5, - 'volume_type': {'id': fake_constants.VOLUME_TYPE3_ID}}) - self.assertVolumeNames(('1', '2', '3', '4')) - v = volumes[volumeName('4')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE3_ID]['storpool:qos_class']] - self.assertEqual(5 * units.Gi, v['size']) -- self.assertEqual('hdd', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE3_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - # Make sure the dictionary is not corrupted somehow... - v = volumes[volumeName('1')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(2 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - for vid in ('1', '2', '3', '4'): - self.driver.delete_volume({'id': vid}) -@@ -797,24 +821,16 @@ class StorPoolTestCase(test.TestCase): - src_type = volume_types[src_type['id']] - dst_type = volume_types[dst_type['id']] - -- src_template = StorPoolTestCase.get_template(src_type, self.cfg) -- dst_template = StorPoolTestCase.get_template(dst_type, self.cfg) -- - vdata1 = { - 'id': fake_constants.VOLUME_ID, - 'name': fake_constants.VOLUME_NAME, - 'size': 1, - 'volume_type': src_type, - } -- if not src_template: -- self.assertEqual( -- self.driver._get_qos_class(vdata1), -- src_type['storpool:qos_class'] -- ) -- else: -- self.assertEqual( -- self.driver._template_from_volume(vdata1), -- src_template) -+ self.assertEqual( -+ self.driver._get_qos_class(vdata1), -+ src_type['storpool:qos_class'] -+ ) - self.driver.create_volume(vdata1) - self.assertVolumeNames((fake_constants.VOLUME_ID,)) - -@@ -824,15 +840,10 @@ class StorPoolTestCase(test.TestCase): - 'size': 1, - 'volume_type': dst_type, - } -- if not dst_template: -- self.assertEqual( -- self.driver._get_qos_class(vdata2), -- dst_type['storpool:qos_class'] -- ) -- else: -- self.assertEqual( -- self.driver._template_from_volume(vdata2), -- dst_template) -+ self.assertEqual( -+ self.driver._get_qos_class(vdata2), -+ dst_type['storpool:qos_class'] -+ ) - with mock.patch.object(self.driver, 'db', - new=MockVolumeDB( - vol_types={ -@@ -844,13 +855,9 @@ class StorPoolTestCase(test.TestCase): - self.assertVolumeNames( - (fake_constants.VOLUME_ID, fake_constants.VOLUME2_ID)) - vol2 = volumes[volumeName(fake_constants.VOLUME2_ID)] -- if not dst_template: -- self.assertEqual( -- vol2['tags']['qc'], -- dst_type['storpool:qos_class']) -- else: -- self.assertEqual(vol2['template'], dst_template) -- -+ self.assertEqual( -+ vol2['tags']['qc'], -+ dst_type['storpool:qos_class']) - self.assertEqual(vol2['baseOn'], volumeName(fake_constants.VOLUME_ID)) - self.assertNotIn('parent', vol2) - -@@ -888,63 +895,6 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -- @mock_volume_types -- def test_config_template(self): -- self.assertVolumeNames([]) -- self.assertDictEqual({}, volumes) -- self.assertDictEqual({}, snapshots) -- -- self.driver.create_volume( -- {'id': 'cfgtempl1', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -- self.assertVolumeNames(('cfgtempl1',)) -- v = volumes[volumeName('cfgtempl1')] -- self.assertNotIn(v, 'replication') -- self.assertEqual('nvme', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl1'}) -- -- self.driver.create_volume( -- {'id': 'cfgtempl2', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) -- self.assertVolumeNames(('cfgtempl2',)) -- v = volumes[volumeName('cfgtempl2')] -- self.assertNotIn('replication', v) -- self.assertEqual('ssd', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl2'}) -- -- save_template = self.driver.configuration.storpool_template -- -- self.driver.configuration.storpool_template = 'hdd' -- save_volume_template = \ -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- -- self.driver.create_volume( -- {'id': 'cfgtempl3', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -- self.assertVolumeNames(('cfgtempl3',)) -- v = volumes[volumeName('cfgtempl3')] -- self.assertNotIn('replication', v) -- self.assertEqual('hdd', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl3'}) -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ -- save_volume_template -- -- self.driver.create_volume( -- {'id': 'cfgtempl4', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) -- self.assertVolumeNames(('cfgtempl4',)) -- v = volumes[volumeName('cfgtempl4')] -- self.assertNotIn('replication', v) -- self.assertEqual('ssd', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl4'}) -- -- self.driver.configuration.storpool_template = save_template -- -- self.assertVolumeNames([]) -- self.assertDictEqual({}, volumes) -- self.assertDictEqual({}, snapshots) -- - @mock_volume_types - def test_config_qos(self): - self.assertVolumeNames([]) -@@ -952,46 +902,50 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({ -- 'id': fake_constants.VOLUME4_ID, -- 'name': fake_constants.VOLUME4_NAME, -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, - 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE4_ID} -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID} - }) -- self.assertVolumeNames((fake_constants.VOLUME4_ID,)) -- v = volumes[volumeName(fake_constants.VOLUME4_ID)] -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] - template = tier_to_template[ -- volume_types[fake_constants.VOLUME_TYPE4_ID][ -+ volume_types[fake_constants.VOLUME_TYPE_ID][ - 'storpool:qos_class']] - self.assertNotIn(v, 'replication') - self.assertEqual(template, v['template']) - self.assertEqual( -- volume_types[fake_constants.VOLUME_TYPE4_ID]['storpool:qos_class'], -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], - v['tags']['qc'] - ) -- self.driver.delete_volume({'id': fake_constants.VOLUME4_ID}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - - self.driver.create_volume({ -- 'id': fake_constants.VOLUME5_ID, -- 'name': fake_constants.VOLUME5_NAME, -+ 'id': fake_constants.VOLUME2_ID, -+ 'name': fake_constants.VOLUME2_NAME, - 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE5_ID} -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID} - }) -- self.assertVolumeNames((fake_constants.VOLUME5_ID,)) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertVolumeNames((fake_constants.VOLUME2_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME2_ID)] - template = tier_to_template[ -- volume_types[fake_constants.VOLUME_TYPE5_ID][ -+ volume_types[fake_constants.VOLUME_TYPE2_ID][ - 'storpool:qos_class']] - self.assertNotIn('replication', v) - self.assertEqual(template, v['template']) - self.assertEqual( -- volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -+ volume_types[fake_constants.VOLUME_TYPE2_ID]['storpool:qos_class'], - v['tags']['qc'] - ) -- self.driver.delete_volume({'id': fake_constants.VOLUME5_ID}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME2_ID}) - - save_default_qos = self.driver.configuration.storpool_qos_class - self.driver.configuration.storpool_qos_class = 'tier99' - -+ save_qos = volume_types[ -+ fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'] -+ del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'] -+ - self.driver.create_volume({ - 'id': fake_constants.VOLUME_ID, - 'name': fake_constants.VOLUME_NAME, -@@ -1005,6 +959,8 @@ class StorPoolTestCase(test.TestCase): - self.assertEqual('tier99', v['tags']['qc']) - self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - -+ volume_types[ -+ fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'] = save_qos - self.driver.configuration.storpool_qos_class = save_default_qos - - self.assertVolumeNames([]) -@@ -1047,10 +1003,6 @@ class StorPoolTestCase(test.TestCase): - @mock_volume_types - def test_get_pool_no_extra_spec(self): - # No storpool_template in the type extra specs, default to config -- save_template = \ -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) -@@ -1063,20 +1015,20 @@ class StorPoolTestCase(test.TestCase): - 'id': fake_constants.VOLUME_TYPE_ID - } - }) -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertVolumeNames((fake_constants.VOLUME_ID,)) - v = volumes[volumeName(fake_constants.VOLUME_ID)] - self.assertNotIn(v, 'replication') -- self.assertEqual(DEFAULT_STORPOOL_TEMPLATE, v['template']) -+ self.assertEqual(template, v['template']) - -- self.assertEqual('template_default', -+ self.assertEqual(f"template_{template}", - self.driver.get_pool({ - 'id': fake_constants.VOLUME_ID, - 'volume_type': { - 'id': fake_constants.VOLUME_TYPE_ID} - })) - -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ -- save_template - self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - - @mock_volume_types -@@ -1312,17 +1264,17 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({ -- 'id': fake_constants.VOLUME5_ID, -- 'name': fake_constants.VOLUME5_NAME, -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, - 'size': 1, -- 'volume_type': volume_types[fake_constants.VOLUME_TYPE5_ID] -+ 'volume_type': volume_types[fake_constants.VOLUME_TYPE_ID] - }) - self.assertEqual( -- volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -- volumes[volumeName(fake_constants.VOLUME5_ID)]['tags']['qc'] -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ volumes[volumeName(fake_constants.VOLUME_ID)]['tags']['qc'] - ) - -- volume = {'id': fake_constants.VOLUME5_ID} -+ volume = {'id': fake_constants.VOLUME_ID} - diff = { - 'encryption': None, - 'extra_specs': { -@@ -1333,15 +1285,10 @@ class StorPoolTestCase(test.TestCase): - } - } - self.driver.retype(None, volume, None, diff, None) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] - self.assertEqual('tier1', v['tags']['qc']) - - diff['extra_specs']['storpool:qos_class'] = ['tier1', 'tier2'] - self.driver.retype(None, volume, None, diff, None) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] - self.assertEqual('tier2', v['tags']['qc']) -- -- diff['extra_specs']['storpool:qos_class'] = ['tier1', None] -- self.driver.retype(None, volume, None, diff, None) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -- self.assertEqual('', v['tags']['qc']) -diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py -index b738baa0e..6bcb62914 100644 ---- a/cinder/volume/drivers/storpool.py -+++ b/cinder/volume/drivers/storpool.py -@@ -61,12 +61,6 @@ storpool_opts = [ - cfg.StrOpt('storpool_iscsi_portal_group', - default=None, - help='The portal group to export volumes via iSCSI in.'), -- cfg.StrOpt('storpool_template', -- default=None, -- help='The StorPool template for volumes with no type.', -- deprecated_for_removal=True, -- deprecated_reason='Operators should use the' -- ' "storpool_qos_class" option instead.'), - cfg.StrOpt('storpool_qos_class', - default=None, - help='The StorPool QoS class for volumes with no QoS class.'), -@@ -129,9 +123,10 @@ class StorPoolDriver(driver.VolumeDriver): - 2.4.0 - Introduce 'storpool:qos_class' extra spec and the - storpool_qos_class option - Deprecate the storpool_template option -+ 2.5.0 - Remove the storpool_template option - """ - -- VERSION = '2.4.0' -+ VERSION = '2.5.0' - CI_WIKI_NAME = 'StorPool_distributed_storage_CI' - - def __init__(self, *args, **kwargs): -@@ -167,15 +162,6 @@ class StorPoolDriver(driver.VolumeDriver): - "; returning default QoS class %s", default) - return default - -- def _template_from_volume(self, volume): -- default = self.configuration.storpool_template -- vtype = volume['volume_type'] -- if vtype is not None: -- specs = volume_types.get_volume_type_extra_specs(vtype['id']) -- if specs is not None: -- return specs.get('storpool_template', default) -- return default -- - def _template_from_storpool_volume(self, volume): - storpool_volume_name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -@@ -193,19 +179,15 @@ class StorPoolDriver(driver.VolumeDriver): - size = int(volume['size']) * units.Gi - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -- template = self._template_from_volume(volume) - qos_class = self._get_qos_class(volume) - - create_request = {'name': name, 'size': size} - -- if qos_class: -- create_request['tags'] = {'qc': qos_class} -- elif template: -- create_request['template'] = template -- else: -- raise self._backendException( -- "Cannot create a volume without a configured StorPool" -- " qos_class or template.") -+ if not qos_class: -+ raise exception.VolumeBackendAPIException( -+ "Cannot create a volume without a 'qos_class' option set") -+ -+ create_request['tags'] = {'qc': qos_class} - - try: - self._sp_api.volume_create(create_request) -@@ -579,8 +561,11 @@ class StorPoolDriver(driver.VolumeDriver): - - create_request = {'name': volname, 'size': size, 'parent': name} - -- if qos_class: -- create_request['tags'] = {'qc': qos_class} -+ if not qos_class: -+ raise self._backendException( -+ "Cannot create a volume from a snapshot without a qos_class" -+ " set") -+ create_request['tags'] = {'qc': qos_class} - - try: - self._sp_api.volume_create(create_request) -@@ -594,19 +579,15 @@ class StorPoolDriver(driver.VolumeDriver): - volname = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - -- template = self._template_from_volume(volume) - qos_class = self._get_qos_class(volume) - - clone_request = {'name': volname, 'size': size, 'baseOn': refname} - -- if qos_class: -- clone_request['tags'] = {'qc': qos_class} -- elif template: -- clone_request['template'] = template -- else: -+ if not qos_class: - raise self._backendException( -- "Cannot clone a volume to a type that has no StorPool QoS" -- " class nor StorPool template configured.") -+ "Cannot clone a volume without a qos_class set") -+ -+ clone_request['tags'] = {'qc': qos_class} - - LOG.info('Using baseOn to clone the volume %s', refname) - try: -@@ -753,7 +734,6 @@ class StorPoolDriver(driver.VolumeDriver): - LOG.error('Retype of encryption type not supported.') - return False - -- templ = self.configuration.storpool_template - if diff['extra_specs']: - # Check for the StorPool extra specs. We intentionally ignore any - # other extra_specs because the cinder scheduler should not even -@@ -764,22 +744,14 @@ class StorPoolDriver(driver.VolumeDriver): - # Retype of a volume backend not supported yet, - # the volume needs to be migrated. - return False -- if diff['extra_specs'].get('storpool_template'): -- v = diff['extra_specs'].get('storpool_template') -- if v[0] != v[1]: -- if v[1] is not None: -- update['template'] = v[1] -- elif templ is not None: -- update['template'] = templ -- else: -- raise self._backendException( -- "Cannot retype a volume to a type that is missing" -- " a configured StorPool template.") - if diff['extra_specs'].get(ES_QOS): - v = diff['extra_specs'].get(ES_QOS) - if v[1] is None: -- update['tags'] = {'qc': ''} -- elif v[0] != v[1]: -+ LOG.error( -+ 'The destination volume type requires the extra spec' -+ ' "storpool:qos_class" to be set') -+ return False -+ if v[0] != v[1]: - update['tags'] = {'qc': v[1]} - - if update: --- -2.43.0 - diff --git a/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.stripped.patch b/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.stripped.patch deleted file mode 100644 index 6925481..0000000 --- a/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.stripped.patch +++ /dev/null @@ -1,597 +0,0 @@ -From 1b0c97225cc91f21ee285e395bf6783325143f32 Mon Sep 17 00:00:00 2001 -From: Biser Milanov -Date: Sat, 20 Dec 2025 00:10:41 +0200 -Subject: StorPool: Remove the storpool_template option - -Change-Id: I827c23136b6ae826b7c64b7fcc1b6248d55e1c2d -Signed-off-by: Biser Milanov ---- - .../unit/volume/drivers/test_storpool.py | 235 +++++++----------- - cinder/volume/drivers/storpool.py | 70 ++---- - 2 files changed, 112 insertions(+), 193 deletions(-) - -diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py -index afad57cb5..06608429d 100644 ---- a/cinder/tests/unit/volume/drivers/test_storpool.py -+++ b/cinder/tests/unit/volume/drivers/test_storpool.py -@@ -33,8 +33,6 @@ from cinder.tests.unit import test - from cinder.volume import configuration as conf - from cinder.volume.drivers import storpool as driver - --DEFAULT_STORPOOL_TEMPLATE = 'default' -- - ISCSI_IQN_OURS = 'beleriand' - ISCSI_IQN_OTHER = 'rohan' - ISCSI_IQN_THIRD = 'gondor' -@@ -45,25 +43,16 @@ ISCSI_PORTAL_GROUP = 'openstack_pg' - volume_types = { - fake_constants.VOLUME_TYPE_ID: { - 'id': fake_constants.VOLUME_TYPE_ID, -- 'storpool_template': 'nvme' -+ 'storpool:qos_class': 'tier0' - }, - fake_constants.VOLUME_TYPE2_ID: { - 'id': fake_constants.VOLUME_TYPE2_ID, -- 'storpool_template': 'ssd' -+ 'storpool:qos_class': 'tier1' - }, - fake_constants.VOLUME_TYPE3_ID: { - 'id': fake_constants.VOLUME_TYPE3_ID, -- 'storpool_template': 'hdd' -- }, -- fake_constants.VOLUME_TYPE4_ID: { -- 'id': fake_constants.VOLUME_TYPE4_ID, -- 'storpool_template': 'ssd', -- 'storpool:qos_class': 'tier1' -- }, -- fake_constants.VOLUME_TYPE5_ID: { -- 'id': fake_constants.VOLUME_TYPE5_ID, - 'storpool:qos_class': 'tier2' -- } -+ }, - } - tier_to_template = { - 'tier0': 'nvme', -@@ -489,7 +478,6 @@ class StorPoolTestCase(test.TestCase): - - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.volume_backend_name = 'storpool_test' -- self.cfg.storpool_template = DEFAULT_STORPOOL_TEMPLATE - self.cfg.storpool_qos_class = None - self.cfg.storpool_replication = 3 - self.cfg.storpool_iscsi_cinder_volume = False -@@ -582,7 +570,7 @@ class StorPoolTestCase(test.TestCase): - for key, val in sorted(volume_types.items())]) - @mock_volume_types - def test_get_qos_class(self, volume): -- expected = volume['volume_type'].get('storpool:qos_class', None) -+ expected = volume['volume_type']['storpool:qos_class'] - actual = self.driver._get_qos_class(volume) - self.assertEqual(expected, actual) - -@@ -631,9 +619,15 @@ class StorPoolTestCase(test.TestCase): - self.assertCountEqual([volumeName('1')], volumes.keys()) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(1 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - caught = False - try: -@@ -653,42 +647,72 @@ class StorPoolTestCase(test.TestCase): - 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) - self.assertVolumeNames(('1',)) - v = volumes[volumeName('1')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(2 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - self.driver.create_volume({'id': '2', 'name': 'v2', 'size': 3, - 'volume_type': - {'id': fake_constants.VOLUME_TYPE_ID}}) - self.assertVolumeNames(('1', '2')) - v = volumes[volumeName('2')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(3 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - self.driver.create_volume( - {'id': '3', 'name': 'v2', 'size': 4, - 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) - self.assertVolumeNames(('1', '2', '3')) - v = volumes[volumeName('3')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE2_ID]['storpool:qos_class']] - self.assertEqual(4 * units.Gi, v['size']) -- self.assertEqual('ssd', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE2_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - self.driver.create_volume( - {'id': '4', 'name': 'v2', 'size': 5, - 'volume_type': {'id': fake_constants.VOLUME_TYPE3_ID}}) - self.assertVolumeNames(('1', '2', '3', '4')) - v = volumes[volumeName('4')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE3_ID]['storpool:qos_class']] - self.assertEqual(5 * units.Gi, v['size']) -- self.assertEqual('hdd', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE3_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - # Make sure the dictionary is not corrupted somehow... - v = volumes[volumeName('1')] -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertEqual(2 * units.Gi, v['size']) -- self.assertEqual('nvme', v['template']) -+ self.assertEqual(template, v['template']) - self.assertNotIn('replication', v.keys()) -+ self.assertEqual( -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ v['tags']['qc'] -+ ) - - for vid in ('1', '2', '3', '4'): - self.driver.delete_volume({'id': vid}) -@@ -797,24 +821,16 @@ class StorPoolTestCase(test.TestCase): - src_type = volume_types[src_type['id']] - dst_type = volume_types[dst_type['id']] - -- src_template = StorPoolTestCase.get_template(src_type, self.cfg) -- dst_template = StorPoolTestCase.get_template(dst_type, self.cfg) -- - vdata1 = { - 'id': fake_constants.VOLUME_ID, - 'name': fake_constants.VOLUME_NAME, - 'size': 1, - 'volume_type': src_type, - } -- if not src_template: -- self.assertEqual( -- self.driver._get_qos_class(vdata1), -- src_type['storpool:qos_class'] -- ) -- else: -- self.assertEqual( -- self.driver._template_from_volume(vdata1), -- src_template) -+ self.assertEqual( -+ self.driver._get_qos_class(vdata1), -+ src_type['storpool:qos_class'] -+ ) - self.driver.create_volume(vdata1) - self.assertVolumeNames((fake_constants.VOLUME_ID,)) - -@@ -824,15 +840,10 @@ class StorPoolTestCase(test.TestCase): - 'size': 1, - 'volume_type': dst_type, - } -- if not dst_template: -- self.assertEqual( -- self.driver._get_qos_class(vdata2), -- dst_type['storpool:qos_class'] -- ) -- else: -- self.assertEqual( -- self.driver._template_from_volume(vdata2), -- dst_template) -+ self.assertEqual( -+ self.driver._get_qos_class(vdata2), -+ dst_type['storpool:qos_class'] -+ ) - with mock.patch.object(self.driver, 'db', - new=MockVolumeDB( - vol_types={ -@@ -844,13 +855,9 @@ class StorPoolTestCase(test.TestCase): - self.assertVolumeNames( - (fake_constants.VOLUME_ID, fake_constants.VOLUME2_ID)) - vol2 = volumes[volumeName(fake_constants.VOLUME2_ID)] -- if not dst_template: -- self.assertEqual( -- vol2['tags']['qc'], -- dst_type['storpool:qos_class']) -- else: -- self.assertEqual(vol2['template'], dst_template) -- -+ self.assertEqual( -+ vol2['tags']['qc'], -+ dst_type['storpool:qos_class']) - self.assertEqual(vol2['baseOn'], volumeName(fake_constants.VOLUME_ID)) - self.assertNotIn('parent', vol2) - -@@ -888,63 +895,6 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) - -- @mock_volume_types -- def test_config_template(self): -- self.assertVolumeNames([]) -- self.assertDictEqual({}, volumes) -- self.assertDictEqual({}, snapshots) -- -- self.driver.create_volume( -- {'id': 'cfgtempl1', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -- self.assertVolumeNames(('cfgtempl1',)) -- v = volumes[volumeName('cfgtempl1')] -- self.assertNotIn(v, 'replication') -- self.assertEqual('nvme', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl1'}) -- -- self.driver.create_volume( -- {'id': 'cfgtempl2', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) -- self.assertVolumeNames(('cfgtempl2',)) -- v = volumes[volumeName('cfgtempl2')] -- self.assertNotIn('replication', v) -- self.assertEqual('ssd', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl2'}) -- -- save_template = self.driver.configuration.storpool_template -- -- self.driver.configuration.storpool_template = 'hdd' -- save_volume_template = \ -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- -- self.driver.create_volume( -- {'id': 'cfgtempl3', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID}}) -- self.assertVolumeNames(('cfgtempl3',)) -- v = volumes[volumeName('cfgtempl3')] -- self.assertNotIn('replication', v) -- self.assertEqual('hdd', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl3'}) -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ -- save_volume_template -- -- self.driver.create_volume( -- {'id': 'cfgtempl4', 'name': 'v1', 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID}}) -- self.assertVolumeNames(('cfgtempl4',)) -- v = volumes[volumeName('cfgtempl4')] -- self.assertNotIn('replication', v) -- self.assertEqual('ssd', v['template']) -- self.driver.delete_volume({'id': 'cfgtempl4'}) -- -- self.driver.configuration.storpool_template = save_template -- -- self.assertVolumeNames([]) -- self.assertDictEqual({}, volumes) -- self.assertDictEqual({}, snapshots) -- - @mock_volume_types - def test_config_qos(self): - self.assertVolumeNames([]) -@@ -952,46 +902,50 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({ -- 'id': fake_constants.VOLUME4_ID, -- 'name': fake_constants.VOLUME4_NAME, -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, - 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE4_ID} -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE_ID} - }) -- self.assertVolumeNames((fake_constants.VOLUME4_ID,)) -- v = volumes[volumeName(fake_constants.VOLUME4_ID)] -+ self.assertVolumeNames((fake_constants.VOLUME_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] - template = tier_to_template[ -- volume_types[fake_constants.VOLUME_TYPE4_ID][ -+ volume_types[fake_constants.VOLUME_TYPE_ID][ - 'storpool:qos_class']] - self.assertNotIn(v, 'replication') - self.assertEqual(template, v['template']) - self.assertEqual( -- volume_types[fake_constants.VOLUME_TYPE4_ID]['storpool:qos_class'], -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], - v['tags']['qc'] - ) -- self.driver.delete_volume({'id': fake_constants.VOLUME4_ID}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - - self.driver.create_volume({ -- 'id': fake_constants.VOLUME5_ID, -- 'name': fake_constants.VOLUME5_NAME, -+ 'id': fake_constants.VOLUME2_ID, -+ 'name': fake_constants.VOLUME2_NAME, - 'size': 1, -- 'volume_type': {'id': fake_constants.VOLUME_TYPE5_ID} -+ 'volume_type': {'id': fake_constants.VOLUME_TYPE2_ID} - }) -- self.assertVolumeNames((fake_constants.VOLUME5_ID,)) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ self.assertVolumeNames((fake_constants.VOLUME2_ID,)) -+ v = volumes[volumeName(fake_constants.VOLUME2_ID)] - template = tier_to_template[ -- volume_types[fake_constants.VOLUME_TYPE5_ID][ -+ volume_types[fake_constants.VOLUME_TYPE2_ID][ - 'storpool:qos_class']] - self.assertNotIn('replication', v) - self.assertEqual(template, v['template']) - self.assertEqual( -- volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -+ volume_types[fake_constants.VOLUME_TYPE2_ID]['storpool:qos_class'], - v['tags']['qc'] - ) -- self.driver.delete_volume({'id': fake_constants.VOLUME5_ID}) -+ self.driver.delete_volume({'id': fake_constants.VOLUME2_ID}) - - save_default_qos = self.driver.configuration.storpool_qos_class - self.driver.configuration.storpool_qos_class = 'tier99' - -+ save_qos = volume_types[ -+ fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'] -+ del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'] -+ - self.driver.create_volume({ - 'id': fake_constants.VOLUME_ID, - 'name': fake_constants.VOLUME_NAME, -@@ -1005,6 +959,8 @@ class StorPoolTestCase(test.TestCase): - self.assertEqual('tier99', v['tags']['qc']) - self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - -+ volume_types[ -+ fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'] = save_qos - self.driver.configuration.storpool_qos_class = save_default_qos - - self.assertVolumeNames([]) -@@ -1047,10 +1003,6 @@ class StorPoolTestCase(test.TestCase): - @mock_volume_types - def test_get_pool_no_extra_spec(self): - # No storpool_template in the type extra specs, default to config -- save_template = \ -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- del volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] -- - self.assertVolumeNames([]) - self.assertDictEqual({}, volumes) - self.assertDictEqual({}, snapshots) -@@ -1063,20 +1015,20 @@ class StorPoolTestCase(test.TestCase): - 'id': fake_constants.VOLUME_TYPE_ID - } - }) -+ template = tier_to_template[ -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class']] - self.assertVolumeNames((fake_constants.VOLUME_ID,)) - v = volumes[volumeName(fake_constants.VOLUME_ID)] - self.assertNotIn(v, 'replication') -- self.assertEqual(DEFAULT_STORPOOL_TEMPLATE, v['template']) -+ self.assertEqual(template, v['template']) - -- self.assertEqual('template_default', -+ self.assertEqual(f"template_{template}", - self.driver.get_pool({ - 'id': fake_constants.VOLUME_ID, - 'volume_type': { - 'id': fake_constants.VOLUME_TYPE_ID} - })) - -- volume_types[fake_constants.VOLUME_TYPE_ID]['storpool_template'] = \ -- save_template - self.driver.delete_volume({'id': fake_constants.VOLUME_ID}) - - @mock_volume_types -@@ -1312,17 +1264,17 @@ class StorPoolTestCase(test.TestCase): - self.assertDictEqual({}, snapshots) - - self.driver.create_volume({ -- 'id': fake_constants.VOLUME5_ID, -- 'name': fake_constants.VOLUME5_NAME, -+ 'id': fake_constants.VOLUME_ID, -+ 'name': fake_constants.VOLUME_NAME, - 'size': 1, -- 'volume_type': volume_types[fake_constants.VOLUME_TYPE5_ID] -+ 'volume_type': volume_types[fake_constants.VOLUME_TYPE_ID] - }) - self.assertEqual( -- volume_types[fake_constants.VOLUME_TYPE5_ID]['storpool:qos_class'], -- volumes[volumeName(fake_constants.VOLUME5_ID)]['tags']['qc'] -+ volume_types[fake_constants.VOLUME_TYPE_ID]['storpool:qos_class'], -+ volumes[volumeName(fake_constants.VOLUME_ID)]['tags']['qc'] - ) - -- volume = {'id': fake_constants.VOLUME5_ID} -+ volume = {'id': fake_constants.VOLUME_ID} - diff = { - 'encryption': None, - 'extra_specs': { -@@ -1333,15 +1285,10 @@ class StorPoolTestCase(test.TestCase): - } - } - self.driver.retype(None, volume, None, diff, None) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] - self.assertEqual('tier1', v['tags']['qc']) - - diff['extra_specs']['storpool:qos_class'] = ['tier1', 'tier2'] - self.driver.retype(None, volume, None, diff, None) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -+ v = volumes[volumeName(fake_constants.VOLUME_ID)] - self.assertEqual('tier2', v['tags']['qc']) -- -- diff['extra_specs']['storpool:qos_class'] = ['tier1', None] -- self.driver.retype(None, volume, None, diff, None) -- v = volumes[volumeName(fake_constants.VOLUME5_ID)] -- self.assertEqual('', v['tags']['qc']) -diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py -index b738baa0e..6bcb62914 100644 ---- a/cinder/volume/drivers/storpool.py -+++ b/cinder/volume/drivers/storpool.py -@@ -61,12 +61,6 @@ storpool_opts = [ - cfg.StrOpt('storpool_iscsi_portal_group', - default=None, - help='The portal group to export volumes via iSCSI in.'), -- cfg.StrOpt('storpool_template', -- default=None, -- help='The StorPool template for volumes with no type.', -- deprecated_for_removal=True, -- deprecated_reason='Operators should use the' -- ' "storpool_qos_class" option instead.'), - cfg.StrOpt('storpool_qos_class', - default=None, - help='The StorPool QoS class for volumes with no QoS class.'), -@@ -129,9 +123,10 @@ class StorPoolDriver(driver.VolumeDriver): - 2.4.0 - Introduce 'storpool:qos_class' extra spec and the - storpool_qos_class option - Deprecate the storpool_template option -+ 2.5.0 - Remove the storpool_template option - """ - -- VERSION = '2.4.0' -+ VERSION = '2.5.0' - CI_WIKI_NAME = 'StorPool_distributed_storage_CI' - - def __init__(self, *args, **kwargs): -@@ -167,15 +162,6 @@ class StorPoolDriver(driver.VolumeDriver): - "; returning default QoS class %s", default) - return default - -- def _template_from_volume(self, volume): -- default = self.configuration.storpool_template -- vtype = volume['volume_type'] -- if vtype is not None: -- specs = volume_types.get_volume_type_extra_specs(vtype['id']) -- if specs is not None: -- return specs.get('storpool_template', default) -- return default -- - def _template_from_storpool_volume(self, volume): - storpool_volume_name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -@@ -193,19 +179,15 @@ class StorPoolDriver(driver.VolumeDriver): - size = int(volume['size']) * units.Gi - name = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) -- template = self._template_from_volume(volume) - qos_class = self._get_qos_class(volume) - - create_request = {'name': name, 'size': size} - -- if qos_class: -- create_request['tags'] = {'qc': qos_class} -- elif template: -- create_request['template'] = template -- else: -- raise self._backendException( -- "Cannot create a volume without a configured StorPool" -- " qos_class or template.") -+ if not qos_class: -+ raise exception.VolumeBackendAPIException( -+ "Cannot create a volume without a 'qos_class' option set") -+ -+ create_request['tags'] = {'qc': qos_class} - - try: - self._sp_api.volume_create(create_request) -@@ -579,8 +561,11 @@ class StorPoolDriver(driver.VolumeDriver): - - create_request = {'name': volname, 'size': size, 'parent': name} - -- if qos_class: -- create_request['tags'] = {'qc': qos_class} -+ if not qos_class: -+ raise self._backendException( -+ "Cannot create a volume from a snapshot without a qos_class" -+ " set") -+ create_request['tags'] = {'qc': qos_class} - - try: - self._sp_api.volume_create(create_request) -@@ -594,19 +579,15 @@ class StorPoolDriver(driver.VolumeDriver): - volname = storpool_utils.os_to_sp_volume_name( - self._volume_prefix, volume['id']) - -- template = self._template_from_volume(volume) - qos_class = self._get_qos_class(volume) - - clone_request = {'name': volname, 'size': size, 'baseOn': refname} - -- if qos_class: -- clone_request['tags'] = {'qc': qos_class} -- elif template: -- clone_request['template'] = template -- else: -+ if not qos_class: - raise self._backendException( -- "Cannot clone a volume to a type that has no StorPool QoS" -- " class nor StorPool template configured.") -+ "Cannot clone a volume without a qos_class set") -+ -+ clone_request['tags'] = {'qc': qos_class} - - LOG.info('Using baseOn to clone the volume %s', refname) - try: -@@ -753,7 +734,6 @@ class StorPoolDriver(driver.VolumeDriver): - LOG.error('Retype of encryption type not supported.') - return False - -- templ = self.configuration.storpool_template - if diff['extra_specs']: - # Check for the StorPool extra specs. We intentionally ignore any - # other extra_specs because the cinder scheduler should not even -@@ -764,22 +744,14 @@ class StorPoolDriver(driver.VolumeDriver): - # Retype of a volume backend not supported yet, - # the volume needs to be migrated. - return False -- if diff['extra_specs'].get('storpool_template'): -- v = diff['extra_specs'].get('storpool_template') -- if v[0] != v[1]: -- if v[1] is not None: -- update['template'] = v[1] -- elif templ is not None: -- update['template'] = templ -- else: -- raise self._backendException( -- "Cannot retype a volume to a type that is missing" -- " a configured StorPool template.") - if diff['extra_specs'].get(ES_QOS): - v = diff['extra_specs'].get(ES_QOS) - if v[1] is None: -- update['tags'] = {'qc': ''} -- elif v[0] != v[1]: -+ LOG.error( -+ 'The destination volume type requires the extra spec' -+ ' "storpool:qos_class" to be set') -+ return False -+ if v[0] != v[1]: - update['tags'] = {'qc': v[1]} - - if update: --- -2.43.0 -