]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
EMC VNX Cinder Driver iSCSI multipath enhancement
authorXi Yang <xi.yang@emc.com>
Wed, 11 Feb 2015 07:06:50 +0000 (02:06 -0500)
committerXi Yang <xi.yang@emc.com>
Thu, 12 Feb 2015 05:25:24 +0000 (00:25 -0500)
This commit is to be consistent with the iSCSI multipath
ehancement in Cinder and Nova:
* Return multiple portals and iqns when multipath=True is
  specified in the connector info
* Return one portal and iqn when multipath=False is specified
  in the connector info

Change-Id: I47376ad6ac38e84569d92d62d3d8daa1359d797d
Implements: blueprint emc-vnx-driver-iscsi-multipath-enhancement

cinder/tests/test_emc_vnxdirect.py
cinder/volume/drivers/emc/emc_cli_fc.py
cinder/volume/drivers/emc/emc_cli_iscsi.py
cinder/volume/drivers/emc/emc_vnx_cli.py

index 6a5c873d1c05a4aa7adf59c7969b3d0941e86b50..405e466fc95f80d387a3ebf5bbc730930e22be2b 100644 (file)
@@ -521,7 +521,8 @@ Available Capacity (GBs):  3257.851
                   'target_iqn':
                   'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
                   'target_lun': 2,
-                  'target_portal': '10.244.214.118:3260'},
+                  'target_portal': '10.244.214.118:3260',
+                  'volume_id': '1'},
          'driver_volume_type': 'iscsi'}
 
     iscsi_connection_info_rw = \
@@ -530,7 +531,21 @@ Available Capacity (GBs):  3257.851
                   'target_iqn':
                   'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
                   'target_lun': 2,
-                  'target_portal': '10.244.214.118:3260'},
+                  'target_portal': '10.244.214.118:3260',
+                  'volume_id': '1'},
+         'driver_volume_type': 'iscsi'}
+
+    iscsi_connection_info_mp = \
+        {'data': {'access_mode': 'rw',
+                  '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'}
 
     PING_OK = ("Reply from 10.0.0.2:  bytes=32 time=1ms TTL=30\n" +
@@ -674,6 +689,25 @@ Available Capacity (GBs):  3257.851
             1               1
         Shareable:             YES""" % sgname, 0)
 
+    def STORAGE_GROUP_HAS_MAP_MP(self, sgname):
+
+        return ("""\
+        Storage Group Name:    %s
+        Storage Group UID:     54:46:57:0F:15:A2:E3:11:9A:8D:FF:E5:3A:03:FD:6D
+        HBA/SP Pairs:
+
+          HBA UID                                          SP Name     SPPort
+          -------                                          -------     ------
+          iqn.1993-08.org.debian:01:222                     SP A         4
+          iqn.1993-08.org.debian:01:222                     SP A         5
+
+        HLU/ALU Pairs:
+
+          HLU Number     ALU Number
+          ----------     ----------
+            1               1
+        Shareable:             YES""" % sgname, 0)
+
     def STORAGE_GROUP_HAS_MAP_2(self, sgname):
 
         return ("""\
@@ -1111,7 +1145,7 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase):
             "volume backend name is not correct")
         self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial")
         self.assertTrue(
-            stats['driver_version'] == "05.00.00",
+            stats['driver_version'] == "05.01.00",
             "driver version is incorrect.")
 
     def test_get_volume_stats_too_many_luns(self):
@@ -1412,6 +1446,46 @@ Time Remaining:  0 second(s)
                                                           '10.0.0.2'))]
         fake_cli.assert_has_calls(expected)
 
+    @mock.patch('random.randint',
+                mock.Mock(return_value=0))
+    def test_initialize_connection_multipath(self):
+        self.configuration.initiator_auto_registration = False
+
+        commands = [('storagegroup', '-list', '-gname', 'fakehost')]
+        results = [self.testData.STORAGE_GROUP_HAS_MAP_MP('fakehost')]
+        fake_cli = self.driverSetup(commands, results)
+        self.driver.cli.iscsi_targets = {
+            'A': [
+                {'Port WWN': 'iqn.1992-04.com.emc:cx.fnm00124000215.a4',
+                 'SP': 'A',
+                 'Port ID': 4,
+                 'Virtual Port ID': 0,
+                 'IP Address': '10.244.214.118'},
+                {'Port WWN': 'iqn.1992-04.com.emc:cx.fnm00124000215.a5',
+                 'SP': 'A',
+                 'Port ID': 5,
+                 'Virtual Port ID': 1,
+                 'IP Address': '10.244.214.119'}],
+            'B': []}
+        test_volume_rw = self.testData.test_volume_rw.copy()
+        test_volume_rw['provider_location'] = 'system^fakesn|type^lun|id^1'
+        connector_m = dict(self.testData.connector)
+        connector_m['multipath'] = True
+        connection_info = self.driver.initialize_connection(
+            test_volume_rw,
+            connector_m)
+
+        self.assertEqual(connection_info,
+                         self.testData.iscsi_connection_info_mp)
+
+        expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost',
+                              poll=False),
+                    mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1,
+                              '-gname', 'fakehost', poll=False),
+                    mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'),
+                              poll=False)]
+        fake_cli.assert_has_calls(expected)
+
     @mock.patch(
         "oslo_concurrency.processutils.execute",
         mock.Mock(
@@ -3156,7 +3230,7 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase):
             "volume backend name is not correct")
         self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial")
         self.assertTrue(
-            stats['driver_version'] == "05.00.00",
+            stats['driver_version'] == "05.01.00",
             "driver version is incorrect.")
 
     def test_get_volume_stats_too_many_luns(self):
