From a7e3d429eade62a416dc4daf4a1266976467b268 Mon Sep 17 00:00:00 2001 From: Helen Walsh Date: Mon, 16 Nov 2015 20:31:53 +0000 Subject: [PATCH] EMC VMAX - get iscsi ip from port in existing MV This patch fixes bug in getting the iscsi ip address from an existing masking view. In this scenario we must query the port(s) in the portgroup belonging to the masking view and not the random port group from the xml file. Change-Id: I4f8bea5c9f8c07a793aa1053678a7a915d565d22 Closes-Bug: #1504460 --- cinder/tests/unit/test_emc_vmax.py | 47 +++++++++-- cinder/volume/drivers/emc/emc_vmax_common.py | 81 ++++++++++++++++--- cinder/volume/drivers/emc/emc_vmax_fc.py | 1 + cinder/volume/drivers/emc/emc_vmax_iscsi.py | 1 + cinder/volume/drivers/emc/emc_vmax_masking.py | 37 +++++++++ 5 files changed, 148 insertions(+), 19 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index 678aa0552..bd87d3474 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -243,8 +243,9 @@ class EMCVMAXCommonData(object): storage_system_v3 = 'SYMMETRIX-+-000197200056' port_group = 'OS-portgroup-PG' lunmaskctrl_id = ( - 'SYMMETRIX+000195900551+OS-fakehost-gold-MV') - lunmaskctrl_name = 'OS-fakehost-gold-MV' + 'SYMMETRIX+000195900551+OS-fakehost-gold-I-MV') + lunmaskctrl_name = ( + 'OS-fakehost-gold-I-MV') initiatorgroup_id = ( 'SYMMETRIX+000195900551+OS-fakehost-IG') @@ -850,6 +851,7 @@ class FakeEcomConnection(object): antecedent = SYMM_LunMasking() antecedent['CreationClassName'] = self.data.lunmask_creationclass2 antecedent['SystemName'] = self.data.storage_system + antecedent['ElementName'] = mvname classcimproperty = Fake_CIMProperty() elementName = ( @@ -1196,6 +1198,9 @@ class FakeEcomConnection(object): def _getinstance_ipprotocolendpoint(self, objectpath): return self._enum_ipprotocolendpoint()[0] + def _getinstance_lunmaskingview(self, objectpath): + return self._enum_maskingView()[0] + def _default_getinstance(self, objectpath): return objectpath @@ -1601,9 +1606,15 @@ class FakeEcomConnection(object): def _enum_maskingView(self): maskingViews = [] - maskingView = {} + maskingView = SYMM_LunMasking() maskingView['CreationClassName'] = 'Symm_LunMaskingView' - maskingView['ElementName'] = 'myMaskingView' + maskingView['ElementName'] = self.data.lunmaskctrl_name + + cimproperty = Fake_CIMProperty() + cimproperty.value = self.data.lunmaskctrl_name + properties = {u'ElementName': cimproperty} + maskingView.properties = properties + maskingViews.append(maskingView) return maskingViews @@ -2053,6 +2064,16 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): issynched = self.driver.common.utils._is_sync_complete(conn, syncname) self.assertFalse(issynched) + def test_get_correct_port_group(self): + self.driver.common.conn = self.fake_ecom_connection() + maskingViewInstanceName = {'CreationClassName': 'Symm_LunMaskingView', + 'ElementName': 'OS-fakehost-gold-I-MV', + 'SystemName': 'SYMMETRIX+000195900551'} + deviceinfodict = {'controller': maskingViewInstanceName} + portgroupname = self.driver.common._get_correct_port_group( + deviceinfodict, self.data.storage_system) + self.assertEqual('OS-portgroup-PG', portgroupname) + def test_generate_unique_trunc_pool(self): pool_under_16_chars = 'pool_under_16' pool1 = self.driver.utils.generate_unique_trunc_pool( @@ -2089,11 +2110,11 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.assertEqual('10.10.10.10', foundIpAddresses[0]) def test_find_device_number(self): - host = 'myhost' + host = 'fakehost' data = ( self.driver.common.find_device_number(self.data.test_volume_v2, host)) - self.assertEqual('OS-myhost-MV', data['maskingview']) + self.assertEqual('OS-fakehost-MV', data['maskingview']) host = 'bogushost' data = ( self.driver.common.find_device_number(self.data.test_volume_v2, @@ -2762,7 +2783,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.data.storage_system, self.data.connector)) # The controller has been found. self.assertEqual( - 'OS-fakehost-gold-MV', + 'OS-fakehost-gold-I-MV', self.driver.common.conn.GetInstance( foundControllerInstanceName)['ElementName']) @@ -3087,6 +3108,8 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def test_already_mapped_no_fast_success( self, _mock_volume_type, mock_wrap_group, mock_wrap_device, mock_is_same_host): + self.driver.common._get_correct_port_group = mock.Mock( + return_value=self.data.port_group) self.driver.initialize_connection(self.data.test_volume, self.data.connector) @@ -3109,6 +3132,8 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def test_map_new_masking_view_no_fast_success( self, _mock_volume_type, mock_wrap_group, mock_storage_group, mock_add_volume): + self.driver.common._wrap_find_device_number = mock.Mock( + return_value={}) self.driver.initialize_connection(self.data.test_volume, self.data.connector) @@ -4055,6 +4080,8 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): def test_already_mapped_fast_success( self, _mock_volume_type, mock_wrap_group, mock_wrap_device, mock_is_same_host): + self.driver.common._get_correct_port_group = mock.Mock( + return_value=self.data.port_group) self.driver.initialize_connection(self.data.test_volume, self.data.connector) @@ -4646,6 +4673,8 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): common = self.driver.common common.get_target_wwns_from_masking_view = mock.Mock( return_value=EMCVMAXCommonData.target_wwns) + common._get_correct_port_group = mock.Mock( + return_value=self.data.port_group) lookup_service = self.driver.zonemanager_lookup_service lookup_service.get_device_mapping_from_network = mock.Mock( return_value=EMCVMAXCommonData.device_map) @@ -5238,6 +5267,8 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): common = self.driver.common common.get_target_wwns = mock.Mock( return_value=EMCVMAXCommonData.target_wwns) + self.driver.common._get_correct_port_group = mock.Mock( + return_value=self.data.port_group) data = self.driver.initialize_connection( self.data.test_volume, self.data.connector) # Test the no lookup service, pre-zoned case. @@ -6207,6 +6238,8 @@ class EMCV3DriverTestCase(test.TestCase): return_value=EMCVMAXCommonData.target_wwns) self.driver.common._initial_setup = mock.Mock( return_value=self.default_extraspec()) + self.driver.common._get_correct_port_group = mock.Mock( + return_value=self.data.port_group) data = self.driver.initialize_connection( self.data.test_volume_v3, self.data.connector) # Test the no lookup service, pre-zoned case. diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 0ba3cac19..1e4fede9d 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -323,8 +323,7 @@ class EMCVMAXCommon(object): {'volume': volumename}) device_info = self.find_device_number(volume, connector['host']) - device_number = device_info['hostlunid'] - if device_number is None: + if 'hostlunid' not in device_info: LOG.info(_LI("Volume %s is not mapped. No volume to unmap."), volumename) return @@ -370,13 +369,15 @@ class EMCVMAXCommon(object): :returns: dict -- deviceInfoDict - device information dict :raises: VolumeBackendAPIException """ + portGroupName = None extraSpecs = self._initial_setup(volume) volumeName = volume['name'] LOG.info(_LI("Initialize connection: %(volume)s."), {'volume': volumeName}) self.conn = self._get_ecom_connection() - deviceInfoDict = self.find_device_number(volume, connector['host']) + deviceInfoDict = self._wrap_find_device_number( + volume, connector['host']) maskingViewDict = self._populate_masking_dict( volume, connector, extraSpecs) @@ -392,17 +393,23 @@ class EMCVMAXCommon(object): "The device number is %(deviceNumber)s."), {'volume': volumeName, 'deviceNumber': deviceNumber}) + # Special case, we still need to get the iscsi ip address. + portGroupName = ( + self._get_correct_port_group( + deviceInfoDict, maskingViewDict['storageSystemName'])) + else: - deviceInfoDict = self._attach_volume( + deviceInfoDict, portGroupName = self._attach_volume( volume, connector, extraSpecs, maskingViewDict, True) else: - deviceInfoDict = self._attach_volume( - volume, connector, extraSpecs, maskingViewDict) + deviceInfoDict, portGroupName = ( + self._attach_volume( + volume, connector, extraSpecs, maskingViewDict)) if self.protocol.lower() == 'iscsi': return self._find_ip_protocol_endpoints( self.conn, deviceInfoDict['storagesystem'], - maskingViewDict['pgGroupName']) + portGroupName) else: return deviceInfoDict @@ -419,6 +426,7 @@ class EMCVMAXCommon(object): :param maskingViewDict: masking view information :param isLiveMigration: boolean, can be None :returns: dict -- deviceInfoDict + String -- port group name :raises: VolumeBackendAPIException """ volumeName = volume['name'] @@ -449,7 +457,7 @@ class EMCVMAXCommon(object): raise exception.VolumeBackendAPIException( data=exception_message) - return deviceInfoDict + return deviceInfoDict, rollbackDict['pgGroupName'] def _is_same_host(self, connector, deviceInfoDict): """Check if the host is the same. @@ -470,6 +478,48 @@ class EMCVMAXCommon(object): return True return False + def _get_correct_port_group(self, deviceInfoDict, storageSystemName): + """Get the portgroup name from the existing masking view. + + :params deviceInfoDict: the device info dictionary + :params storageSystemName: storage system name + :returns: String port group name + """ + if ('controller' in deviceInfoDict and + deviceInfoDict['controller'] is not None): + maskingViewInstanceName = deviceInfoDict['controller'] + try: + maskingViewInstance = ( + self.conn.GetInstance(maskingViewInstanceName)) + except Exception: + exception_message = (_("Unable to get the name of " + "the masking view.")) + raise exception.VolumeBackendAPIException( + data=exception_message) + + # Get the portgroup from masking view + portGroupInstanceName = ( + self.masking._get_port_group_from_masking_view( + self.conn, + maskingViewInstance['ElementName'], + storageSystemName)) + try: + portGroupInstance = ( + self.conn.GetInstance(portGroupInstanceName)) + portGroupName = ( + portGroupInstance['ElementName']) + except Exception: + exception_message = (_("Unable to get the name of " + "the portgroup.")) + raise exception.VolumeBackendAPIException( + data=exception_message) + else: + exception_message = (_("Cannot get the portgroup from " + "the masking view.")) + raise exception.VolumeBackendAPIException( + data=exception_message) + return portGroupName + def terminate_connection(self, volume, connector): """Disallow connection from connector. @@ -1426,6 +1476,9 @@ class EMCVMAXCommon(object): 'initiator': foundinitiatornames}) return foundinitiatornames + def _wrap_find_device_number(self, volume, host): + return self.find_device_number(volume, host) + def find_device_number(self, volume, host): """Given the volume dict find a device number. @@ -1438,6 +1491,7 @@ class EMCVMAXCommon(object): """ maskedvols = [] data = {} + foundController = None foundNumDeviceNumber = None foundMaskingViewName = None volumeName = volume['name'] @@ -1455,9 +1509,9 @@ class EMCVMAXCommon(object): if index > -1: unitinstance = self.conn.GetInstance(unitname, LocalOnly=False) - numDeviceNumber = int(unitinstance['DeviceNumber'], - 16) + numDeviceNumber = int(unitinstance['DeviceNumber'], 16) foundNumDeviceNumber = numDeviceNumber + foundController = controller controllerInstance = self.conn.GetInstance(controller, LocalOnly=False) propertiesList = controllerInstance.properties.items() @@ -1468,7 +1522,8 @@ class EMCVMAXCommon(object): devicedict = {'hostlunid': foundNumDeviceNumber, 'storagesystem': storageSystemName, - 'maskingview': foundMaskingViewName} + 'maskingview': foundMaskingViewName, + 'controller': foundController} maskedvols.append(devicedict) if not maskedvols: @@ -4350,12 +4405,14 @@ class EMCVMAXCommon(object): def _find_ip_protocol_endpoints(self, conn, storageSystemName, portgroupname): - """Find the IP protocol endpoint for ISCSI + """Find the IP protocol endpoint for ISCSI. :param storageSystemName: the system name :param portgroupname: the portgroup name :returns: foundIpAddresses """ + LOG.debug("The portgroup name for iscsiadm is %(pg)s", + {'pg': portgroupname}) foundipaddresses = [] configservice = ( self.utils.find_controller_configuration_service( diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index d64af926e..38405e5b7 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -58,6 +58,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): for VMAX3 (bug #1520549) - necessary updates for CG changes (#1534616) - Changing PercentSynced to CopyState (bug #1517103) + - Getting iscsi ip from port in existing masking view """ 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 78fd1d1e1..d1cde1c4f 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -64,6 +64,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): for VMAX3 (bug #1520549) - necessary updates for CG changes (#1534616) - Changing PercentSynced to CopyState (bug #1517103) + - Getting iscsi ip from port in existing masking view """ VERSION = "2.3.0" diff --git a/cinder/volume/drivers/emc/emc_vmax_masking.py b/cinder/volume/drivers/emc/emc_vmax_masking.py index 66127d90c..d770e58be 100644 --- a/cinder/volume/drivers/emc/emc_vmax_masking.py +++ b/cinder/volume/drivers/emc/emc_vmax_masking.py @@ -89,6 +89,7 @@ class EMCVMAXMasking(object): defaultStorageGroupInstanceName = None fastPolicyName = None assocStorageGroupName = None + storageGroupInstanceName = None if isLiveMigration is False: if isV3: defaultStorageGroupInstanceName = ( @@ -127,6 +128,11 @@ class EMCVMAXMasking(object): {'maskingViewName': maskingViewDict['maskingViewName']}) errorMessage = e + rollbackDict['pgGroupName'], errorMessage = ( + self._get_port_group_name_from_mv( + conn, maskingViewDict['maskingViewName'], + maskingViewDict['storageSystemName'])) + if not errorMessage: # Only after the masking view has been validated, add the # volume to the storage group and recheck that it has been @@ -2618,3 +2624,34 @@ class EMCVMAXMasking(object): foundHardwareIDsInstanceNames.append(hardwareIdInstanceName) return foundHardwareIDsInstanceNames + + def _get_port_group_name_from_mv(self, conn, maskingViewName, + storageSystemName): + """Get the port group name from the masking view. + + :param conn: the connection to the ecom server + :param maskingViewName: the masking view name + :param storageSystemName: the storage system name + :returns: String - port group name + String - error message + """ + errorMessage = None + portGroupName = None + portGroupInstanceName = ( + self._get_port_group_from_masking_view( + conn, maskingViewName, storageSystemName)) + if portGroupInstanceName is None: + LOG.error(_LE( + "Cannot get port group from masking view: " + "%(maskingViewName)s. "), + {'maskingViewName': maskingViewName}) + else: + try: + portGroupInstance = ( + conn.GetInstance(portGroupInstanceName)) + portGroupName = ( + portGroupInstance['ElementName']) + except Exception: + LOG.error(_LE( + "Cannot get port group name.")) + return portGroupName, errorMessage -- 2.45.2