diff --git a/os_brick/initiator/connectors/fibre_channel.py b/os_brick/initiator/connectors/fibre_channel.py index 849fb7e9a..b66ce47fd 100644 --- a/os_brick/initiator/connectors/fibre_channel.py +++ b/os_brick/initiator/connectors/fibre_channel.py @@ -20,6 +20,7 @@ from oslo_service import loopingcall import six from os_brick import exception +from os_brick.i18n import _ from os_brick import initiator from os_brick.initiator.connectors import base from os_brick.initiator import linuxfc @@ -70,12 +71,70 @@ class FibreChannelConnector(base.BaseLinuxConnector): """Where do we look for FC based volumes.""" return '/dev/disk/by-path' - def _get_possible_volume_paths(self, connection_properties, hbas): - ports = connection_properties['target_wwn'] - possible_devs = self._get_possible_devices(hbas, ports) + def _add_targets_to_connection_properties(self, connection_properties): + target_wwn = connection_properties.get('target_wwn') + target_wwns = connection_properties.get('target_wwns') + if target_wwns: + wwns = target_wwns + elif isinstance(target_wwn, list): + wwns = target_wwn + elif isinstance(target_wwn, six.string_types): + wwns = [target_wwn] + else: + wwns = [] - lun = connection_properties.get('target_lun', 0) - host_paths = self._get_host_devices(possible_devs, lun) + target_lun = connection_properties.get('target_lun', 0) + target_luns = connection_properties.get('target_luns') + if target_luns: + luns = target_luns + elif isinstance(target_lun, int): + luns = [target_lun] + else: + luns = [] + + if len(luns) == len(wwns): + # Handles single wwwn + lun or multiple, potentially + # different wwns or luns + targets = list(zip(wwns, luns)) + elif len(luns) == 1 and len(wwns) > 1: + # For the case of multiple wwns, but a single lun (old path) + targets = [] + for wwn in wwns: + targets.append((wwn, luns[0])) + else: + # Something is wrong, this shouldn't happen + msg = _("Unable to find potential volume paths for FC device " + "with lun: %(lun)s and wwn: %(wwn)s") % { + "lun": target_lun, "wwn": target_wwn} + LOG.error(msg) + raise exception.VolumePathsNotFound(msg) + + connection_properties['targets'] = targets + + wwpn_lun_map = dict() + for wwpn, lun in targets: + wwpn_lun_map[wwpn] = lun + + # If there is an initiator_target_map we can update it too + if 'initiator_target_map' in connection_properties: + itmap = connection_properties['initiator_target_map'] + new_itmap = dict() + for init_wwpn in itmap: + target_wwpns = itmap[init_wwpn] + init_targets = [] + for target_wwpn in target_wwpns: + if target_wwpn in wwpn_lun_map: + init_targets.append((target_wwpn, + wwpn_lun_map[target_wwpn])) + new_itmap[init_wwpn] = init_targets + connection_properties['initiator_target_lun_map'] = new_itmap + + return connection_properties + + def _get_possible_volume_paths(self, connection_properties, hbas): + targets = connection_properties['targets'] + possible_devs = self._get_possible_devices(hbas, targets) + host_paths = self._get_host_devices(possible_devs) return host_paths def get_volume_paths(self, connection_properties): @@ -100,6 +159,9 @@ class FibreChannelConnector(base.BaseLinuxConnector): Try and update the local kernel's size information for an FC volume. """ + connection_properties = self._add_targets_to_connection_properties( + connection_properties) + volume_paths = self.get_volume_paths(connection_properties) if volume_paths: return self._linuxscsi.extend_volume(volume_paths) @@ -126,6 +188,9 @@ class FibreChannelConnector(base.BaseLinuxConnector): LOG.debug("execute = %s", self._execute) device_info = {'type': 'block'} + connection_properties = self._add_targets_to_connection_properties( + connection_properties) + hbas = self._linuxfc.get_fc_hbas_info() host_devices = self._get_possible_volume_paths( connection_properties, hbas) @@ -194,9 +259,14 @@ class FibreChannelConnector(base.BaseLinuxConnector): LOG.debug("connect_volume returning %s", device_info) return device_info - def _get_host_devices(self, possible_devs, lun): + def _get_host_devices(self, possible_devs): + """Compute the device paths on the system with an id, wwn, and lun + + :param possible_devs: list of (pci_id, wwn, lun) tuples + :return: list of device paths on the system based on the possible_devs + """ host_devices = [] - for pci_num, target_wwn in possible_devs: + for pci_num, target_wwn, lun in possible_devs: host_device = "/dev/disk/by-path/pci-%s-fc-%s-lun-%s" % ( pci_num, target_wwn, @@ -204,14 +274,13 @@ class FibreChannelConnector(base.BaseLinuxConnector): host_devices.append(host_device) return host_devices - def _get_possible_devices(self, hbas, wwnports): + def _get_possible_devices(self, hbas, targets): """Compute the possible fibre channel device options. :param hbas: available hba devices. - :param wwnports: possible wwn addresses. Can either be string - or list of strings. + :param targets: tuple of possible wwn addresses and lun combinations. - :returns: list of (pci_id, wwn) tuples + :returns: list of (pci_id, wwn, lun) tuples Given one or more wwn (mac addresses for fibre channel) ports do the matrix math to figure out a set of pci device, wwn @@ -219,23 +288,13 @@ class FibreChannelConnector(base.BaseLinuxConnector): provides a search space for the device connection. """ - # the wwn (think mac addresses for fiber channel devices) can - # either be a single value or a list. Normalize it to a list - # for further operations. - wwns = [] - if isinstance(wwnports, list): - for wwn in wwnports: - wwns.append(str(wwn)) - elif isinstance(wwnports, six.string_types): - wwns.append(str(wwnports)) - raw_devices = [] for hba in hbas: pci_num = self._get_pci_num(hba) if pci_num is not None: - for wwn in wwns: + for wwn, lun in targets: target_wwn = "0x%s" % wwn.lower() - raw_devices.append((pci_num, target_wwn)) + raw_devices.append((pci_num, target_wwn, lun)) return raw_devices @utils.trace @@ -257,6 +316,10 @@ class FibreChannelConnector(base.BaseLinuxConnector): devices = [] wwn = None + + connection_properties = self._add_targets_to_connection_properties( + connection_properties) + volume_paths = self.get_volume_paths(connection_properties) mpath_path = None for path in volume_paths: diff --git a/os_brick/initiator/linuxfc.py b/os_brick/initiator/linuxfc.py index a9db91b28..4921e9d99 100644 --- a/os_brick/initiator/linuxfc.py +++ b/os_brick/initiator/linuxfc.py @@ -19,7 +19,6 @@ import os from oslo_concurrency import processutils as putils from oslo_log import log as logging -import six from os_brick.initiator import linuxscsi @@ -42,20 +41,18 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI): single WWNN for all ports, so caller should expect us to return either explicit channel and targets or wild cards if we cannot determine them. - :returns: List of lists with [c, t] entries, the channel and target + The connection properties will need to have "target" values defined in + it which are expected to be tuples of (wwpn, lun). + + :returns: List of lists with [c, t, l] entries, the channel and target may be '-' wildcards if unable to determine them. """ # We want the target's WWPNs, so we use the initiator_target_map if # present for this hba or default to target_wwns if not present. - wwpns = conn_props['target_wwn'] + targets = conn_props['targets'] if 'initiator_target_map' in conn_props: - wwpns = conn_props['initiator_target_map'].get(hba['port_name'], - wwpns) - - # If it's not a string then it's an iterable (most likely a list), - # so we need to create a BRE for the grep query. - if not isinstance(wwpns, six.string_types): - wwpns = '\|'.join(wwpns) + targets = conn_props['initiator_target_lun_map'].get( + hba['port_name'], targets) # Leave only the number from the host_device field (ie: host6) host_device = hba['host_device'] @@ -63,33 +60,36 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI): host_device = host_device[4:] path = '/sys/class/fc_transport/target%s:' % host_device - # Since we'll run the command in a shell ensure BRE are being used - cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpns, - 'path': path} - try: - # We need to run command in shell to expand the * glob - out, _err = self._execute(cmd, shell=True) - return [line.split('/')[4].split(':')[1:] - for line in out.split('\n') if line.startswith(path)] - except Exception as exc: - LOG.debug('Could not get HBA channel and SCSI target ID, path: ' - '%(path)s*, reason: %(reason)s', {'path': path, - 'reason': exc}) - return [['-', '-']] + ctls = [] + for wwpn, lun in targets: + cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpn, + 'path': path} + try: + # We need to run command in shell to expand the * glob + out, _err = self._execute(cmd, shell=True) + ctls += [line.split('/')[4].split(':')[1:] + [lun] + for line in out.split('\n') if line.startswith(path)] + except Exception as exc: + LOG.debug('Could not get HBA channel and SCSI target ID, path:' + ' %(path)s*, reason: %(reason)s', {'path': path, + 'reason': exc}) + # If we didn't find any paths just give back wildcards for + # the channel and target ids. + ctls.append(['-', '-', lun]) + return ctls def rescan_hosts(self, hbas, connection_properties): LOG.debug('Rescaning HBAs %(hbas)s with connection properties ' '%(conn_props)s', {'hbas': hbas, 'conn_props': connection_properties}) - target_lun = connection_properties['target_lun'] - get_cts = self._get_hba_channel_scsi_target + get_ctsl = self._get_hba_channel_scsi_target - # Use initiator_target_map provided by backend as HBA exclussion map - ports = connection_properties.get('initiator_target_map') + # Use initiator_target_map provided by backend as HBA exclusion map + ports = connection_properties.get('initiator_target_lun_map') if ports: hbas = [hba for hba in hbas if hba['port_name'] in ports] LOG.debug('Using initiator target map to exclude HBAs') - process = [(hba, get_cts(hba, connection_properties)) + process = [(hba, get_ctsl(hba, connection_properties)) for hba in hbas] # With no target map we'll check if target implements single WWNN for @@ -100,20 +100,20 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI): no_info = [] for hba in hbas: - cts = get_cts(hba, connection_properties) + ctls = get_ctsl(hba, connection_properties) found_info = True - for hba_channel, target_id in cts: + for hba_channel, target_id, target_lun in ctls: if hba_channel == '-' or target_id == '-': found_info = False target_list = with_info if found_info else no_info - target_list.append((hba, cts)) + target_list.append((hba, ctls)) process = with_info or no_info msg = "implements" if with_info else "doesn't implement" LOG.debug('FC target %s single WWNN for all ports.', msg) - for hba, cts in process: - for hba_channel, target_id in cts: + for hba, ctls in process: + for hba_channel, target_id, target_lun in ctls: LOG.debug('Scanning host %(host)s (wwnn: %(wwnn)s, c: ' '%(channel)s, t: %(target)s, l: %(lun)s)', {'host': hba['host_device'], diff --git a/os_brick/tests/initiator/connectors/test_fibre_channel.py b/os_brick/tests/initiator/connectors/test_fibre_channel.py index c8c0465e2..47f6b0557 100644 --- a/os_brick/tests/initiator/connectors/test_fibre_channel.py +++ b/os_brick/tests/initiator/connectors/test_fibre_channel.py @@ -11,6 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import ddt import mock import os @@ -67,13 +68,13 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase): 'device_path': hbas[0]['ClassDevicePath']}] return info - def fibrechan_connection(self, volume, location, wwn): + def fibrechan_connection(self, volume, location, wwn, lun=1): return {'driver_volume_type': 'fibrechan', 'data': { 'volume_id': volume['id'], 'target_portal': location, 'target_wwn': wwn, - 'target_lun': 1, + 'target_lun': lun, }} @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas') @@ -125,8 +126,10 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase): location = '10.0.2.15:3260' wwn = '1234567890123456' connection_info = self.fibrechan_connection(vol, location, wwn) - volume_paths = self.connector.get_volume_paths( - connection_info['data']) + conn_data = self.connector._add_targets_to_connection_properties( + connection_info['data'] + ) + volume_paths = self.connector.get_volume_paths(conn_data) expected = ['/dev/disk/by-path/pci-0000:05:00.2' '-fc-0x1234567890123456-lun-1'] @@ -169,10 +172,15 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase): name = 'volume-00000001' vol = {'id': 1, 'name': name} # Should work for string, unicode, and list - wwns = ['1234567890123456', six.text_type('1234567890123456'), - ['1234567890123456', '1234567890123457']] - for wwn in wwns: - connection_info = self.fibrechan_connection(vol, location, wwn) + wwns_luns = [ + ('1234567890123456', 1), + (six.text_type('1234567890123456'), 1), + (['1234567890123456', '1234567890123457'], 1), + (['1234567890123456', '1234567890123457'], 1), + ] + for wwn, lun in wwns_luns: + connection_info = self.fibrechan_connection(vol, location, + wwn, lun) dev_info = self.connector.connect_volume(connection_info['data']) exp_wwn = wwn[0] if isinstance(wwn, list) else wwn dev_str = ('/dev/disk/by-path/pci-0000:05:00.2-fc-0x%s-lun-1' % @@ -188,13 +196,13 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase): # Should not work for anything other than string, unicode, and list connection_info = self.fibrechan_connection(vol, location, 123) - self.assertRaises(exception.NoFibreChannelHostsFound, + self.assertRaises(exception.VolumePathsNotFound, self.connector.connect_volume, connection_info['data']) get_fc_hbas_mock.side_effect = [[]] get_fc_hbas_info_mock.side_effect = [[]] - self.assertRaises(exception.NoFibreChannelHostsFound, + self.assertRaises(exception.VolumePathsNotFound, self.connector.connect_volume, connection_info['data']) @@ -470,3 +478,160 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase): was_multipath) remove_mock.assert_called_once_with('/dev/sda', flush=flush_mock.return_value) + + @ddt.data( + { + "target_info": { + "target_lun": 1, + "target_wwn": '1234567890123456', + }, + "expected_targets": [ + ('1234567890123456', 1) + ] + }, + { + "target_info": { + "target_lun": 1, + "target_wwn": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 1), + ] + }, + { + "target_info": { + "target_luns": [1, 1], + "target_wwn": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 1), + ] + }, + { + "target_info": { + "target_luns": [1, 2], + "target_wwn": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 2), + ] + }, + { + "target_info": { + "target_luns": [1, 1], + "target_wwns": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 1), + ] + }, + { + "target_info": { + "target_lun": 7, + "target_luns": [1, 1], + "target_wwn": 'foo', + "target_wwns": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 1), + ] + }, + # Add the zone map in now + { + "target_info": { + "target_lun": 1, + "target_wwn": '1234567890123456', + }, + "expected_targets": [ + ('1234567890123456', 1) + ], + "itmap": { + '0004567890123456': ['1234567890123456'] + }, + "expected_map": { + '0004567890123456': [('1234567890123456', 1)] + } + }, + { + "target_info": { + "target_lun": 1, + "target_wwn": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 1), + ], + "itmap": { + '0004567890123456': ['1234567890123456', + '1234567890123457'] + }, + "expected_map": { + '0004567890123456': [('1234567890123456', 1), + ('1234567890123457', 1)] + } + }, + { + "target_info": { + "target_luns": [1, 2], + "target_wwn": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 2), + ], + "itmap": { + '0004567890123456': ['1234567890123456'], + '1004567890123456': ['1234567890123457'], + }, + "expected_map": { + '0004567890123456': [('1234567890123456', 1)], + '1004567890123456': [('1234567890123457', 2)], + } + }, + { + "target_info": { + "target_luns": [1, 2], + "target_wwn": ['1234567890123456', '1234567890123457'], + }, + "expected_targets": [ + ('1234567890123456', 1), + ('1234567890123457', 2), + ], + "itmap": { + '0004567890123456': ['1234567890123456', + '1234567890123457'] + }, + "expected_map": { + '0004567890123456': [('1234567890123456', 1), + ('1234567890123457', 2)] + } + }, + + ) + @ddt.unpack + def test__add_targets_to_connection_properties(self, target_info, + expected_targets, + itmap=None, + expected_map=None): + volume = {'id': 'fake_uuid'} + wwn = '1234567890123456' + conn = self.fibrechan_connection(volume, "10.0.2.15:3260", wwn) + conn['data'].update(target_info) + + if itmap: + conn['data']['initiator_target_map'] = itmap + + connection_info = self.connector._add_targets_to_connection_properties( + conn['data']) + self.assertIn('targets', connection_info) + self.assertEqual(expected_targets, connection_info['targets']) + + if itmap: + self.assertIn('initiator_target_lun_map', connection_info) + self.assertEqual(expected_map, + connection_info['initiator_target_lun_map']) diff --git a/os_brick/tests/initiator/test_linuxfc.py b/os_brick/tests/initiator/test_linuxfc.py index dbe6414cc..3107594c0 100644 --- a/os_brick/tests/initiator/test_linuxfc.py +++ b/os_brick/tests/initiator/test_linuxfc.py @@ -50,9 +50,17 @@ class LinuxFCTestCase(base.TestCase): connection_properties = { 'initiator_target_map': {'50014380186af83c': ['514f0c50023f6c00'], '50014380186af83e': ['514f0c50023f6c01']}, + 'initiator_target_lun_map': { + '50014380186af83c': [('514f0c50023f6c00', 1)], + '50014380186af83e': [('514f0c50023f6c01', 1)] + }, 'target_discovered': False, 'target_lun': 1, - 'target_wwn': ['514f0c50023f6c00', '514f0c50023f6c01'] + 'target_wwn': ['514f0c50023f6c00', '514f0c50023f6c01'], + 'targets': [ + ('514f0c50023f6c00', 1), + ('514f0c50023f6c01', 1), + ] } hbas = [ @@ -69,6 +77,7 @@ class LinuxFCTestCase(base.TestCase): ] if not zone_manager: del connection_properties['initiator_target_map'] + del connection_properties['initiator_target_lun_map'] return hbas, connection_properties def test__get_hba_channel_scsi_target_single_wwpn(self): @@ -76,6 +85,7 @@ class LinuxFCTestCase(base.TestCase): '') hbas, con_props = self.__get_rescan_info() con_props['target_wwn'] = con_props['target_wwn'][0] + con_props['targets'] = con_props['targets'][0:1] with mock.patch.object(self.lfc, '_execute', return_value=execute_results) as execute_mock: res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props) @@ -83,22 +93,56 @@ class LinuxFCTestCase(base.TestCase): 'grep -Gil "514f0c50023f6c00" ' '/sys/class/fc_transport/target6:*/port_name', shell=True) - expected = [['0', '1']] + expected = [['0', '1', 1]] self.assertListEqual(expected, res) def test__get_hba_channel_scsi_target_multiple_wwpn(self): - execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n' - '/sys/class/fc_transport/target6:0:2/port_name\n', - '') + execute_results = [ + ['/sys/class/fc_transport/target6:0:1/port_name\n', ''], + ['/sys/class/fc_transport/target6:0:2/port_name\n', ''], + ] hbas, con_props = self.__get_rescan_info() with mock.patch.object(self.lfc, '_execute', - return_value=execute_results) as execute_mock: + side_effect=execute_results) as execute_mock: res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props) - execute_mock.assert_called_once_with( - 'grep -Gil "514f0c50023f6c00\|514f0c50023f6c01" ' - '/sys/class/fc_transport/target6:*/port_name', - shell=True) - expected = [['0', '1'], ['0', '2']] + expected_cmds = [ + mock.call('grep -Gil "514f0c50023f6c00" ' + '/sys/class/fc_transport/target6:*/port_name', + shell=True), + mock.call('grep -Gil "514f0c50023f6c01" ' + '/sys/class/fc_transport/target6:*/port_name', + shell=True), + ] + execute_mock.assert_has_calls(expected_cmds) + + expected = [['0', '1', 1], ['0', '2', 1]] + self.assertListEqual(expected, res) + + def test__get_hba_channel_scsi_target_multiple_wwpn_and_luns(self): + execute_results = [ + ['/sys/class/fc_transport/target6:0:1/port_name\n', ''], + ['/sys/class/fc_transport/target6:0:2/port_name\n', ''], + ] + hbas, con_props = self.__get_rescan_info() + con_props['target_lun'] = [1, 7] + con_props['targets'] = [ + ('514f0c50023f6c00', 1), + ('514f0c50023f6c01', 7), + ] + with mock.patch.object(self.lfc, '_execute', + side_effect=execute_results) as execute_mock: + res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props) + expected_cmds = [ + mock.call('grep -Gil "514f0c50023f6c00" ' + '/sys/class/fc_transport/target6:*/port_name', + shell=True), + mock.call('grep -Gil "514f0c50023f6c01" ' + '/sys/class/fc_transport/target6:*/port_name', + shell=True), + ] + execute_mock.assert_has_calls(expected_cmds) + + expected = [['0', '1', 1], ['0', '2', 7]] self.assertListEqual(expected, res) def test__get_hba_channel_scsi_target_zone_manager(self): @@ -112,7 +156,7 @@ class LinuxFCTestCase(base.TestCase): 'grep -Gil "514f0c50023f6c00" ' '/sys/class/fc_transport/target6:*/port_name', shell=True) - expected = [['0', '1']] + expected = [['0', '1', 1]] self.assertListEqual(expected, res) def test__get_hba_channel_scsi_target_not_found(self): @@ -135,11 +179,11 @@ class LinuxFCTestCase(base.TestCase): 'grep -Gil "514f0c50023f6c00" ' '/sys/class/fc_transport/target6:*/port_name', shell=True) - self.assertEqual([['-', '-']], res) + self.assertEqual(res, [['-', '-', 1]]) def test_rescan_hosts_initiator_map(self): """Test FC rescan with initiator map and not every HBA connected.""" - get_chan_results = [[['2', '3'], ['4', '5']], [['6', '7']]] + get_chan_results = [[['2', '3', 1], ['4', '5', 1]], [['6', '7', 1]]] hbas, con_props = self.__get_rescan_info(zone_manager=True) @@ -177,12 +221,14 @@ class LinuxFCTestCase(base.TestCase): def test_rescan_hosts_single_wwnn(self): """Test FC rescan with no initiator map and single WWNN for ports.""" - get_chan_results = [[['2', '3'], ['4', '5']], - [['6', '7']], [['-', '-']]] + get_chan_results = [ + [['2', '3', 1], ['4', '5', 1]], + [['6', '7', 1]], + [['-', '-', 1]], + ] + + hbas, con_props = self.__get_rescan_info(zone_manager=False) - hbas, con_props = self.__get_rescan_info(zone_manager=True) - # Remove the initiator map - con_props.pop('initiator_target_map') # This HBA is the one that is not included in the single WWNN. hbas.append({'device_path': ('/sys/devices/pci0000:00/0000:00:02.0/' '0000:04:00.2/host8/fc_host/host8'), @@ -216,10 +262,11 @@ class LinuxFCTestCase(base.TestCase): def test_rescan_hosts_wildcard(self): """Test when we don't have initiator map or target is single WWNN.""" - get_chan_results = [[['-', '-']], [['-', '-']]] + get_chan_results = [[['-', '-', 1]], [['-', '-', 1]]] hbas, con_props = self.__get_rescan_info(zone_manager=True) # Remove the initiator map con_props.pop('initiator_target_map') + con_props.pop('initiator_target_lun_map') with mock.patch.object(self.lfc, '_get_hba_channel_scsi_target', side_effect=get_chan_results), \ mock.patch.object(self.lfc, '_execute',