]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add support for manage/unmanage volume commands to PureISCSIDriver
authorPatrick East <patrick.east@purestorage.com>
Thu, 18 Dec 2014 23:21:02 +0000 (15:21 -0800)
committerPatrick East <patrick.east@purestorage.com>
Fri, 16 Jan 2015 19:11:18 +0000 (11:11 -0800)
This change adds implementations of the required methods to make
manage and unmanage commands work with the PureISCSIDriver. You can now
call them and specify a volume name of an existing non-cinder volume on
the array and it will rename the volume accordingly to allow cinder to
manage it. When a volume is unmanaged it is renamed with a “-unmanaged”
suffix and left on the array.

Implements: blueprint pure-iscsi-manage-volume
Change-Id: I2fa63ba50a367fd06a2ede7768eb338bf6a1ee4e

cinder/tests/test_pure.py
cinder/volume/drivers/pure.py

index 2a80dd77d03cfd08e957705c6ee1c5d679bd8ac2..50492589ce20bac63c05626ae0cad17b20e7b27a 100644 (file)
@@ -802,3 +802,113 @@ class PureISCSIDriverTestCase(test.TestCase):
         self.assert_error_propagates(
             [self.array.destroy_pgroup],
             self.driver.delete_cgsnapshot, mock_context, mock_cgsnap)
