From a509ab75b906ea930d94630cf7e38f2f0d2b70f0 Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Wed, 11 Feb 2015 02:06:50 -0500 Subject: [PATCH] EMC VNX Cinder Driver iSCSI multipath enhancement 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 | 82 +++++++++++++++++++- cinder/volume/drivers/emc/emc_cli_fc.py | 1 + cinder/volume/drivers/emc/emc_cli_iscsi.py | 17 ++++- cinder/volume/drivers/emc/emc_vnx_cli.py | 88 +++++++++++++++------- 4 files changed, 157 insertions(+), 31 deletions(-) diff --git a/cinder/tests/test_emc_vnxdirect.py b/cinder/tests/test_emc_vnxdirect.py index 6a5c873d1..405e466fc 100644 --- a/cinder/tests/test_emc_vnxdirect.py +++ b/cinder/tests/test_emc_vnxdirect.py @@ -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): diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index 40580492b..e19065832 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -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): diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index 43b0819f5..cc0b9f78e 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 41c0acd39..fe2d40e7b 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -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 -- 2.45.2