]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Return multiple iSCSI portals in VNX Cinder driver
authorTina <tina.tang@emc.com>
Wed, 5 Aug 2015 03:15:03 +0000 (23:15 -0400)
committerTina <tina.tang@emc.com>
Thu, 20 Aug 2015 21:24:18 +0000 (17:24 -0400)
Return multiple iSCSI portals (using keys target_portals, target_iqns
and target_luns) in VNX Cinder driver whether the multipath option
is used or not. A single portal is continuously being returned in
target_portal, target_iqn and target_lun for drivers other than
libvirt driver.

Change-Id: Ibfd989d10a65a3b87647f3d7a4ff3596d822eec9
Closes-Bug: #1482017

cinder/tests/unit/test_emc_vnxdirect.py
cinder/volume/drivers/emc/emc_vnx_cli.py

index 55a51dab3424dacdbb9a8a9f71d5254cf20b29fb..0cb240a031eeeb8117a1c8107b863916a556479f 100644 (file)
@@ -792,26 +792,32 @@ VLAN ID:  Disabled
 IP Address:  192.168.4.53
 """, 0)
 
-    iscsi_connection_info = \
-        {'data': {'target_discovered': True,
-                  'target_iqn':
-                  'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
-                  'target_lun': 2,
-                  'target_portal': '10.244.214.118:3260',
-                  'volume_id': '1'},
-         'driver_volume_type': 'iscsi'}
-
-    iscsi_connection_info_mp = \
-        {'data': {'target_discovered': True,
-                  'target_iqns': [
-                      'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
-                      'iqn.1992-04.com.emc:cx.fnm00124000215.a5'],
-                  'target_luns': [2, 2],
-                  'target_portals': [
-                      '10.244.214.118:3260',
-                      '10.244.214.119:3260'],
-                  'volume_id': '1'},
-         'driver_volume_type': 'iscsi'}
+    iscsi_connection_info = {
+        'data': {'target_discovered': True,
+                 'target_iqn':
+                 'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
+                 'target_lun': 2,
+                 'target_portal': '10.244.214.118:3260',
+                 'target_iqns': ['iqn.1992-04.com.emc:cx.fnm00124000215.a4'],
+                 'target_luns': [2],
+                 'target_portals': ['10.244.214.118:3260'],
+                 'volume_id': '1'},
+        'driver_volume_type': 'iscsi'}
+
+    iscsi_connection_info_mp = {
+        'data': {'target_discovered': True,
+                 'target_iqns': [
+                     'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
+                     'iqn.1992-04.com.emc:cx.fnm00124000215.a5'],
+                 'target_iqn': 'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
+                 'target_luns': [2, 2],
+                 'target_lun': 2,
+                 'target_portals': [
+                     '10.244.214.118:3260',
+                     '10.244.214.119:3260'],
+                 'target_portal': '10.244.214.118:3260',
+                 'volume_id': '1'},
+        'driver_volume_type': 'iscsi'}
 
     PING_OK = ("Reply from 10.0.0.2:  bytes=32 time=1ms TTL=30\n" +
                "Reply from 10.0.0.2:  bytes=32 time=1ms TTL=30\n" +
@@ -2038,6 +2044,10 @@ Time Remaining:  0 second(s)
 
     @mock.patch('random.randint',
                 mock.Mock(return_value=0))
+    @mock.patch('cinder.volume.drivers.emc.emc_vnx_cli.'
+                'CommandLineHelper.ping_node',
+                mock.Mock(return_value=True))
+    @mock.patch('random.shuffle', mock.Mock(return_value=0))
     def test_initialize_connection_multipath(self):
         self.configuration.initiator_auto_registration = False
 
@@ -4049,6 +4059,111 @@ Time Remaining:  0 second(s)
         except NotImplementedError:
             self.fail('Interface unmanage need to be implemented')
 
+    @mock.patch("random.shuffle", mock.Mock())
+    def test_find_available_iscsi_targets_without_pingnode(self):
+        self.configuration.iscsi_initiators = None
+        self.driverSetup()
+        port_a1 = {'Port WWN': 'fake_iqn_a1',
+                   'SP': 'A',
+                   'Port ID': 1,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_a1'}
+        port_a2 = {'Port WWN': 'fake_iqn_a2',
+                   'SP': 'A',
+                   'Port ID': 2,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_a2'}
+        port_b1 = {'Port WWN': 'fake_iqn_b1',
+                   'SP': 'B',
+                   'Port ID': 1,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_b1'}
+        all_targets = {'A': [port_a1, port_a2],
+                       'B': [port_b1]}
+        targets = self.driver.cli._client.find_available_iscsi_targets(
+            'fakehost',
+            'B',
+            {('A', 2), ('B', 1)},
+            all_targets)
+        self.assertEqual([port_b1, port_a2], targets)
+
+    @mock.patch("random.shuffle", mock.Mock())
+    @mock.patch.object(emc_vnx_cli.CommandLineHelper,
+                       'ping_node')
+    def test_find_available_iscsi_targets_with_pingnode(self, ping_node):
+        self.configuration.iscsi_initiators = (
+            '{"fakehost": ["10.0.0.2"]}')
+        self.driverSetup()
+        port_a1 = {'Port WWN': 'fake_iqn_a1',
+                   'SP': 'A',
+                   'Port ID': 1,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_a1'}
+        port_a2 = {'Port WWN': 'fake_iqn_a2',
+                   'SP': 'A',
+                   'Port ID': 2,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_a2'}
+        port_b1 = {'Port WWN': 'fake_iqn_b1',
+                   'SP': 'B',
+                   'Port ID': 1,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_b1'}
+        all_targets = {'A': [port_a1, port_a2],
+                       'B': [port_b1]}
+        ping_node.side_effect = [False, True]
+        targets = self.driver.cli._client.find_available_iscsi_targets(
+            'fakehost',
+            'B',
+            {('A', 2), ('A', 1), ('B', 1)},
+            all_targets)
+        self.assertEqual([port_a1, port_b1, port_a2], targets)
+        ping_node.side_effect = [False, False, True]
+        targets = self.driver.cli._client.find_available_iscsi_targets(
+            'fakehost',
+            'B',
+            {('A', 2), ('A', 1), ('B', 1)},
+            all_targets)
+        self.assertEqual([port_a2, port_b1, port_a1], targets)
+
+    @mock.patch('cinder.volume.drivers.emc.emc_vnx_cli.'
+                'EMCVnxCliBase.get_lun_owner',
+                mock.Mock(return_value='A'))
+    @mock.patch('cinder.volume.drivers.emc.emc_vnx_cli.'
+                'CommandLineHelper.get_registered_spport_set',
+                mock.Mock())
+    @mock.patch.object(emc_vnx_cli.CommandLineHelper,
+                       'find_available_iscsi_targets')
+    def test_vnx_get_iscsi_properties(self, find_available_iscsi_targets):
+        self.driverSetup()
+        port_a1 = {'Port WWN': 'fake_iqn_a1',
+                   'SP': 'A',
+                   'Port ID': 1,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_a1'}
+        port_b1 = {'Port WWN': 'fake_iqn_b1',
+                   'SP': 'B',
+                   'Port ID': 1,
+                   'Virtual Port ID': 0,
+                   'IP Address': 'fake_ip_b1'}
+        find_available_iscsi_targets.return_value = [port_a1, port_b1]
+        connect_info = self.driver.cli.vnx_get_iscsi_properties(
+            self.testData.test_volume, self.testData.connector, 1, '')
+        expected_info = {
+            'target_discovered': True,
+            'target_iqns': [
+                'fake_iqn_a1',
+                'fake_iqn_b1'],
+            'target_iqn': 'fake_iqn_a1',
+            'target_luns': [1, 1],
+            'target_lun': 1,
+            'target_portals': [
+                'fake_ip_a1:3260',
+                'fake_ip_b1:3260'],
+            'target_portal': 'fake_ip_a1:3260',
+            'volume_id': '1'}
+        self.assertEqual(expected_info, connect_info)
+
 
 class EMCVNXCLIDArrayBasedDriverTestCase(DriverTestCaseBase):
     def setUp(self):
index 9e16a30315ef0f6c95eee5fee562a2728d2beff2..1d22b92fbf0790f527dd0e810d9871f35a497d74 100644 (file)
@@ -1592,57 +1592,60 @@ class CommandLineHelper(object):
     def find_available_iscsi_targets(self, hostname,
                                      preferred_sp,
                                      registered_spport_set,
-                                     all_iscsi_targets,
-                                     multipath=False):
+                                     all_iscsi_targets):
+        """Finds available iscsi targets for a host.
+
+        When the iscsi_initiator_map is configured, the driver will find
+        an accessible portal and put it as the first portal in the portal
+        list to ensure the accessible portal will be used when multipath
+        is not used. All the registered portals will be returned for Nova
+        to clean up all the unused devices related to this LUN created by
+        logging into these portals during attaching other LUNs on VNX.
+        """
+
         if self.iscsi_initiator_map and hostname in self.iscsi_initiator_map:
             iscsi_initiator_ips = list(self.iscsi_initiator_map[hostname])
             random.shuffle(iscsi_initiator_ips)
         else:
             iscsi_initiator_ips = None
+
         # Check the targets on the owner first
         if preferred_sp == 'A':
             target_sps = ('A', 'B')
         else:
             target_sps = ('B', 'A')
 
-        if multipath:
-            target_portals = []
-            for target_sp in target_sps:
-                sp_portals = all_iscsi_targets[target_sp]
-                for portal in sp_portals:
-                    spport = (portal['SP'], portal['Port ID'])
-                    if spport not in registered_spport_set:
-                        LOG.debug("Skip SP Port %(port)s since "
-                                  "no path from %(host)s is through it",
-                                  {'port': spport,
-                                   'host': hostname})
-                        continue
-                    target_portals.append(portal)
-            return target_portals
-
+        target_portals = []
         for target_sp in target_sps:
-            target_portals = list(all_iscsi_targets[target_sp])
-            random.shuffle(target_portals)
-            for target_portal in target_portals:
-                spport = (target_portal['SP'], target_portal['Port ID'])
+            sp_portals = all_iscsi_targets[target_sp]
+            random.shuffle(sp_portals)
+            for portal in sp_portals:
+                spport = (portal['SP'], portal['Port ID'])
                 if spport not in registered_spport_set:
-                    LOG.debug("Skip SP Port %(port)s since "
-                              "no path from %(host)s is through it",
-                              {'port': spport,
-                               'host': hostname})
+                    LOG.debug(
+                        "Skip SP Port %(port)s since "
+                        "no path from %(host)s is through it.",
+                        {'port': spport,
+                         'host': hostname})
                     continue
-                if iscsi_initiator_ips is not None:
-                    for initiator_ip in iscsi_initiator_ips:
-                        if self.ping_node(target_portal, initiator_ip):
-                            return [target_portal]
+                target_portals.append(portal)
+
+        main_portal_index = None
+        if iscsi_initiator_ips:
+            for i, portal in enumerate(target_portals):
+                for initiator_ip in iscsi_initiator_ips:
+                    if self.ping_node(portal, initiator_ip):
+                        main_portal_index = i
+                        break
                 else:
-                    LOG.debug("No iSCSI IP address of %(hostname)s is known. "
-                              "Return a random target portal %(portal)s.",
-                              {'hostname': hostname,
-                               'portal': target_portal})
-                    return [target_portal]
+                    # Else for the for loop. If there is no main portal found,
+                    # continue to try next initiator IP.
+                    continue
+                break
 
-        return None
+        if main_portal_index is not None:
+            target_portals.insert(0, target_portals.pop(main_portal_index))
+        return target_portals
 
     def _is_sp_unavailable_error(self, out):
         error_pattern = '(^Error.*Message.*End of data stream.*)|'\
@@ -3176,7 +3179,6 @@ class EMCVnxCliBase(object):
 
     def vnx_get_iscsi_properties(self, volume, connector, hlu, sg_raw_output):
         storage_group = connector['host']
-        multipath = connector.get('multipath', False)
         owner_sp = self.get_lun_owner(volume)
         registered_spports = self._client.get_registered_spport_set(
             connector['initiator'],
@@ -3185,40 +3187,31 @@ class EMCVnxCliBase(object):
         targets = self._client.find_available_iscsi_targets(
             storage_group, owner_sp,
             registered_spports,
-            self.iscsi_targets,
-            multipath)
-
-        properties = {}
-
-        if not multipath:
-            properties = {'target_discovered': False,
-                          'target_iqn': 'unknown',
-                          'target_portal': 'unknown',
-                          'target_lun': 'unknown',
-                          'volume_id': volume['id']}
-            if targets:
-                properties['target_discovered'] = True
-                properties['target_iqn'] = targets[0]['Port WWN']
-                properties['target_portal'] = \
-                    "%s:3260" % targets[0]['IP Address']
-                properties['target_lun'] = hlu
+            self.iscsi_targets)
+        properties = {'target_discovered': False,
+                      'target_iqn': 'unknown',
+                      'target_iqns': None,
+                      'target_portal': 'unknown',
+                      'target_portals': None,
+                      'target_lun': 'unknown',
+                      'target_luns': None,
+                      'volume_id': volume['id']}
+        if targets:
+            properties['target_discovered'] = True
+            properties['target_iqns'] = [t['Port WWN'] for t in targets]
+            properties['target_iqn'] = properties['target_iqns'][0]
+            properties['target_portals'] = [
+                "%s:3260" % t['IP Address'] for t in targets]
+            properties['target_portal'] = properties['target_portals'][0]
+            properties['target_luns'] = [hlu] * len(targets)
+            properties['target_lun'] = hlu
         else:
-            properties = {'target_discovered': False,
-                          'target_iqns': None,
-                          'target_portals': None,
-                          'target_luns': None,
-                          'volume_id': volume['id']}
-            if targets:
-                properties['target_discovered'] = True
-                properties['target_iqns'] = [t['Port WWN'] for t in targets]
-                properties['target_portals'] = [
-                    "%s:3260" % t['IP Address'] for t in targets]
-                properties['target_luns'] = [hlu] * len(targets)
-
-        if not targets:
             LOG.error(_LE('Failed to find available iSCSI targets for %s.'),
                       storage_group)
 
+        LOG.debug('The iSCSI properties for %(host)s is %(properties)s.',
+                  {'host': storage_group,
+                   'properties': properties})
         return properties
 
     def vnx_get_fc_properties(self, connector, device_number):