From: Xing Yang Date: Thu, 1 Oct 2015 02:28:56 +0000 (-0400) Subject: VMAX Target iSCSI IP Address X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=3793ed0491ec484e85565508ae890020730cfbeb;p=openstack-build%2Fcinder-build.git VMAX Target iSCSI IP Address In VMAX iSCSI driver, the iscsi_ip_address was hardcoded in cinder.conf. This may have issues with multi-portgroup environment. If a customer has multiple portgroups containing different ports with different iSCSI IP addresses, then ‘iscsiadm‘ command cannot use more than the one hardcoded IP address in its sendtargets operation. This patch addresses this by examining the ports in the portgroup of the masking view used in the attach operation. Each port has a corresponding iSCSI IP address. A portgroup with only one port will have one IP address; a portgroup with multiple ports will have multiple IP addresses. Even if there is more than one IP address, the first in the list is likely to result in a successful iscsiadm -sendtargets. Closes-Bug: #1501678 Change-Id: I52ab6b278a114d55aec5e66b38ff76fd1bfb1b49 --- diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index 49d75f305..2050375e8 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -89,6 +89,10 @@ class EMC_StorageHardwareID(dict): pass +class CIM_IPProtocolEndpoint(dict): + pass + + class SE_ReplicationSettingData(dict): def __init__(self, *args, **kwargs): self['DefaultInstance'] = self.createInstance() @@ -144,6 +148,12 @@ class Fake_CIMProperty(object): cimproperty.value = [2, 10] return cimproperty + def fake_getipv4address(self): + cimproperty = Fake_CIMProperty() + cimproperty.key = 'IPv4Address' + cimproperty.value = '10.10.10.10' + return cimproperty + class Fake_CIM_TierPolicyServiceCapabilities(object): @@ -764,6 +774,10 @@ 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() else: result = self._default_assocnames(objectpath) return result @@ -989,6 +1003,12 @@ class FakeEcomConnection(object): def _assocnames_portgroup(self): return self._enum_portgroup() + def _assocnames_memberofcollection(self): + return self._enum_hostedservice() + + def _assocnames_bindsto(self): + return self._enum_ipprotocolendpoint() + def _default_assocnames(self, objectpath): return objectpath @@ -1143,6 +1163,9 @@ class FakeEcomConnection(object): repServCpblInstance.properties = properties return repServCpblInstance + def _getinstance_ipprotocolendpoint(self, objectpath): + return self._enum_ipprotocolendpoint()[0] + def _default_getinstance(self, objectpath): return objectpath @@ -1589,6 +1612,20 @@ class FakeEcomConnection(object): swIdentities.append(swIdentity) return swIdentities + def _enum_ipprotocolendpoint(self): + ipprotocolendpoints = [] + ipprotocolendpoint = CIM_IPProtocolEndpoint() + ipprotocolendpoint['CreationClassName'] = 'CIM_IPProtocolEndpoint' + ipprotocolendpoint['SystemName'] = self.data.storage_system + classcimproperty = Fake_CIMProperty() + ipv4addresscimproperty = ( + classcimproperty.fake_getipv4address()) + properties = {u'IPv4Address': ipv4addresscimproperty} + ipprotocolendpoint.properties = properties + ipprotocolendpoint.path = ipprotocolendpoint + ipprotocolendpoints.append(ipprotocolendpoint) + return ipprotocolendpoints + def _default_enum(self): names = [] name = {} @@ -1832,6 +1869,12 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): host_over_38_chars) self.assertEqual(host2, host3) + def test_find_ip_protocol_endpoints(self): + conn = self.fake_ecom_connection() + foundIpAddresses = self.driver.common._find_ip_protocol_endpoints( + conn, self.data.storage_system, self.data.port_group) + self.assertEqual('10.10.10.10', foundIpAddresses[0]) + def test_find_device_number(self): host = 'myhost' data = ( @@ -2274,7 +2317,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): conn, self.data.storage_system)) foundPortGroupInstanceName = ( - self.driver.common.masking._find_port_group( + self.driver.common.masking.find_port_group( conn, controllerConfigService, self.data.port_group)) # The port group has been found. self.assertEqual( @@ -2284,7 +2327,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.driver.common.masking.utils.get_existing_instance = mock.Mock( return_value=None) foundPortGroupInstanceName2 = ( - self.driver.common.masking._find_port_group( + self.driver.common.masking.find_port_group( conn, controllerConfigService, self.data.port_group)) # The port group has not been found as it has been deleted # externally or by another thread. diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 6b89e4dd0..f7c51d9c4 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -375,6 +375,8 @@ class EMCVMAXCommon(object): {'volume': volumeName}) self.conn = self._get_ecom_connection() deviceInfoDict = self.find_device_number(volume, connector['host']) + maskingViewDict = self._populate_masking_dict( + volume, connector, extraSpecs) if ('hostlunid' in deviceInfoDict and deviceInfoDict['hostlunid'] is not None): @@ -390,15 +392,20 @@ class EMCVMAXCommon(object): 'deviceNumber': deviceNumber}) else: deviceInfoDict = self._attach_volume( - volume, connector, extraSpecs, True) + volume, connector, extraSpecs, maskingViewDict, True) else: - deviceInfoDict = self._attach_volume(volume, connector, - extraSpecs) + deviceInfoDict = self._attach_volume( + volume, connector, extraSpecs, maskingViewDict) - return deviceInfoDict + if self.protocol.lower() == 'iscsi': + return self._find_ip_protocol_endpoints( + self.conn, deviceInfoDict['storagesystem'], + maskingViewDict['pgGroupName']) + else: + return deviceInfoDict def _attach_volume(self, volume, connector, extraSpecs, - isLiveMigration=None): + maskingViewDict, isLiveMigration=None): """Attach a volume to a host. If live migration is being undertaken then the volume @@ -407,6 +414,7 @@ class EMCVMAXCommon(object): :params volume: the volume object :params connector: the connector object :param extraSpecs: extra specifications + :param maskingViewDict: masking view information :param isLiveMigration: boolean, can be None :returns: dict -- deviceInfoDict :raises: VolumeBackendAPIException @@ -4303,3 +4311,36 @@ class EMCVMAXCommon(object): context, db, group['id'], modelUpdate['status']) return modelUpdate, volumes_model_update + + def _find_ip_protocol_endpoints(self, conn, storageSystemName, + portgroupname): + """Find the IP protocol endpoint for ISCSI + + :param storageSystemName: the system name + :param portgroupname: the portgroup name + :returns: foundIpAddresses + """ + foundipaddresses = [] + configservice = ( + self.utils.find_controller_configuration_service( + conn, storageSystemName)) + portgroupinstancename = ( + self.masking.find_port_group(conn, configservice, portgroupname)) + iscsiendpointinstancenames = ( + self.utils.get_iscsi_protocol_endpoints( + conn, portgroupinstancename)) + + for iscsiendpointinstancename in iscsiendpointinstancenames: + tcpendpointinstancenames = ( + self.utils.get_tcp_protocol_endpoints( + conn, iscsiendpointinstancename)) + for tcpendpointinstancename in tcpendpointinstancenames: + ipendpointinstancenames = ( + self.utils.get_ip_protocol_endpoints( + conn, tcpendpointinstancename)) + for ipendpointinstancename in ipendpointinstancenames: + ipaddress = ( + self.utils.get_iscsi_ip_address( + conn, ipendpointinstancename)) + foundipaddresses.append(ipaddress) + return foundipaddresses diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 8e97262ec..b120a736e 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -61,12 +61,13 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): emc_vmax_common.EMCVMAXCommon('iSCSI', self.VERSION, configuration=self.configuration)) + self.iscsi_ip_addresses = [] def check_for_setup_error(self): pass def create_volume(self, volume): - """Creates a EMC(VMAX/VNX) volume.""" + """Creates a VMAX volume.""" volpath = self.common.create_volume(volume) model_update = {} @@ -153,7 +154,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): } } """ - self.common.initialize_connection( + self.iscsi_ip_addresses = self.common.initialize_connection( volume, connector) iscsi_properties = self.smis_get_iscsi_properties( @@ -165,21 +166,42 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 'data': iscsi_properties } + def _call_iscsiadm(self, iscsi_ip_address): + """Calls iscsiadm with iscsi ip address""" + try: + (out, _err) = self._execute('iscsiadm', '-m', 'discovery', + '-t', 'sendtargets', '-p', + iscsi_ip_address, + run_as_root=True) + return out, _err, False, None + except Exception as ex: + return None, None, True, ex + def smis_do_iscsi_discovery(self, volume): + """Calls iscsiadm with each iscsi ip address in the list""" LOG.info(_LI("ISCSI provider_location not stored, using discovery.")) - if not self._check_for_iscsi_ip_address(): - LOG.error(_LE( - "You must set your iscsi_ip_address in cinder.conf.")) - - (out, _err) = self._execute('iscsiadm', '-m', 'discovery', - '-t', 'sendtargets', '-p', - self.configuration.iscsi_ip_address, - run_as_root=True) + targets = [] + if len(self.iscsi_ip_addresses) == 0: + LOG.error(_LE("The list of iscsi_ip_addresses is empty")) + return targets + + for iscsi_ip_address in self.iscsi_ip_addresses: + out, _err, go_again, ex = self._call_iscsiadm(iscsi_ip_address) + if not go_again: + break + if not out: + if ex: + exception_message = (_("Unsuccessful iscsiadm. " + "Exception is %(ex)s. ") + % {'ex': ex}) + else: + exception_message = (_("iscsiadm execution failed. ")) + raise exception.VolumeBackendAPIException(data=exception_message) LOG.info(_LI( "smis_do_iscsi_discovery is: %(out)s."), {'out': out}) - targets = [] + for target in out.splitlines(): targets.append(target) diff --git a/cinder/volume/drivers/emc/emc_vmax_masking.py b/cinder/volume/drivers/emc/emc_vmax_masking.py index e0e610084..c62ba5c26 100644 --- a/cinder/volume/drivers/emc/emc_vmax_masking.py +++ b/cinder/volume/drivers/emc/emc_vmax_masking.py @@ -497,9 +497,9 @@ class EMCVMAXMasking(object): if self._is_volume_in_storage_group( conn, storageGroupInstanceName, volumeInstance, sgGroupName): - LOG.warning(_LW( + LOG.debug( "Volume: %(volumeName)s is already part " - "of storage group %(sgGroupName)s."), + "of storage group %(sgGroupName)s.", {'volumeName': volumeName, 'sgGroupName': sgGroupName}) else: @@ -518,10 +518,10 @@ class EMCVMAXMasking(object): 'sgGroupName': sgGroupName}) LOG.error(msg) else: - LOG.debug("Successfully added %(volumeName)s to " - "%(sgGroupName)s.", - {'volumeName': volumeName, - 'sgGroupName': sgGroupName}) + LOG.info(_LI( + "Successfully added %(volumeName)s to %(sgGroupName)s."), + {'volumeName': volumeName, + 'sgGroupName': sgGroupName}) return msg @@ -752,7 +752,7 @@ class EMCVMAXMasking(object): return foundStorageGroupInstanceName - def _find_port_group(self, conn, controllerConfigService, portGroupName): + def find_port_group(self, conn, controllerConfigService, portGroupName): """Given the port Group name get the port group instance name. :param conn: connection to the ecom server @@ -1031,8 +1031,9 @@ class EMCVMAXMasking(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) - LOG.info(_LI("Created new masking view : %(maskingViewName)s."), - {'maskingViewName': maskingViewName}) + LOG.info(_LI( + "Created new masking view : %(maskingViewName)s."), + {'maskingViewName': maskingViewName}) return rc, job def find_new_masking_view(self, conn, jobDict): @@ -1148,7 +1149,7 @@ class EMCVMAXMasking(object): :param pgGroupName: the port group name :returns: instance name foundPortGroupInstanceName """ - foundPortGroupInstanceName = self._find_port_group( + foundPortGroupInstanceName = self.find_port_group( conn, controllerConfigService, pgGroupName) if foundPortGroupInstanceName is None: LOG.error(_LE( @@ -1978,9 +1979,9 @@ class EMCVMAXMasking(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) else: - LOG.debug("Masking view %(maskingViewName)s " - "successfully deleted.", - {'maskingViewName': maskingViewName}) + LOG.info(_LI( + "Masking view %(maskingViewName)s successfully deleted."), + {'maskingViewName': maskingViewName}) def _get_and_remove_rule_association( self, conn, fastPolicyName, isTieringPolicySupported, @@ -2001,9 +2002,9 @@ class EMCVMAXMasking(object): tierPolicyInstanceName = self.fast.get_tier_policy_by_name( conn, storageSystemName, fastPolicyName) - LOG.info(_LI( + LOG.debug( "Policy: %(policy)s, policy service:%(service)s, " - "masking group: %(maskingGroup)s."), + "masking group: %(maskingGroup)s.", {'policy': tierPolicyInstanceName, 'service': tierPolicyServiceInstanceName, 'maskingGroup': storageGroupInstanceName}) @@ -2280,9 +2281,9 @@ class EMCVMAXMasking(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) else: - LOG.debug("Storage Group %(storageGroupName)s " - "successfully deleted.", - {'storageGroupName': storageGroupName}) + LOG.info(_LI( + "Storage Group %(storageGroupName)s successfully deleted."), + {'storageGroupName': storageGroupName}) def _delete_storage_group(self, conn, controllerConfigService, storageGroupInstanceName, storageGroupName, diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index f29809ce6..87c97d465 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -2376,3 +2376,55 @@ class EMCVMAXUtils(object): 'last': poolName[-7:]})) else: return poolName + + def get_iscsi_protocol_endpoints(self, conn, portgroupinstancename): + """Get the iscsi protocol endpoints of a port group. + + :param conn: the ecom connection + :param portgroupinstancename: the portgroup instance name + :returns: iscsiendpoints + """ + iscsiendpoints = conn.AssociatorNames( + portgroupinstancename, + AssocClass='CIM_MemberOfCollection') + return iscsiendpoints + + def get_tcp_protocol_endpoints(self, conn, iscsiendpointinstancename): + """Get the tcp protocol endpoints associated with an iscsi endpoint + + :param conn: the ecom connection + :param iscsiendpointinstancename: the iscsi endpoint instance name + :returns: tcpendpoints + """ + tcpendpoints = conn.AssociatorNames( + iscsiendpointinstancename, + AssocClass='CIM_BindsTo') + return tcpendpoints + + def get_ip_protocol_endpoints(self, conn, tcpendpointinstancename): + """Get the ip protocol endpoints associated with an tcp endpoint + + :param conn: the ecom connection + :param tcpendpointinstancename: the tcp endpoint instance name + :returns: ipendpoints + """ + ipendpoints = conn.AssociatorNames( + tcpendpointinstancename, + AssocClass='CIM_BindsTo') + return ipendpoints + + def get_iscsi_ip_address(self, conn, ipendpointinstancename): + """Get the IPv4Address from the ip endpoint instance name + + :param conn: the ecom connection + :param ipendpointinstancename: the ip endpoint instance name + :returns: foundIpAddress + """ + foundIpAddress = None + ipendpointinstance = conn.GetInstance(ipendpointinstancename) + propertiesList = ipendpointinstance.properties.items() + for properties in propertiesList: + if properties[0] == 'IPv4Address': + cimProperties = properties[1] + foundIpAddress = cimProperties.value + return foundIpAddress