diff --git a/os_brick/initiator/connectors/fibre_channel.py b/os_brick/initiator/connectors/fibre_channel.py index 4c9489a3c..1798ab2f8 100644 --- a/os_brick/initiator/connectors/fibre_channel.py +++ b/os_brick/initiator/connectors/fibre_channel.py @@ -160,7 +160,8 @@ class FibreChannelConnector(base.BaseLinuxConnector): "Will rescan & retry. Try number: %(tries)s."), {'tries': tries}) - self._linuxfc.rescan_hosts(hbas) + self._linuxfc.rescan_hosts(hbas, + connection_properties['target_lun']) self.tries = self.tries + 1 self.host_device = None diff --git a/os_brick/initiator/linuxfc.py b/os_brick/initiator/linuxfc.py index ca256e9f3..f3ab8aed1 100644 --- a/os_brick/initiator/linuxfc.py +++ b/os_brick/initiator/linuxfc.py @@ -20,17 +20,55 @@ import os from oslo_concurrency import processutils as putils from oslo_log import log as logging -from os_brick.i18n import _LW +from os_brick.i18n import _LE, _LW from os_brick.initiator import linuxscsi LOG = logging.getLogger(__name__) class LinuxFibreChannel(linuxscsi.LinuxSCSI): - def rescan_hosts(self, hbas): + def _get_hba_channel_scsi_target(self, hba): + """Try to get the HBA channel and SCSI target for an HBA. + + This method only works for Fibre Channel targets that implement a + single WWNN for all ports, so caller should expect us to return either + None or an empty list. + """ + # Leave only the number from the host_device field (ie: host6) + host_device = hba['host_device'] + if host_device and len(host_device) > 4: + host_device = host_device[4:] + + path = '/sys/class/fc_transport/target%s:' % host_device + cmd = 'grep %(wwnn)s %(path)s*/node_name' % {'wwnn': hba['node_name'], + 'path': path} + try: + out, _err = self._execute(cmd) + return [line.split('/')[4].split(':')[1:] + for line in out.split('\n') if line.startswith(path)] + except Exception as exc: + LOG.error(_LE('Could not get HBA channel and SCSI target ID, ' + 'reason: %s'), exc) + return None + + def rescan_hosts(self, hbas, target_lun): for hba in hbas: - self.echo_scsi_command("/sys/class/scsi_host/%s/scan" - % hba['host_device'], "- - -") + # Try to get HBA channel and SCSI target to use as filters + cts = self._get_hba_channel_scsi_target(hba) + # If we couldn't get the channel and target use wildcards + if not cts: + cts = [('-', '-')] + for hba_channel, target_id in cts: + LOG.debug('Scanning host %(host)s (wwnn: %(wwnn)s, c: ' + '%(channel)s, t: %(target)s, l: %(lun)s)', + {'host': hba['host_device'], + 'wwnn': hba['node_name'], 'channel': hba_channel, + 'target': target_id, 'lun': target_lun}) + self.echo_scsi_command( + "/sys/class/scsi_host/%s/scan" % hba['host_device'], + "%(c)s %(t)s %(l)s" % {'c': hba_channel, + 't': target_id, + 'l': target_lun}) def get_fc_hbas(self): """Get the Fibre Channel HBA information.""" diff --git a/os_brick/tests/initiator/test_linuxfc.py b/os_brick/tests/initiator/test_linuxfc.py index 5c693eb49..b6889d7c4 100644 --- a/os_brick/tests/initiator/test_linuxfc.py +++ b/os_brick/tests/initiator/test_linuxfc.py @@ -35,12 +35,91 @@ class LinuxFCTestCase(base.TestCase): return "", None def test_rescan_hosts(self): - hbas = [{'host_device': 'foo'}, - {'host_device': 'bar'}, ] - self.lfc.rescan_hosts(hbas) - expected_commands = ['tee -a /sys/class/scsi_host/foo/scan', - 'tee -a /sys/class/scsi_host/bar/scan'] - self.assertEqual(expected_commands, self.cmds) + # We check that we try to get the HBA channel and SCSI target + execute_results = ( + ('/sys/class/fc_transport/target10:2:3/node_name:' + '0x5006016090203181\n/sys/class/fc_transport/target10:4:5/' + 'node_name:0x5006016090203181', ''), + None, + None, + ('/sys/class/fc_transport/target11:6:7/node_name:' + '0x5006016090203181\n/sys/class/fc_transport/target11:8:9/' + 'node_name:0x5006016090203181', ''), + None, + None) + hbas = [{'host_device': 'host10', 'node_name': '5006016090203181'}, + {'host_device': 'host11', 'node_name': '5006016090203181'}] + with mock.patch.object(self.lfc, '_execute', + side_effect=execute_results) as execute_mock: + self.lfc.rescan_hosts(hbas, 1) + expected_commands = [ + mock.call('grep 5006016090203181 /sys/class/fc_transport/' + 'target10:*/node_name'), + mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan', + process_input='2 3 1', + root_helper=None, run_as_root=True), + mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan', + process_input='4 5 1', + root_helper=None, run_as_root=True), + mock.call('grep 5006016090203181 /sys/class/fc_transport/' + 'target11:*/node_name'), + mock.call('tee', '-a', '/sys/class/scsi_host/host11/scan', + process_input='6 7 1', + root_helper=None, run_as_root=True), + mock.call('tee', '-a', '/sys/class/scsi_host/host11/scan', + process_input='8 9 1', + root_helper=None, run_as_root=True)] + + execute_mock.assert_has_calls(expected_commands) + self.assertEqual(len(expected_commands), execute_mock.call_count) + + def test_rescan_hosts_wildcard(self): + hbas = [{'host_device': 'host10', 'node_name': '5006016090203181'}, + {'host_device': 'host11', 'node_name': '5006016090203181'}] + with mock.patch.object(self.lfc, '_get_hba_channel_scsi_target', + return_value=None), \ + mock.patch.object(self.lfc, '_execute', + return_value=None) as execute_mock: + + self.lfc.rescan_hosts(hbas, 1) + + expected_commands = [ + mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan', + process_input='- - 1', + root_helper=None, run_as_root=True), + mock.call('tee', '-a', '/sys/class/scsi_host/host11/scan', + process_input='- - 1', + root_helper=None, run_as_root=True)] + + execute_mock.assert_has_calls(expected_commands) + self.assertEqual(len(expected_commands), execute_mock.call_count) + + def test_rescan_hosts_wildcard_exception(self): + def _execute(cmd, *args, **kwargs): + if cmd.startswith('grep'): + raise Exception + + hbas = [{'host_device': 'host10', 'node_name': '5006016090203181'}, + {'host_device': 'host11', 'node_name': '5006016090203181'}] + with mock.patch.object(self.lfc, '_execute', + side_effect=_execute) as execute_mock: + + self.lfc.rescan_hosts(hbas, 1) + + expected_commands = [ + mock.call('grep 5006016090203181 /sys/class/fc_transport/' + 'target10:*/node_name'), + mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan', + process_input='- - 1', + root_helper=None, run_as_root=True), + mock.call('grep 5006016090203181 /sys/class/fc_transport/' + 'target11:*/node_name'), + mock.call('tee', '-a', '/sys/class/scsi_host/host11/scan', + process_input='- - 1', + root_helper=None, run_as_root=True)] + + execute_mock.assert_has_calls(expected_commands) + self.assertEqual(len(expected_commands), execute_mock.call_count) def test_get_fc_hbas_fail(self): def fake_exec1(a, b, c, d, run_as_root=True, root_helper='sudo'):