From cab08ef256e91566077d2e4ec792b68665dc7780 Mon Sep 17 00:00:00 2001 From: Tina Date: Tue, 4 Aug 2015 23:15:03 -0400 Subject: [PATCH] Return multiple iSCSI portals in VNX Cinder driver 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 | 155 ++++++++++++++++++++--- cinder/volume/drivers/emc/emc_vnx_cli.py | 125 +++++++++--------- 2 files changed, 194 insertions(+), 86 deletions(-) diff --git a/cinder/tests/unit/test_emc_vnxdirect.py b/cinder/tests/unit/test_emc_vnxdirect.py index 55a51dab3..0cb240a03 100644 --- a/cinder/tests/unit/test_emc_vnxdirect.py +++ b/cinder/tests/unit/test_emc_vnxdirect.py @@ -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): diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 9e16a3031..1d22b92fb 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -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): -- 2.45.2