+
+    def test_manage_existing(self):
+        ref_name = 'vol1'
+        volume_ref = {'name': ref_name}
+        self.array.list_volume_private_connections.return_value = []
+        vol_name = VOLUME['name'] + '-cinder'
+        self.driver.manage_existing(VOLUME, volume_ref)
+        self.array.list_volume_private_connections.assert_called_with(ref_name)
+        self.array.rename_volume.assert_called_with(ref_name, vol_name)
+
+    def test_manage_existing_error_propagates(self):
+        self.array.list_volume_private_connections.return_value = []
+        self.assert_error_propagates(
+            [self.array.list_volume_private_connections,
+             self.array.rename_volume],
+            self.driver.manage_existing,
+            VOLUME, {'name': 'vol1'}
+        )
+
+    def test_manage_existing_bad_ref(self):
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          VOLUME, {'bad_key': 'bad_value'})
+
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          VOLUME, {'name': ''})
+
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          VOLUME, {'name': None})
+
+        self.array.get_volume.side_effect = \
+            self.purestorage_module.PureHTTPError(
+                text="Volume does not exist.",
+                code=400
+            )
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          VOLUME, {'name': 'non-existing-volume'})
+
+    def test_manage_existing_with_connected_hosts(self):
+        ref_name = 'vol1'
+        self.array.list_volume_private_connections.return_value = \
+            ["host1", "host2"]
+
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          VOLUME, {'name': ref_name})
+
+        self.array.list_volume_private_connections.assert_called_with(ref_name)
+        self.assertFalse(self.array.rename_volume.called)
+
+    def test_manage_existing_get_size(self):
+        ref_name = 'vol1'
+        volume_ref = {'name': ref_name}
+        expected_size = 5
+        self.array.get_volume.return_value = {"size": 5368709120}
+
+        size = self.driver.manage_existing_get_size(VOLUME, volume_ref)
+
+        self.assertEqual(expected_size, size)
+        self.array.get_volume.assert_called_with(ref_name)
+
+    def test_manage_existing_get_size_error_propagates(self):
+        self.array.get_volume.return_value = mock.MagicMock()
+        self.assert_error_propagates([self.array.get_volume],
+                                     self.driver.manage_existing_get_size,
+                                     VOLUME, {'name': 'vol1'})
+
+    def test_manage_existing_get_size_bad_ref(self):
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing_get_size,
+                          VOLUME, {'bad_key': 'bad_value'})
+
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing_get_size,
+                          VOLUME, {'name': ''})
+
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing_get_size,
+                          VOLUME, {'name': None})
+
+    def test_unmanage(self):
+        vol_name = VOLUME['name'] + "-cinder"
+        unmanaged_vol_name = vol_name + "-unmanaged"
+
+        self.driver.unmanage(VOLUME)
+
+        self.array.rename_volume.assert_called_with(vol_name,
+                                                    unmanaged_vol_name)
+
+    def test_unmanage_error_propagates(self):
+        self.assert_error_propagates([self.array.rename_volume],
+                                     self.driver.unmanage,
+                                     VOLUME)
+
+    def test_unmanage_with_deleted_volume(self):
+        vol_name = VOLUME['name'] + "-cinder"
+        unmanaged_vol_name = vol_name + "-unmanaged"
+        self.array.rename_volume.side_effect = \
+            self.purestorage_module.PureHTTPError(
+                text="Volume does not exist.",
+                code=400
+            )
+
+        self.driver.unmanage(VOLUME)
+
+        self.array.rename_volume.assert_called_with(vol_name,
+                                                    unmanaged_vol_name)
index 6ba3e789c51970900430b45ae3de9ac59d69daaa..62813fdf72e9c4d8d98a6931dbaa33079bbe854a 100644 (file)
@@ -18,6 +18,7 @@ Volume driver for Pure Storage FlashArray storage system.
 This driver requires Purity version 3.4.0 or later.
 """
 
+import math
 import re
 import uuid
 
@@ -494,3 +495,94 @@ class PureISCSIDriver(san.SanISCSIDriver):
 
         LOG.debug("Leave PureISCSIDriver.delete_cgsnapshot")
         return model_update, snapshots
+
+    def _validate_manage_existing_ref(self, existing_ref):
+        """Ensure that an existing_ref is valid and return volume info
+
+        If the ref is not valid throw a ManageExistingInvalidReference
+        exception with an appropriate error.
+        """
+        if "name" not in existing_ref or not existing_ref["name"]:
+            raise exception.ManageExistingInvalidReference(
+                existing_ref=existing_ref,
+                reason=_("PureISCSIDriver manage_existing requires a 'name'"
+                         " key to identify an existing volume."))
+
+        ref_vol_name = existing_ref['name']
+
+        try:
+            volume_info = self._array.get_volume(ref_vol_name)
+            if volume_info:
+                return volume_info
+        except purestorage.PureHTTPError as err:
+            with excutils.save_and_reraise_exception() as ctxt:
+                if (err.code == 400 and
+                        ERR_MSG_NOT_EXIST in err.text):
+                    ctxt.reraise = False
+
+        # If volume information was unable to be retrieved we need
+        # to throw a Invalid Reference exception
+        raise exception.ManageExistingInvalidReference(
+            existing_ref=existing_ref,
+            reason=_("Unable to find volume with name=%s") % ref_vol_name)
+
+    def manage_existing(self, volume, existing_ref):
+        """Brings an existing backend storage object under Cinder management.
+
+        We expect a volume name in the existing_ref that matches one in Purity.
+        """
+        LOG.debug("Enter PureISCSIDriver.manage_existing.")
+
+        self._validate_manage_existing_ref(existing_ref)
+
+        ref_vol_name = existing_ref['name']
+
+        connected_hosts = \
+            self._array.list_volume_private_connections(ref_vol_name)
+        if len(connected_hosts) > 0:
+            raise exception.ManageExistingInvalidReference(
+                existing_ref=existing_ref,
+                reason=_("PureISCSIDriver manage_existing cannot manage a "
+                         "volume connected to hosts. Please disconnect the "
+                         "volume from existing hosts before importing."))
+
+        new_vol_name = _get_vol_name(volume)
+        LOG.info(_LI("Renaming existing volume %(ref_name)s to %(new_name)s"),
+                 {"ref_name": ref_vol_name, "new_name": new_vol_name})
+        self._array.rename_volume(ref_vol_name, new_vol_name)
+        LOG.debug("Leave PureISCSIDriver.manage_existing.")
+        return None
+
+    def manage_existing_get_size(self, volume, existing_ref):
+        """Return size of volume to be managed by manage_existing.
+
+        We expect a volume name in the existing_ref that matches one in Purity.
+        """
+        LOG.debug("Enter PureISCSIDriver.manage_existing_get_size.")
+
+        volume_info = self._validate_manage_existing_ref(existing_ref)
+        size = math.ceil(float(volume_info["size"]) / units.Gi)
+
+        LOG.debug("Leave PureISCSIDriver.manage_existing_get_size.")
+        return size
+
+    def unmanage(self, volume):
+        """Removes the specified volume from Cinder management.
+
+        Does not delete the underlying backend storage object.
+
+        The volume will be renamed with "-unmanaged" as a suffix
+        """
+        vol_name = _get_vol_name(volume)
+        unmanaged_vol_name = vol_name + "-unmanaged"
+        LOG.info(_LI("Renaming existing volume %(ref_name)s to %(new_name)s"),
+                 {"ref_name": vol_name, "new_name": unmanaged_vol_name})
+        try:
+            self._array.rename_volume(vol_name, unmanaged_vol_name)
+        except purestorage.PureHTTPError as err:
+            with excutils.save_and_reraise_exception() as ctxt:
+                if (err.code == 400 and
+                        ERR_MSG_NOT_EXIST in err.text):
+                    ctxt.reraise = False
+                    LOG.warn(_LW("Volume unmanage was unable to rename "
+                                 "the volume, error message: %s"), err.text)