From 522dc50ab3740a000183b297c70b96e4ec50a443 Mon Sep 17 00:00:00 2001 From: Biser Milanov <122020836+sp-bmilanov@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:36:30 +0200 Subject: [PATCH] cinder/zed,caracal: Update the QoS class functionality --- defs/components.json | 64 +- ...class-extra-spec-to-the-StorPool-API.patch | 484 -------------- ...ra-spec-to-the-StorPool-API.stripped.patch | 467 ------------- ...on-when-creating-or-retyping-volumes.patch | 297 +++++++++ ...reating-or-retyping-volumes.stripped.patch | 297 +++++++++ ...se-only-baseOn-when-cloning-a-volume.patch | 161 +++++ ...aseOn-when-cloning-a-volume.stripped.patch | 161 +++++ ...-deprecate-the-storpool_template-one.patch | 615 ++++++++++++++++++ ...e-the-storpool_template-one.stripped.patch | 615 ++++++++++++++++++ ...-Remove-the-storpool_template-option.patch | 597 +++++++++++++++++ ...he-storpool_template-option.stripped.patch | 597 +++++++++++++++++ drivers/cinder/openstack/zed/storpool.py | 168 ++--- drivers/nova/openstack/zed/driver.py | 3 +- 13 files changed, 3460 insertions(+), 1066 deletions(-) delete mode 100644 drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch delete mode 100644 drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch create mode 100644 drivers/cinder/openstack/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch create mode 100644 drivers/cinder/openstack/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch create mode 100644 drivers/cinder/openstack/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch create mode 100644 drivers/cinder/openstack/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch create mode 100644 drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.patch create mode 100644 drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.stripped.patch create mode 100644 drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.patch create mode 100644 drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.stripped.patch diff --git a/defs/components.json b/defs/components.json index 05e3d18..a1ad2e5 100644 --- a/defs/components.json +++ b/defs/components.json @@ -134,6 +134,39 @@ } }, "zed": { + "21.3.1.0.1.0": { + "comment": "StorPool updates for Ubuntu cloud archive Zed 21.3.1-0ubuntu1.1~cloud0", + "files": { + "volume/driver.py": { + "sha256": "b321efdba75c06b798e5cb16a733eee0756a63a0aab97ea0b5a79a9512ab98ff" + }, + "volume/drivers/storpool.py": { + "sha256": "78fb891c583c2e96e8a6a89a70f9e20f5536f44e9275c8f434da1b198fd6b9d4" + }, + "image/cache.py": { + "sha256": "846acf2d07f0b94b2c6ce63e9d3c9cf0da9cac6a6e960f5eb9e4bddc4df41257" + }, + "interface/volume_driver.py": { + "sha256": "cfd38d727a4965005ebe635ea0913bca6e4a76f986340f6d2d0de1a28745507a" + }, + "tests/unit/image/test_cache.py": { + "sha256": "43494e2c9fac4dfbd7e55c3362efe25c71d07a4a27c5df539929b22b40d0c843" + }, + "tests/unit/volume/flows/test_create_volume_flow.py": { + "sha256": "ecc6898a4996ffbdc08ecc6ef972c6dc79a68eea4781812ecba949dfb3631fb6" + }, + "volume/flows/manager/create_volume.py": { + "sha256": "0d213ea353b80692aaef8bdf93bebf75416251c9838629790ca7c603bce1e4b5" + }, + "volume/manager.py": { + "sha256": "f299b6bc8030d55761061ee0a191a7429f38dfc8043f0f82f68e2c32057be059" + }, + "tests/unit/volume/drivers/test_storpool.py": { + "sha256": "091a5e2c201af21b702f9799e3043ca641ceaf558a369e83598552182f08ded8" + } + }, + "outdated": false + }, "21.3.1.0.0.2": { "comment": "StorPool updates for Ubuntu cloud archive Zed 21.3.1-0ubuntu1.1~cloud0", "files": { @@ -165,7 +198,7 @@ "sha256": "091a5e2c201af21b702f9799e3043ca641ceaf558a369e83598552182f08ded8" } }, - "outdated": false + "outdated": true }, "21.3.1.0.0.1": { "comment": "StorPool updates for Ubuntu cloud archive Zed 21.3.1-0ubuntu1.1~cloud0", @@ -1335,6 +1368,33 @@ } }, "zed": { + "26.2.2.1.1.0": { + "comment": "StorPool updates for Ubuntu cloud archive Zed 26.2.2-0ubuntu1~cloud0", + "files": { + "virt/libvirt/driver.py": { + "sha256": "8776aa7ca5a38ddd5c1b19721bdb84d611f2d96881fa16de952c79ff5b849aed" + }, + "virt/libvirt/config.py": { + "sha256": "2e9f0fa9c792bc356badb99f0ffa378f6eefa8da6a2b2da5c8b6fb632a63c4ba" + }, + "conf/libvirt.py": { + "sha256": "8ef215829756cb58d386fe7466184b51773e58361ee31102b3bf5cc59b80a2aa" + }, + "tests/fixtures/libvirt_data.py": { + "sha256": "97c441c46658d586cba7047731bbec063377403f20294acf5984e3781695c1c9" + }, + "tests/unit/virt/libvirt/test_config.py": { + "sha256": "1ad51b1119a920209b1b937c833abeab99dbc4a18bc32bcb341a8d4b97795fd0" + }, + "virt/libvirt/volume/volume.py": { + "sha256": "27e7f62d25c29d04ea6f43abe473aec7db729b9defc5f800c6b94c2472b22200" + }, + "virt/libvirt/volume/storpool.py": { + "sha256": "9183c1b44e381b0cca1c461dff5b2147ef8f19c5142ad08adce516c7cd4468f5" + } + }, + "outdated": false + }, "26.2.2.1.0.0": { "comment": "StorPool updates for Ubuntu cloud archive Zed 26.2.2-0ubuntu1~cloud0", "files": { @@ -1360,7 +1420,7 @@ "sha256": "9183c1b44e381b0cca1c461dff5b2147ef8f19c5142ad08adce516c7cd4468f5" } }, - "outdated": false + "outdated": true }, "26.2.2.0.0.1": { "comment": "StorPool updates for Ubuntu cloud archive Zed 26.2.2-0ubuntu1~cloud0", diff --git a/drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch b/drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch deleted file mode 100644 index f4afeca..0000000 --- a/drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.patch +++ /dev/null @@ -1,484 +0,0 @@ -From fbb62919a881b500f4d3bdbe0f1c4094dd5122a1 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/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch b/drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch deleted file mode 100644 index f7aed06..0000000 --- a/drivers/cinder/openstack/caracal/patches/15_973769_1_StorPool-Propagate-a-storpool-qos_class-extra-spec-to-the-StorPool-API.stripped.patch +++ /dev/null @@ -1,467 +0,0 @@ -From fbb62919a881b500f4d3bdbe0f1c4094dd5122a1 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/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch b/drivers/cinder/openstack/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch new file mode 100644 index 0000000..5909412 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.patch @@ -0,0 +1,297 @@ +From 56009f435288edfb3eb0c9842c68341f3ecb4b82 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/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch b/drivers/cinder/openstack/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch new file mode 100644 index 0000000..5909412 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/15_973811_1_StorPool-Do-not-use-the-option-storpool_replication-when-creating-or-retyping-volumes.stripped.patch @@ -0,0 +1,297 @@ +From 56009f435288edfb3eb0c9842c68341f3ecb4b82 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/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch b/drivers/cinder/openstack/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch new file mode 100644 index 0000000..1ba78c2 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.patch @@ -0,0 +1,161 @@ +From 25a0182eddf21d467cb9e2e33b1b9f1b8fb9576e 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/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch b/drivers/cinder/openstack/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch new file mode 100644 index 0000000..1ba78c2 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/16_973812_1_StorPool-Use-only-baseOn-when-cloning-a-volume.stripped.patch @@ -0,0 +1,161 @@ +From 25a0182eddf21d467cb9e2e33b1b9f1b8fb9576e 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/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 new file mode 100644 index 0000000..eb30556 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.patch @@ -0,0 +1,615 @@ +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 new file mode 100644 index 0000000..eb30556 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/17_973813_1_StorPool-Add-the-storpool_qos-option-and-deprecate-the-storpool_template-one.stripped.patch @@ -0,0 +1,615 @@ +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/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 new file mode 100644 index 0000000..6925481 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.patch @@ -0,0 +1,597 @@ +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 new file mode 100644 index 0000000..6925481 --- /dev/null +++ b/drivers/cinder/openstack/caracal/patches/18_973814_1_StorPool-Remove-the-storpool_template-option.stripped.patch @@ -0,0 +1,597 @@ +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/zed/storpool.py b/drivers/cinder/openstack/zed/storpool.py index de33b74..becf2f0 100644 --- a/drivers/cinder/openstack/zed/storpool.py +++ b/drivers/cinder/openstack/zed/storpool.py @@ -72,15 +72,14 @@ cfg.StrOpt('iscsi_portal_group', default=None, help='The portal group to export volumes via iSCSI in.'), - cfg.StrOpt('storpool_template', + cfg.StrOpt('storpool_qos_class', default=None, - help='The StorPool template for volumes with no type.'), + help='The StorPool QoS class for volumes with no QoS class.'), 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 @@ -154,9 +153,15 @@ class StorPoolDriver(driver.VolumeDriver): - Implement clone_image() - Implement revert_to_snapshot(). - Add support for exporting volumes via iSCSI + 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 + 2.5.0 - Remove the storpool_template option """ - VERSION = '2.0.0' + VERSION = '2.5.0' CI_WIKI_NAME = 'StorPool_distributed_storage_CI' def __init__(self, *args, **kwargs): @@ -172,29 +177,32 @@ def __init__(self, *args, **kwargs): 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 is not None: - return extra_specs.get(ES_QOS) - return None - def _backendException(self, e): return exception.VolumeBackendAPIException(data=six.text_type(e)) - 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) + 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_storpool_volume(self, volume): + storpool_volume_name = self._attach.volumeName(volume['id']) + storpool_volume = self._attach.volumeList(storpool_volume_name) + return storpool_volume[0].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: @@ -203,19 +211,15 @@ def get_pool(self, volume): def create_volume(self, volume): size = int(volume['size']) * units.Gi name = self._attach.volumeName(volume['id']) - template = self._template_from_volume(volume) - qos_class = StorPoolDriver.qos_from_volume(volume) + qos_class = self._get_qos_class(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 qos_class: + raise exception.VolumeBackendAPIException( + "Cannot create a volume without a 'qos_class' option set") - if qos_class is not None: - create_request['tags'] = {'qc': qos_class} + create_request['tags'] = {'qc': qos_class} try: self._attach.api().volumeCreate(create_request) @@ -580,12 +584,15 @@ def create_volume_from_snapshot(self, volume, snapshot): size = int(volume['size']) * units.Gi volname = self._attach.volumeName(volume['id']) name = self._attach.snapshotName('snap', snapshot['id']) - qos_class = StorPoolDriver.qos_from_volume(volume) + qos_class = self._get_qos_class(volume) create_request = {'name': volname, 'size': size, 'parent': name} - if qos_class is not None: - 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._attach.api().volumeCreate(create_request) @@ -633,78 +640,22 @@ def create_cloned_volume(self, volume, src_vref): refname = self._attach.volumeName(src_vref['id']) size = int(volume['size']) * units.Gi volname = self._attach.volumeName(volume['id']) - qos_class = StorPoolDriver.qos_from_volume(volume) - clone_request = {'name': volname, 'size': size} + qos_class = self._get_qos_class(volume) - if qos_class is not None: - clone_request['tags'] = {'qc': qos_class} + clone_request = {'name': volname, 'size': size, 'baseOn': refname} - 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)s template %(template)s', { - 'vol_id': repr(volume['id']), - 'template': repr(template), - }) - if template == src_template: - LOG.info('Using baseOn to clone a volume into the same template') - clone_request['baseOn'] = refname - try: - self._attach.api().volumeCreate(clone_request) - except spapi.ApiError as e: - raise self._backendException(e) + if not qos_class: + raise self._backendException( + "Cannot clone a volume without a qos_class set") - return None + clone_request['tags'] = {'qc': qos_class} - snapname = self._attach.snapshotName('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._attach.api().snapshotCreate(refname, {'name': snapname}) + self._attach.api().volumeCreate(clone_request) except spapi.ApiError as e: - if e.name != 'objectExists': - raise self._backendException(e) - - try: - try: - self._attach.api().snapshotUpdate( - snapname, - {'template': template}, - ) - except spapi.ApiError as e: - raise self._backendException(e) - - try: - clone_request['parent'] = snapname - self._attach.api().volumeCreate(clone_request) - except spapi.ApiError as e: - raise self._backendException(e) - - try: - self._attach.api().snapshotUpdate( - snapname, - {'tags': {'transient': '1.0'}}, - ) - except spapi.ApiError 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._attach.api().snapshotDelete(snapname) - except spapi.ApiError 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): @@ -843,8 +794,6 @@ def retype(self, context, volume, new_type, diff, host): LOG.error('Retype of encryption type not supported.') return False - templ = self.configuration.storpool_template - repl = self.configuration.storpool_replication if diff['extra_specs']: for (k, v) in diff['extra_specs'].items(): if k == 'volume_backend_name': @@ -852,18 +801,13 @@ def retype(self, context, volume, new_type, diff, host): # Retype of a volume backend not supported yet, # the volume needs to be migrated. return False - elif k == '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: - update['replication'] = repl elif k == 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'].update({'qc': v[1]}) else: # We ignore any extra specs that we do not know about. diff --git a/drivers/nova/openstack/zed/driver.py b/drivers/nova/openstack/zed/driver.py index 3501c53..4614c92 100644 --- a/drivers/nova/openstack/zed/driver.py +++ b/drivers/nova/openstack/zed/driver.py @@ -1904,6 +1904,7 @@ def _connect_volume(self, context, connection_info, instance, for bdm in objects.BlockDeviceMappingList.get_by_instance_uuid( context, instance.uuid): if ('volume_id' in bdm and + 'volume' in connection_info['data'] and bdm['volume_id'] == connection_info['data']['volume']): if 'device_name' in bdm: LOG.debug("Volume: %(vol)s is attached at" @@ -1919,7 +1920,7 @@ def _connect_volume(self, context, connection_info, instance, LOG.info("Could not find the device name for volume" " %(vol) at VM %(vm)s", { - 'vol': connection_info['data']['volume'], + 'vol': connection_info['data'], 'vm': instance.uuid })