index 40580492ba10dd81165abe76335e14562ff2f54d..e1906583265ffd1766e9eee17611f418c49cc2fd 100644 (file)
@@ -51,6 +51,7 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
                 Initiator Auto Deregistration,
                 Force Deleting LUN in Storage Groups,
                 robust enhancement
+        5.1.0 - iSCSI multipath enhancement
     """
 
     def __init__(self, *args, **kwargs):
index 43b0819f5db3eaee8659979099c235b94e2cea45..cc0b9f78ee8535c6df81a7fcd19a9c43f8579814 100644 (file)
@@ -48,6 +48,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
                 Initiator Auto Deregistration,
                 Force Deleting LUN in Storage Groups,
                 robust enhancement
+        5.1.0 - iSCSI multipath enhancement
     """
 
     def __init__(self, *args, **kwargs):
@@ -117,7 +118,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
 
         The iscsi driver returns a driver_volume_type of 'iscsi'.
         the format of the driver data is defined in vnx_get_iscsi_properties.
-        Example return value::
+        Example return value (multipath is not enabled)::
 
             {
                 'driver_volume_type': 'iscsi'
@@ -130,6 +131,20 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
                 }
             }
 
+        Example return value (multipath is enabled)::
+
+            {
+                'driver_volume_type': 'iscsi'
+                'data': {
+                    'target_discovered': True,
+                    'target_iqns': ['iqn.2010-10.org.openstack:volume-00001',
+                                    'iqn.2010-10.org.openstack:volume-00002'],
+                    'target_portals': ['127.0.0.1:3260', '127.0.1.1:3260'],
+                    'target_luns': [1, 1],
+                    'access_mode': 'rw'
+                }
+            }
+
         """
         return self.cli.initialize_connection(volume, connector)
 
index 41c0acd392f87481d203b0ce3660897c6bcbe0ca..fe2d40e7b416392f9ce94fec688a0482cdf49a41 100644 (file)
@@ -1337,10 +1337,11 @@ class CommandLineHelper(object):
                     connection_pingnode)
         return False
 
-    def find_avaialable_iscsi_target_one(self, hostname,
-                                         preferred_sp,
-                                         registered_spport_set,
-                                         all_iscsi_targets):
+    def find_available_iscsi_targets(self, hostname,
+                                     preferred_sp,
+                                     registered_spport_set,
+                                     all_iscsi_targets,
+                                     multipath=False):
         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)
@@ -1352,6 +1353,21 @@ class CommandLineHelper(object):
         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
+
         for target_sp in target_sps:
             target_portals = list(all_iscsi_targets[target_sp])
             random.shuffle(target_portals)
@@ -1366,7 +1382,7 @@ class CommandLineHelper(object):
                 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
+                            return [target_portal]
                 else:
                     LOG.debug("No iSCSI IP address of %(hostname)s is known. "
                               "Return a random target portal %(portal)s.",
@@ -1533,7 +1549,7 @@ class CommandLineHelper(object):
 class EMCVnxCliBase(object):
     """This class defines the functions to use the native CLI functionality."""
 
-    VERSION = '05.00.00'
+    VERSION = '05.01.00'
     stats = {'driver_version': VERSION,
              'free_capacity_gb': 'unknown',
              'reserved_percentage': 0,
@@ -2515,34 +2531,54 @@ 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'],
             storage_group,
             sg_raw_output)
-        target = self._client.find_avaialable_iscsi_target_one(
+        targets = self._client.find_available_iscsi_targets(
             storage_group, owner_sp,
             registered_spports,
-            self.iscsi_targets)
-        properties = {'target_discovered': True,
-                      'target_iqn': 'unknown',
-                      'target_portal': 'unknown',
-                      'target_lun': 'unknown',
-                      'volume_id': volume['id']}
-        if target:
-            properties = {'target_discovered': True,
-                          'target_iqn': target['Port WWN'],
-                          'target_portal': "%s:3260" % target['IP Address'],
-                          'target_lun': hlu}
-            LOG.debug("iSCSI Properties: %s", properties)
-            auth = volume['provider_auth']
-            if auth:
-                (auth_method, auth_username, auth_secret) = auth.split()
-                properties['auth_method'] = auth_method
-                properties['auth_username'] = auth_username
-                properties['auth_password'] = auth_secret
+            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
+
+                auth = volume['provider_auth']
+                if auth:
+                    (auth_method, auth_username, auth_secret) = auth.split()
+                    properties['auth_method'] = auth_method
+                    properties['auth_username'] = auth_username
+                    properties['auth_password'] = auth_secret
         else:
-            LOG.error(_LE('Failed to find an available iSCSI targets for %s.'),
+            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)
 
         return properties