From 4d220211cfb9ec775ac8090b3c467570a26bf156 Mon Sep 17 00:00:00 2001 From: Patrick East Date: Thu, 18 Dec 2014 15:21:02 -0800 Subject: [PATCH] Add support for manage/unmanage volume commands to PureISCSIDriver MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 | 110 ++++++++++++++++++++++++++++++++++ cinder/volume/drivers/pure.py | 92 ++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/cinder/tests/test_pure.py b/cinder/tests/test_pure.py index 2a80dd77d..50492589c 100644 --- a/cinder/tests/test_pure.py +++ b/cinder/tests/test_pure.py @@ -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) diff --git a/cinder/volume/drivers/pure.py b/cinder/volume/drivers/pure.py index 6ba3e789c..62813fdf7 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -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) -- 2.45.2