From e25afb876e885f6fba7643df6a981dc8241f055c Mon Sep 17 00:00:00 2001 From: Helen Walsh Date: Mon, 23 Nov 2015 23:32:35 +0000 Subject: [PATCH] EMC VMAX - Extend Volume for VMAX3 The Extend Volume functionality for the VMAX3 is supported in EMC Solutions Enabler 8.1.0.3 onward. Change-Id: Ic83f491f47be016e8fbe15216f0410881f9a3b3e Blueprint: vmax3-extend-volume --- cinder/tests/unit/test_emc_vmax.py | 165 ++++++++++++++++-- cinder/volume/drivers/emc/emc_vmax_common.py | 32 +++- cinder/volume/drivers/emc/emc_vmax_fc.py | 2 + cinder/volume/drivers/emc/emc_vmax_iscsi.py | 2 + .../drivers/emc/emc_vmax_provision_v3.py | 49 ++++++ 5 files changed, 235 insertions(+), 15 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index d42e9eff4..48470fcd7 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -723,17 +723,40 @@ class FakeEcomConnection(object): def AssociatorNames(self, objectpath, ResultClass='default', AssocClass='default'): result = None + if objectpath == 'point_to_storage_instance_names': + result = ['FirstStorageTierInstanceNames'] + if ResultClass != 'default': + result = self.ResultClassHelper(ResultClass, objectpath) + + if result is None and AssocClass != 'default': + result = self.AssocClassHelper(AssocClass, objectpath) + if result is None: + result = self._default_assocnames(objectpath) + return result + + def AssocClassHelper(self, AssocClass, objectpath): + if AssocClass == 'CIM_HostedService': + result = self._assocnames_hostedservice() + elif AssocClass == 'CIM_AssociatedTierPolicy': + result = self._assocnames_assoctierpolicy() + elif AssocClass == 'CIM_OrderedMemberOfCollection': + result = self._enum_storagevolumes() + elif AssocClass == 'CIM_BindsTo': + result = self._assocnames_bindsto() + elif AssocClass == 'CIM_MemberOfCollection': + result = self._assocnames_memberofcollection() + else: + result = None + return result + + def ResultClassHelper(self, ResultClass, objectpath): if ResultClass == 'EMC_LunMaskingSCSIProtocolController': result = self._assocnames_lunmaskctrl() - elif AssocClass == 'CIM_HostedService': - result = self._assocnames_hostedservice() elif ResultClass == 'CIM_TierPolicyServiceCapabilities': result = self._assocnames_policyCapabilities() elif ResultClass == 'Symm_TierPolicyRule': result = self._assocnames_policyrule() - elif AssocClass == 'CIM_AssociatedTierPolicy': - result = self._assocnames_assoctierpolicy() elif ResultClass == 'CIM_StoragePool': result = self._assocnames_storagepool() elif ResultClass == 'EMC_VirtualProvisioningPool': @@ -756,8 +779,6 @@ class FakeEcomConnection(object): result = self._enum_repservcpbls() elif ResultClass == 'CIM_ReplicationGroup': result = self._enum_repgroups() - elif AssocClass == 'CIM_OrderedMemberOfCollection': - result = self._enum_storagevolumes() elif ResultClass == 'Symm_FCSCSIProtocolEndpoint': result = self._enum_fcscsiendpoint() elif ResultClass == 'Symm_SRPStoragePool': @@ -774,12 +795,12 @@ class FakeEcomConnection(object): result = self._enum_maskingView() elif ResultClass == 'EMC_Meta': result = self._enum_metavolume() - elif AssocClass == 'CIM_BindsTo': - result = self._assocnames_bindsto() - elif AssocClass == 'CIM_MemberOfCollection': - result = self._assocnames_memberofcollection() + elif ResultClass == 'EMC_FrontEndSCSIProtocolController': + result = self._enum_maskingView() + elif ResultClass == 'CIM_TierPolicyRule': + result = self._assocnames_tierpolicy(objectpath) else: - result = self._default_assocnames(objectpath) + result = None return result def ReferenceNames(self, objectpath, @@ -6092,6 +6113,68 @@ class EMCV3DriverTestCase(test.TestCase): self.data.remainingSLOCapacity) self.assertEqual(remainingSLOCapacityGb, remainingCapacityGb) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'get_volume_size', + return_value='2147483648') + def test_extend_volume(self, mock_volume_size): + newSize = '2' + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.extend_volume(self.data.test_volume_v3, newSize) + + def test_extend_volume_smaller_size_exception(self): + test_local_volume = {'name': 'vol1', + 'size': 4, + 'volume_name': 'vol1', + 'id': 'vol1', + 'device_id': '1', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol1', + 'display_description': 'test volume', + 'volume_type_id': 'abc', + 'provider_location': six.text_type( + self.data.provider_location), + 'status': 'available', + 'host': self.data.fake_host_v3, + 'NumberOfBlocks': 100, + 'BlockSize': self.data.block_size + } + newSize = '2' + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.assertRaises( + exception.VolumeBackendAPIException, + self.driver.extend_volume, + test_local_volume, newSize) + + def test_extend_volume_exception(self): + common = self.driver.common + newsize = '2' + common._initial_setup = mock.Mock(return_value=None) + common._find_lun = mock.Mock(return_value=None) + self.assertRaises( + exception.VolumeBackendAPIException, + common.extend_volume, + self.data.test_volume, newsize) + + def test_extend_volume_size_tally_exception(self): + common = self.driver.common + newsize = '2' + self.driver.common._initial_setup = mock.Mock( + return_value=self.data.extra_specs) + vol = {'SystemName': self.data.storage_system} + common._find_lun = mock.Mock(return_value=vol) + common._extend_v3_volume = mock.Mock(return_value=(0, vol)) + common.utils.find_volume_instance = mock.Mock( + return_value='2147483648') + common.utils.get_volume_size = mock.Mock(return_value='2147483646') + self.assertRaises( + exception.VolumeBackendAPIException, + common.extend_volume, + self.data.test_volume, newsize) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: @@ -7029,3 +7112,63 @@ class EMCVMAXProvisionV3Test(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, provisionv3.get_storage_pool_setting, conn, storagePoolCapability, slo, workload) + + def test_extend_volume_in_SG(self): + provisionv3 = self.driver.common.provisionv3 + conn = FakeEcomConnection() + storageConfigService = { + 'CreationClassName': 'Symm_ElementCompositionService', + 'SystemName': 'SYMMETRIX+000195900551'} + theVolumeInstanceName = ( + conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + inVolumeInstanceName = ( + conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + volumeSize = 3 + + extraSpecs = {'volume_backend_name': 'GOLD_BE', + 'isV3': True} + job = { + 'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}} + conn.InvokeMethod = mock.Mock(return_value=(4096, job)) + provisionv3.utils.wait_for_job_complete = mock.Mock(return_value=( + 0, 'Success')) + volumeDict = {'classname': u'Symm_StorageVolume', + 'keybindings': EMCVMAXCommonData.keybindings} + provisionv3.get_volume_dict_from_job = ( + mock.Mock(return_value=volumeDict)) + result = provisionv3.extend_volume_in_SG(conn, storageConfigService, + theVolumeInstanceName, + inVolumeInstanceName, + volumeSize, extraSpecs) + self.assertEqual( + ({'classname': u'Symm_StorageVolume', + 'keybindings': { + 'CreationClassName': u'Symm_StorageVolume', + 'DeviceID': u'1', + 'SystemCreationClassName': u'Symm_StorageSystem', + 'SystemName': u'SYMMETRIX+000195900551'}}, 0), result) + + def test_extend_volume_in_SG_with_Exception(self): + provisionv3 = self.driver.common.provisionv3 + conn = FakeEcomConnection() + storageConfigService = { + 'CreationClassName': 'Symm_ElementCompositionService', + 'SystemName': 'SYMMETRIX+000195900551'} + theVolumeInstanceName = ( + conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + inVolumeInstanceName = ( + conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + volumeSize = 3 + + extraSpecs = {'volume_backend_name': 'GOLD_BE', + 'isV3': True} + job = { + 'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}} + conn.InvokeMethod = mock.Mock(return_value=(4096, job)) + provisionv3.utils.wait_for_job_complete = mock.Mock(return_value=( + 2, 'Failure')) + self.assertRaises( + exception.VolumeBackendAPIException, + provisionv3.extend_volume_in_SG, conn, storageConfigService, + theVolumeInstanceName, inVolumeInstanceName, volumeSize, + extraSpecs) diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 27b21c310..d8c031518 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -519,10 +519,14 @@ class EMCVMAXCommon(object): additionalVolumeSize = self.utils.convert_gb_to_bits( additionalVolumeSize) - # This is V2 - rc, modifiedVolumeDict = self._extend_composite_volume( - volumeInstance, volumeName, newSize, additionalVolumeSize, - extraSpecs) + if extraSpecs[ISV3]: + rc, modifiedVolumeDict = self._extend_v3_volume( + volumeInstance, volumeName, newSize, extraSpecs) + else: + # This is V2. + rc, modifiedVolumeDict = self._extend_composite_volume( + volumeInstance, volumeName, newSize, additionalVolumeSize, + extraSpecs) # Check the occupied space of the new extended volume. extendedVolumeInstance = self.utils.find_volume_instance( @@ -4361,3 +4365,23 @@ class EMCVMAXCommon(object): conn, ipendpointinstancename)) foundipaddresses.append(ipaddress) return foundipaddresses + + def _extend_v3_volume(self, volumeInstance, volumeName, newSize, + extraSpecs): + """Extends a VMAX3 volume. + + :param volumeInstance: volume instance + :param volumeName: volume name + :param newSize: new size the volume will be increased to + :param extraSpecs: extra specifications + :returns: int -- return code + :returns: volumeDict + """ + new_size_in_bits = int(self.utils.convert_gb_to_bits(newSize)) + storageConfigService = self.utils.find_storage_configuration_service( + self.conn, volumeInstance['SystemName']) + volumeDict, rc = self.provisionv3.extend_volume_in_SG( + self.conn, storageConfigService, volumeInstance.path, + volumeName, new_size_in_bits, extraSpecs) + + return rc, volumeDict diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index cf33cc6fb..690faadd6 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -47,6 +47,8 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): - get_short_host_name needs to be called in find_device_number (bug #1520635) - Proper error handling for invalid SLOs (bug #1512795) + - Extend Volume for VMAX3, SE8.1.0.3 + https://blueprints.launchpad.net/cinder/+spec/vmax3-extend-volume """ VERSION = "2.3.0" diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 878a26849..97525de78 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -55,6 +55,8 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): - get_short_host_name needs to be called in find_device_number (bug #1520635) - Proper error handling for invalid SLOs (bug #1512795) + - Extend Volume for VMAX3, SE8.1.0.3 + https://blueprints.launchpad.net/cinder/+spec/vmax3-extend-volume """ VERSION = "2.3.0" diff --git a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py index dc8442e1c..3c301930a 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py @@ -676,3 +676,52 @@ class EMCVMAXProvisionV3(object): except KeyError: pass return remainingCapacityGb + + def extend_volume_in_SG( + self, conn, storageConfigService, volumeInstanceName, + volumeName, volumeSize, extraSpecs): + """Extend a volume instance. + + :param conn: connection the the ecom server + :param storageConfigservice: the storage configuration service + :param volumeInstanceName: the volume instance name + :param volumeName: the volume name (String) + :param volumeSize: the volume size + :param extraSpecs: additional info + :returns: volumeDict + :returns: int -- return code + :raises: VolumeBackendAPIException + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'CreateOrModifyElementFromStoragePool', + storageConfigService, TheElement=volumeInstanceName, + Size=self.utils.get_num(volumeSize, '64')) + + LOG.debug("Extend Volume: %(volumename)s. Return code: %(rc)lu.", + {'volumename': volumeName, + 'rc': rc}) + + if rc != 0: + rc, error_desc = self.utils.wait_for_job_complete(conn, job, + extraSpecs) + if rc != 0: + exceptionMessage = (_( + "Error Extend Volume: %(volumeName)s. " + "Return code: %(rc)lu. Error: %(error)s.") + % {'volumeName': volumeName, + 'rc': rc, + 'error': error_desc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod CreateOrModifyElementFromStoragePool " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + # Find the newly created volume. + volumeDict = self.get_volume_dict_from_job(conn, job['Job']) + return volumeDict, rc -- 2.45.2