]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMAX Target iSCSI IP Address
authorXing Yang <xing.yang@emc.com>
Thu, 1 Oct 2015 02:28:56 +0000 (22:28 -0400)
committerXing Yang <xing.yang@emc.com>
Mon, 5 Oct 2015 02:13:25 +0000 (22:13 -0400)
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

cinder/tests/unit/test_emc_vmax.py
cinder/volume/drivers/emc/emc_vmax_common.py
cinder/volume/drivers/emc/emc_vmax_iscsi.py
cinder/volume/drivers/emc/emc_vmax_masking.py
cinder/volume/drivers/emc/emc_vmax_utils.py

index 49d75f305f9ea651565d40cf655ddf02f5dcccf0..2050375e83dab704f2c794cf918593123380dd60 100644 (file)
@@ -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.
index 6b89e4dd067c5dc96f92b12a22ab460608597275..f7c51d9c43c89394b7ee0a317cc253d8ce47986c 100644 (file)
@@ -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
index 8e97262ec07d5b835f500e629ebeafb36e24fac4..b120a736e324a1e6e74010b0cded424fdc05f6b8 100644 (file)
@@ -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)
 
index e0e61008438987a035f87c84af7a2468130a1f95..c62ba5c26686f27f525d48976ac440bfaa6b1469 100644 (file)
@@ -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,
index f29809ce6c1238877266e85bde8ab399b985b12d..87c97d4657676d62a29dda1824eae5bfb4b9daa1 100644 (file)
@@ -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