RBD: Fix issue with managing volume with type properties

Certain properties like multiattach, replication etc. were
not inherited by the volume when we manage it using a volume type.
This patch enables it by calling the ``_setup_volume`` method in
the manage workflow to apply the volume type properties to the
RBD image.

Closes-Bug: #2115985

Signed-off-by: Rajat Dhasmana <rajatdhasmana@gmail.com>
Change-Id: Ia58b12a48ff853c82a3fc0a9a3f8ad8a54e284f0
This commit is contained in:
Rajat Dhasmana
2025-07-04 13:59:58 +00:00
committed by whoami-rajat
parent 27373d61fe
commit dc8a59c918
3 changed files with 115 additions and 12 deletions

View File

@@ -294,6 +294,18 @@ class RBDTestCase(test.TestCase):
self.qos_policy_b = {"read_iops_sec": "500", self.qos_policy_b = {"read_iops_sec": "500",
"write_iops_sec": "200"} "write_iops_sec": "200"}
# For tests involving multiattach volume type
MULTIATTACH_FULL_FEATURES = (
driver.RBDDriver.RBD_FEATURE_LAYERING |
driver.RBDDriver.RBD_FEATURE_EXCLUSIVE_LOCK |
driver.RBDDriver.RBD_FEATURE_OBJECT_MAP |
driver.RBDDriver.RBD_FEATURE_FAST_DIFF |
driver.RBDDriver.RBD_FEATURE_JOURNALING)
MULTIATTACH_REDUCED_FEATURES = (
driver.RBDDriver.RBD_FEATURE_LAYERING |
driver.RBDDriver.RBD_FEATURE_EXCLUSIVE_LOCK)
@ddt.data({'cluster_name': None, 'pool_name': 'rbd'}, @ddt.data({'cluster_name': None, 'pool_name': 'rbd'},
{'cluster_name': 'volumes', 'pool_name': None}) {'cluster_name': 'volumes', 'pool_name': None})
@ddt.unpack @ddt.unpack
@@ -737,6 +749,87 @@ class RBDTestCase(test.TestCase):
self.assertEqual([self.mock_rbd.ImageNotFound], self.assertEqual([self.mock_rbd.ImageNotFound],
RAISED_EXCEPTIONS) RAISED_EXCEPTIONS)
@common_mocks
def test_manage_existing_replicated_type(self):
client = self.mock_client.return_value
client.__enter__.return_value = client
self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
self.context,
id=fake.VOLUME_TYPE_ID,
extra_specs={'replication_enabled': '<is> True'})
with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \
mock_rbd_image_rename:
exist_volume = 'vol-exist'
existing_ref = {'source-name': exist_volume}
mock_rbd_image_rename.return_value = 0
res = self.driver.manage_existing(self.volume_a, existing_ref)
mock_rbd_image_rename.assert_called_with(
client.ioctx,
exist_volume,
self.volume_a.name)
self.assertEqual('enabled', res['replication_status'])
@common_mocks
def test_manage_existing_multiattach_type(self):
client = self.mock_client.return_value
client.__enter__.return_value = client
image = self.mock_proxy.return_value.__enter__.return_value
image_features = self.MULTIATTACH_FULL_FEATURES
image.features.return_value = image_features
expected_res = {
'provider_location': "{\"saved_features\":%s}" % image_features}
self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
self.context,
id=fake.VOLUME_TYPE_ID,
extra_specs={'multiattach': '<is> True'})
with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \
mock_rbd_image_rename:
exist_volume = 'vol-exist'
existing_ref = {'source-name': exist_volume}
mock_rbd_image_rename.return_value = 0
res = self.driver.manage_existing(self.volume_a, existing_ref)
mock_rbd_image_rename.assert_called_with(
client.ioctx,
exist_volume,
self.volume_a.name)
self.assertEqual(expected_res, res)
@common_mocks
def test_manage_existing_invalid_type(self):
client = self.mock_client.return_value
client.__enter__.return_value = client
# Replication and multiattach are mutually exclusive
extra_specs = {
'replication_enabled': '<is> True',
'multiattach': '<is> True'
}
self.volume_a.volume_type = fake_volume.fake_volume_type_obj(
self.context,
id=fake.VOLUME_TYPE_ID,
extra_specs=extra_specs)
with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \
mock_rbd_image_rename:
exist_volume = 'vol-exist'
existing_ref = {'source-name': exist_volume}
mock_rbd_image_rename.return_value = 0
res = self.assertRaises(
exception.ManageExistingVolumeTypeMismatch,
self.driver.manage_existing, self.volume_a, existing_ref)
self.assertIn(
"Manage existing volume failed due to volume type mismatch",
str(res))
self.assertIn(
"Replication and Multiattach are mutually exclusive.",
str(res))
# Ensure rename is not called
mock_rbd_image_rename.assert_not_called()
@common_mocks @common_mocks
@mock.patch.object(driver.RBDDriver, '_get_image_status') @mock.patch.object(driver.RBDDriver, '_get_image_status')
def test_get_manageable_volumes(self, mock_get_image_status): def test_get_manageable_volumes(self, mock_get_image_status):
@@ -3439,17 +3532,6 @@ class RBDTestCase(test.TestCase):
self.driver.RBD_FEATURE_EXCLUSIVE_LOCK, self.driver.RBD_FEATURE_EXCLUSIVE_LOCK,
self.driver.MULTIATTACH_EXCLUSIONS) self.driver.MULTIATTACH_EXCLUSIONS)
MULTIATTACH_FULL_FEATURES = (
driver.RBDDriver.RBD_FEATURE_LAYERING |
driver.RBDDriver.RBD_FEATURE_EXCLUSIVE_LOCK |
driver.RBDDriver.RBD_FEATURE_OBJECT_MAP |
driver.RBDDriver.RBD_FEATURE_FAST_DIFF |
driver.RBDDriver.RBD_FEATURE_JOURNALING)
MULTIATTACH_REDUCED_FEATURES = (
driver.RBDDriver.RBD_FEATURE_LAYERING |
driver.RBDDriver.RBD_FEATURE_EXCLUSIVE_LOCK)
@ddt.data(MULTIATTACH_FULL_FEATURES, MULTIATTACH_REDUCED_FEATURES) @ddt.data(MULTIATTACH_FULL_FEATURES, MULTIATTACH_REDUCED_FEATURES)
@common_mocks @common_mocks
def test_enable_multiattach(self, features): def test_enable_multiattach(self, features):

