RBD: Fix handling of RBD errors in get_manageable_volumes

The "except ImageNotFound" was outside of the required try block,
so ImageNotFound errors would result in exceptions being thrown
up to the API layer.

Add handling for other errors that could be thrown by RBD
here, too.

Reference info:
https://github.com/ceph/ceph/blob/08d7ff952d/src/pybind/rbd/rbd.pyx#L8

Closes-Bug: #2065713
Change-Id: Ifaceb04263973b1445c7f13a6155dc63647d7176
This commit is contained in:
Eric Harney 2024-05-14 13:33:48 -04:00
parent 1ecfffafa6
commit 7f49b41eef
3 changed files with 48 additions and 6 deletions

View File

@ -81,6 +81,10 @@ class MockPermissionError(MockException):
errno = errno.EPERM errno = errno.EPERM
class MockTimeOutException(MockException):
"""Used as mock for TimeOut."""
class MockImageHasSnapshotsException(MockException): class MockImageHasSnapshotsException(MockException):
"""Used as mock for rbd.ImageHasSnapshots.""" """Used as mock for rbd.ImageHasSnapshots."""
@ -114,12 +118,14 @@ def common_mocks(f):
inst.mock_proxy = mock_proxy inst.mock_proxy = mock_proxy
inst.mock_rbd.RBD.Error = Exception inst.mock_rbd.RBD.Error = Exception
inst.mock_rados.Error = Exception inst.mock_rados.Error = Exception
inst.mock_rbd.Error = Exception
inst.mock_rbd.ImageBusy = MockImageBusyException inst.mock_rbd.ImageBusy = MockImageBusyException
inst.mock_rbd.ImageNotFound = MockImageNotFoundException inst.mock_rbd.ImageNotFound = MockImageNotFoundException
inst.mock_rbd.ImageExists = MockImageExistsException inst.mock_rbd.ImageExists = MockImageExistsException
inst.mock_rbd.ImageHasSnapshots = MockImageHasSnapshotsException inst.mock_rbd.ImageHasSnapshots = MockImageHasSnapshotsException
inst.mock_rbd.InvalidArgument = MockInvalidArgument inst.mock_rbd.InvalidArgument = MockInvalidArgument
inst.mock_rbd.PermissionError = MockPermissionError inst.mock_rbd.PermissionError = MockPermissionError
inst.mock_rbd.TimeOut = MockTimeOutException
inst.driver.rbd = inst.mock_rbd inst.driver.rbd = inst.mock_rbd
aux = inst.driver.rbd aux = inst.driver.rbd
@ -766,6 +772,32 @@ class RBDTestCase(test.TestCase):
] ]
self.assertEqual(exp, res) self.assertEqual(exp, res)
@common_mocks
@mock.patch.object(driver.RBDDriver, '_get_image_status')
def test_get_manageable_volumes_exc(self, mock_get_image_status):
cinder_vols = [{'id': '00000000-0000-0000-0000-000000000000'}]
vols = ['volume-00000000-0000-0000-0000-000000000000', 'vol1', 'vol2',
'volume-11111111-1111-1111-1111-111111111111.deleted']
self.mock_rbd.RBD.return_value.list.return_value = vols
image = self.mock_proxy.return_value.__enter__.return_value
# Four images are present, but the third image can't be opened
image.size.side_effect = [2 * units.Gi,
self.mock_rbd.ImageNotFound,
self.mock_rbd.TimeOut,
self.mock_rbd.PermissionError]
mock_get_image_status.side_effect = [
{'watchers': []},
{'watchers': []},
{'watchers': []}]
res = self.driver.get_manageable_volumes(
cinder_vols, None, 1000, 0, ['size'], ['asc'])
exp = [{'size': 2, 'reason_not_safe': 'already managed',
'extra_info': None, 'safe_to_manage': False,
'reference': {'source-name':
'volume-00000000-0000-0000-0000-000000000000'},
'cinder_id': '00000000-0000-0000-0000-000000000000'}]
self.assertEqual(exp, res)
@common_mocks @common_mocks
def test_delete_backup_snaps(self): def test_delete_backup_snaps(self):
self.driver.rbd.Image.remove_snap = mock.Mock() self.driver.rbd.Image.remove_snap = mock.Mock()

View File

@ -2205,10 +2205,10 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
with RADOSClient(self) as client: with RADOSClient(self) as client:
for image_name in self.RBDProxy().list(client.ioctx): for image_name in self.RBDProxy().list(client.ioctx):
image_id = volume_utils.extract_id_from_volume_name(image_name) image_id = volume_utils.extract_id_from_volume_name(image_name)
with RBDVolumeProxy(self, image_name, read_only=True, try:
client=client.cluster, with RBDVolumeProxy(self, image_name, read_only=True,
ioctx=client.ioctx) as image: client=client.cluster,
try: ioctx=client.ioctx) as image:
image_info = { image_info = {
'reference': {'source-name': image_name}, 'reference': {'source-name': image_name},
'size': int(math.ceil( 'size': int(math.ceil(
@ -2236,8 +2236,11 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
image_info['safe_to_manage'] = True image_info['safe_to_manage'] = True
image_info['reason_not_safe'] = None image_info['reason_not_safe'] = None
manageable_volumes.append(image_info) manageable_volumes.append(image_info)
except self.rbd.ImageNotFound: except self.rbd.ImageNotFound:
LOG.debug("Image %s is not found.", image_name) LOG.debug("Image %s is not found.", image_name)
except self.rbd.Error as error:
LOG.debug("Cannot open image %(image)s. (%(error)s)",
({'image': image_name, 'error': error}))
return volume_utils.paginate_entries_list( return volume_utils.paginate_entries_list(
manageable_volumes, marker, limit, offset, sort_keys, sort_dirs) manageable_volumes, marker, limit, offset, sort_keys, sort_dirs)

View File

@ -0,0 +1,7 @@
---
fixes:
- |
`Bug #2065713 <https://bugs.launchpad.net/cinder/+bug/2065713>`_: Due to
incorrect exception handling, ImageNotFound errors in the RBD driver's
get_manageable_volumes operation would propagate up to the API layer rather
than being caught and handled in the driver.