Fix FC: Only scan connected HBAs

Current OS-Brick FC code always scans all present HBAs, which could
unintentionally add unwanted devices, for example in the following
environment:

    +-------+     +------+   +-----------------+
    | host5 +-----+      +---+ Port.A   VNX    |
    |       |     | FCSW +---+ Port.B          |
    | host6 +-----+      |   +-----------------+
    |       |     +------+
    |       |     +------+
    | host7 +-----+      |   +-----------------+
    |       |     | FCSW +---+ Port.C  XtremIO |
    | host8 +-----+      +---+ Port.D          |
    +-------+     +------+   +-----------------+

This patch limits what HBAs get scanned:

- If we have an initiator map, we only scan on the HBAs that are there
- If we are in the single WWNN for all ports case we only scan HBAs that
  are connected
- If we can't do any better we scan all HBAs with wildcards

Closes-Bug: #1765000
Change-Id: I3ba8f9683211d550727a97fc455175f2b0482886
(cherry picked from commit db40d98044)
This commit is contained in:
Gorka Eguileor 2018-04-18 12:34:37 +02:00
parent 773fad170f
commit 8fab1a01cb
2 changed files with 82 additions and 7 deletions

View File

@ -77,14 +77,38 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
return None
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
# Use initiator_target_map provided by backend as HBA exclussion map
ports = connection_properties.get('initiator_target_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))
for hba in hbas]
# With no target map we'll check if target implements single WWNN for
# all ports, if it does we only use HBAs connected (info was found),
# otherwise we are forced to blindly scan all HBAs.
else:
with_info = []
no_info = []
for hba in hbas:
# Try to get HBA channel and SCSI target to use as filters
cts = self._get_hba_channel_scsi_target(hba, connection_properties)
# If we couldn't get the channel and target use wildcards
if not cts:
cts = [('-', '-')]
cts = get_cts(hba, connection_properties)
target_list = with_info if cts else no_info
cts = cts or [('-', '-')]
target_list.append((hba, cts))
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:
LOG.debug('Scanning host %(host)s (wwnn: %(wwnn)s, c: '
'%(channel)s, t: %(target)s, l: %(lun)s)',

View File

@ -137,10 +137,58 @@ class LinuxFCTestCase(base.TestCase):
shell=True)
self.assertIsNone(res)
def test_rescan_hosts(self):
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']]]
hbas, con_props = self.__get_rescan_info(zone_manager=True)
# This HBA is not in the initiator map, so we should not scan it or try
# to get the channel and target
hbas.append({'device_path': ('/sys/devices/pci0000:00/0000:00:02.0/'
'0000:04:00.2/host8/fc_host/host8'),
'host_device': 'host8',
'node_name': '50014380186af83g',
'port_name': '50014380186af83h'})
with mock.patch.object(self.lfc, '_execute',
return_value=None) as execute_mock, \
mock.patch.object(self.lfc, '_get_hba_channel_scsi_target',
side_effect=get_chan_results) as mock_get_chan:
self.lfc.rescan_hosts(hbas, con_props)
expected_commands = [
mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
process_input='2 3 1',
root_helper=None, run_as_root=True),
mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
process_input='4 5 1',
root_helper=None, run_as_root=True),
mock.call('tee', '-a', '/sys/class/scsi_host/host7/scan',
process_input='6 7 1',
root_helper=None, run_as_root=True)]
execute_mock.assert_has_calls(expected_commands)
self.assertEqual(len(expected_commands), execute_mock.call_count)
expected_calls = [mock.call(hbas[0], con_props),
mock.call(hbas[1], con_props)]
mock_get_chan.assert_has_calls(expected_calls)
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']], None]
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'),
'host_device': 'host8',
'node_name': '50014380186af83g',
'port_name': '50014380186af83h'})
with mock.patch.object(self.lfc, '_execute',
return_value=None) as execute_mock, \
mock.patch.object(self.lfc, '_get_hba_channel_scsi_target',
@ -166,7 +214,10 @@ class LinuxFCTestCase(base.TestCase):
mock_get_chan.assert_has_calls(expected_calls)
def test_rescan_hosts_wildcard(self):
"""Test when we don't have initiator map or target is single WWNN."""
hbas, con_props = self.__get_rescan_info(zone_manager=True)
# Remove the initiator map
con_props.pop('initiator_target_map')
with mock.patch.object(self.lfc, '_get_hba_channel_scsi_target',
side_effect=(None, [])), \
mock.patch.object(self.lfc, '_execute',