]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Ceph rbd volume manage/unmanage support
authorling-yun <zengyunling@huawei.com>
Thu, 29 May 2014 12:36:20 +0000 (20:36 +0800)
committerling-yun <zengyunling@huawei.com>
Thu, 3 Jul 2014 12:28:12 +0000 (20:28 +0800)
Ceph rbd support for managing and unmanaging volumes.
Partially Implements: blueprint add-export-import-volumes

Change-Id: I272f65621a3dac50a154b7f2caa545ae6fb0e54f

cinder/tests/test_rbd.py
cinder/volume/drivers/rbd.py

index e20338601b382ce44b9d4fa834d83b50c53e0309..7e891ffb11124e3f6b1e9c7e4c020d54e0ae3b9c 100644 (file)
@@ -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
index 4b3afed78037e1ba86990e9de97ca2b59c26ff63..9035d9a470e5b97bc30d09d6c917561518861b3e 100644 (file)
@@ -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': <name of rbd image>}
+        """
+        # 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': <name of rbd image>}
+        """
+
+        # 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)