From 15c715eed8733d0c98466f9cc758976708152350 Mon Sep 17 00:00:00 2001 From: ling-yun Date: Thu, 29 May 2014 20:36:20 +0800 Subject: [PATCH] Ceph rbd volume manage/unmanage support Ceph rbd support for managing and unmanaging volumes. Partially Implements: blueprint add-export-import-volumes Change-Id: I272f65621a3dac50a154b7f2caa545ae6fb0e54f --- cinder/tests/test_rbd.py | 75 ++++++++++++++++++++++++++++++++++++ cinder/volume/drivers/rbd.py | 63 ++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/cinder/tests/test_rbd.py b/cinder/tests/test_rbd.py index e20338601..7e891ffb1 100644 --- a/cinder/tests/test_rbd.py +++ b/cinder/tests/test_rbd.py @@ -58,6 +58,10 @@ class MockImageBusyException(MockException): """Used as mock for rbd.ImageBusy.""" +class MockImageExistsException(MockException): + """Used as mock for rbd.ImageExists.""" + + def common_mocks(f): """Decorator to set mocks common to all tests. @@ -84,6 +88,7 @@ def common_mocks(f): inst.mock_rados.Error = Exception inst.mock_rbd.ImageBusy = MockImageBusyException inst.mock_rbd.ImageNotFound = MockImageNotFoundException + inst.mock_rbd.ImageExists = MockImageExistsException inst.driver.rbd = inst.mock_rbd inst.driver.rados = inst.mock_rados @@ -175,6 +180,76 @@ class RBDTestCase(test.TestCase): client.__exit__.assert_called_once() mock_supports_layering.assert_called_once() + @common_mocks + def test_manage_existing_get_size(self): + with mock.patch.object(self.driver.rbd.Image, 'size') as \ + mock_rbd_image_size: + with mock.patch.object(self.driver.rbd.Image, 'close') \ + as mock_rbd_image_close: + mock_rbd_image_size.return_value = 2 * units.Gi + existing_ref = {'rbd_name': self.volume_name} + return_size = self.driver.manage_existing_get_size( + self.volume, + existing_ref) + self.assertEqual(2, return_size) + mock_rbd_image_size.assert_called_once_with() + mock_rbd_image_close.assert_called_once_with() + + @common_mocks + def test_manage_existing_get_invalid_size(self): + + with mock.patch.object(self.driver.rbd.Image, 'size') as \ + mock_rbd_image_size: + with mock.patch.object(self.driver.rbd.Image, 'close') \ + as mock_rbd_image_close: + mock_rbd_image_size.return_value = 'abcd' + existing_ref = {'rbd_name': self.volume_name} + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.manage_existing_get_size, + self.volume, existing_ref) + + mock_rbd_image_size.assert_called_once_with() + mock_rbd_image_close.assert_called_once_with() + + @common_mocks + def test_manage_existing(self): + client = self.mock_client.return_value + client.__enter__.return_value = client + + with mock.patch.object(driver, 'RADOSClient') as mock_rados_client: + with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \ + mock_rbd_image_rename: + exist_volume = 'vol-exist' + existing_ref = {'rbd_name': exist_volume} + mock_rbd_image_rename.return_value = 0 + mock_rbd_image_rename(mock_rados_client.ioctx, + exist_volume, + self.volume_name) + self.driver.manage_existing(self.volume, existing_ref) + mock_rbd_image_rename.assert_called_with( + mock_rados_client.ioctx, + exist_volume, + self.volume_name) + + @common_mocks + def test_manage_existing_with_exist_rbd_image(self): + client = self.mock_client.return_value + client.__enter__.return_value = client + + self.mock_rbd.Image.rename = mock.Mock() + self.mock_rbd.Image.rename.side_effect = \ + MockImageExistsException + + exist_volume = 'vol-exist' + existing_ref = {'rbd_name': exist_volume} + self.assertRaises(self.mock_rbd.ImageExists, + self.driver.manage_existing, + self.volume, existing_ref) + + #make sure the exception was raised + self.assertEqual(RAISED_EXCEPTIONS, + [self.mock_rbd.ImageExists]) + @common_mocks def test_create_volume_no_layering(self): client = self.mock_client.return_value diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py index 4b3afed78..9035d9a47 100644 --- a/cinder/volume/drivers/rbd.py +++ b/cinder/volume/drivers/rbd.py @@ -859,3 +859,66 @@ class RBDDriver(driver.VolumeDriver): LOG.debug("Extend volume from %(old_size)s GB to %(new_size)s GB.", {'old_size': old_size, 'new_size': new_size}) + + def manage_existing(self, volume, existing_ref): + """Manages an existing image. + + Renames the image name to match the expected name for the volume. + Error checking done by manage_existing_get_size is not repeated. + + :param volume: + volume ref info to be set + :param existing_ref: + existing_ref is a dictionary of the form: + {'rbd_name': } + """ + # Raise an exception if we didn't find a suitable rbd image. + with RADOSClient(self) as client: + rbd_name = existing_ref['rbd_name'] + self.rbd.RBD().rename(client.ioctx, strutils.safe_encode(rbd_name), + strutils.safe_encode(volume['name'])) + + def manage_existing_get_size(self, volume, existing_ref): + """Return size of an existing image for manage_existing. + + :param volume: + volume ref info to be set + :param existing_ref: + existing_ref is a dictionary of the form: + {'rbd_name': } + """ + + # Check that the reference is valid + if 'rbd_name' not in existing_ref: + reason = _('Reference must contain rbd_name element.') + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, reason=reason) + + rbd_name = strutils.safe_encode(existing_ref['rbd_name']) + + with RADOSClient(self) as client: + # Raise an exception if we didn't find a suitable rbd image. + try: + rbd_image = self.rbd.Image(client.ioctx, rbd_name) + image_size = rbd_image.size() + except self.rbd.ImageNotFound: + kwargs = {'existing_ref': rbd_name, + 'reason': 'Specified rbd image does not exist.'} + raise exception.ManageExistingInvalidReference(**kwargs) + finally: + rbd_image.close() + + # RBD image size is returned in bytes. Attempt to parse + # size as a float and round up to the next integer. + try: + convert_size = int(math.ceil(int(image_size))) / units.Gi + return convert_size + except ValueError: + exception_message = (_("Failed to manage existing volume " + "%(name)s, because reported size " + "%(size)s was not a floating-point" + " number.") + % {'name': rbd_name, + 'size': image_size}) + raise exception.VolumeBackendAPIException( + data=exception_message) -- 2.45.2