View File

@@ -2141,8 +2141,17 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
LOG.debug("Extend volume from %(old_size)s GB to %(new_size)s GB.", LOG.debug("Extend volume from %(old_size)s GB to %(new_size)s GB.",
{'old_size': old_size, 'new_size': new_size}) {'old_size': old_size, 'new_size': new_size})
def _is_valid_type(self, volume_type):
want_replication = self._is_replicated_type(volume_type)
want_multiattach = self._is_multiattach_type(volume_type)
if want_replication and want_multiattach:
return False
return True
def manage_existing(self, def manage_existing(self,
volume: Volume, existing_ref: dict[str, str]) -> None: volume: Volume,
existing_ref: dict[str, str]) -> dict[str, Any]:
"""Manages an existing image. """Manages an existing image.
Renames the image name to match the expected name for the volume. Renames the image name to match the expected name for the volume.
@@ -2154,12 +2163,17 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
existing_ref is a dictionary of the form: existing_ref is a dictionary of the form:
{'source-name': <name of RBD image>} {'source-name': <name of RBD image>}
""" """
# check if the volume type is valid and fail fast if not
if not self._is_valid_type(volume.volume_type):
msg = _('Replication and Multiattach are mutually exclusive.')
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
# Raise an exception if we didn't find a suitable rbd image. # Raise an exception if we didn't find a suitable rbd image.
with RADOSClient(self) as client: with RADOSClient(self) as client:
rbd_name = existing_ref['source-name'] rbd_name = existing_ref['source-name']
self.RBDProxy().rename(client.ioctx, self.RBDProxy().rename(client.ioctx,
utils.convert_str(rbd_name), utils.convert_str(rbd_name),
volume.name) volume.name)
return self._setup_volume(volume)
def manage_existing_get_size(self, def manage_existing_get_size(self,
volume: Volume, volume: Volume,

View File

@@ -0,0 +1,7 @@
---
fixes:
- |
RBD `bug #2115985
<https://bugs.launchpad.net/cinder/+bug/2115985>`_: Fixed
issue when managing a volume with ``multiattach`` or
``replication_enabled`` properties in volume type.