Merge "Fixing FC scanning"

This commit is contained in:
Zuul 2018-01-15 21:58:36 +00:00 committed by Gerrit Code Review
commit 956678b4c0
3 changed files with 141 additions and 71 deletions

View File

@ -157,8 +157,7 @@ class FibreChannelConnector(base.BaseLinuxConnector):
"Will rescan & retry. Try number: %(tries)s.", "Will rescan & retry. Try number: %(tries)s.",
{'tries': self.tries}) {'tries': self.tries})
self._linuxfc.rescan_hosts(hbas, self._linuxfc.rescan_hosts(hbas, connection_properties)
connection_properties['target_lun'])
self.tries = self.tries + 1 self.tries = self.tries + 1
self.host_device = None self.host_device = None

View File

@ -19,6 +19,7 @@ import os
from oslo_concurrency import processutils as putils from oslo_concurrency import processutils as putils
from oslo_log import log as logging from oslo_log import log as logging
import six
from os_brick.initiator import linuxscsi from os_brick.initiator import linuxscsi
@ -34,7 +35,7 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
else: else:
return False return False
def _get_hba_channel_scsi_target(self, hba): def _get_hba_channel_scsi_target(self, hba, conn_props):
"""Try to get the HBA channel and SCSI target for an HBA. """Try to get the HBA channel and SCSI target for an HBA.
This method only works for Fibre Channel targets that implement a This method only works for Fibre Channel targets that implement a
@ -43,28 +44,44 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
:returns: List or None :returns: List or None
""" """
# 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']
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)
# Leave only the number from the host_device field (ie: host6) # Leave only the number from the host_device field (ie: host6)
host_device = hba['host_device'] host_device = hba['host_device']
if host_device and len(host_device) > 4: if host_device and len(host_device) > 4:
host_device = host_device[4:] host_device = host_device[4:]
path = '/sys/class/fc_transport/target%s:' % host_device path = '/sys/class/fc_transport/target%s:' % host_device
cmd = 'grep %(wwnn)s %(path)s*/node_name' % {'wwnn': hba['node_name'], # Since we'll run the command in a shell ensure BRE are being used
'path': path} cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpns,
'path': path}
try: try:
out, _err = self._execute(cmd) # We need to run command in shell to expand the * glob
out, _err = self._execute(cmd, shell=True)
return [line.split('/')[4].split(':')[1:] return [line.split('/')[4].split(':')[1:]
for line in out.split('\n') if line.startswith(path)] for line in out.split('\n') if line.startswith(path)]
except Exception as exc: except Exception as exc:
LOG.debug('Could not get HBA channel and SCSI target ID, path: ' LOG.debug('Could not get HBA channel and SCSI target ID, path: '
'%(path)s, reason: %(reason)s', {'path': path, '%(path)s*, reason: %(reason)s', {'path': path,
'reason': exc}) 'reason': exc})
return None return None
def rescan_hosts(self, hbas, target_lun): def rescan_hosts(self, hbas, connection_properties):
target_lun = connection_properties['target_lun']
for hba in hbas: for hba in hbas:
# Try to get HBA channel and SCSI target to use as filters # Try to get HBA channel and SCSI target to use as filters
cts = self._get_hba_channel_scsi_target(hba) cts = self._get_hba_channel_scsi_target(hba, connection_properties)
# If we couldn't get the channel and target use wildcards # If we couldn't get the channel and target use wildcards
if not cts: if not cts:
cts = [('-', '-')] cts = [('-', '-')]

View File

@ -44,87 +44,141 @@ class LinuxFCTestCase(base.TestCase):
has_fc = self.lfc.has_fc_support() has_fc = self.lfc.has_fc_support()
self.assertTrue(has_fc) self.assertTrue(has_fc)
def test_rescan_hosts(self): @staticmethod
# We check that we try to get the HBA channel and SCSI target def __get_rescan_info(zone_manager=False):
execute_results = (
('/sys/class/fc_transport/target10:2:3/node_name:' connection_properties = {
'0x5006016090203181\n/sys/class/fc_transport/target10:4:5/' 'initiator_target_map': {'50014380186af83c': ['514f0c50023f6c00'],
'node_name:0x5006016090203181', ''), '50014380186af83e': ['514f0c50023f6c01']},
None, 'target_discovered': False,
None, 'target_lun': 1,
('/sys/class/fc_transport/target11:6:7/node_name:' 'target_wwn': ['514f0c50023f6c00', '514f0c50023f6c01']
'0x5006016090203181\n/sys/class/fc_transport/target11:8:9/' }
'node_name:0x5006016090203181', ''),
None, hbas = [
None) {'device_path': ('/sys/devices/pci0000:00/0000:00:02.0/'
hbas = [{'host_device': 'host10', 'node_name': '5006016090203181'}, '0000:04:00.0/host6/fc_host/host6'),
{'host_device': 'host11', 'node_name': '5006016090203181'}] 'host_device': 'host6',
'node_name': '50014380186af83d',
'port_name': '50014380186af83c'},
{'device_path': ('/sys/devices/pci0000:00/0000:00:02.0/'
'0000:04:00.1/host7/fc_host/host7'),
'host_device': 'host7',
'node_name': '50014380186af83f',
'port_name': '50014380186af83e'},
]
if not zone_manager:
del connection_properties['initiator_target_map']
return hbas, connection_properties
def test__get_hba_channel_scsi_target_single_wwpn(self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
hbas, con_props = self.__get_rescan_info()
con_props['target_wwn'] = con_props['target_wwn'][0]
with mock.patch.object(self.lfc, '_execute', with mock.patch.object(self.lfc, '_execute',
side_effect=execute_results) as execute_mock: return_value=execute_results) as execute_mock:
self.lfc.rescan_hosts(hbas, 1) res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
expected = [['0', '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',
'')
hbas, con_props = self.__get_rescan_info()
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)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00\|514f0c50023f6c01" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
expected = [['0', '1'], ['0', '2']]
self.assertListEqual(expected, res)
def test__get_hba_channel_scsi_target_zone_manager(self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
hbas, con_props = self.__get_rescan_info(zone_manager=True)
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)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
expected = [['0', '1']]
self.assertListEqual(expected, res)
def test__get_hba_channel_scsi_target_not_found(self):
hbas, con_props = self.__get_rescan_info(zone_manager=True)
with mock.patch.object(self.lfc, '_execute',
return_value=('', '')) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
self.assertListEqual([], res)
def test__get_hba_channel_scsi_target_exception(self):
hbas, con_props = self.__get_rescan_info(zone_manager=True)
with mock.patch.object(self.lfc, '_execute',
side_effect=Exception) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
self.assertIsNone(res)
def test_rescan_hosts(self):
get_chan_results = [[['2', '3'], ['4', '5']], [['6', '7']]]
hbas, con_props = self.__get_rescan_info(zone_manager=True)
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 = [ expected_commands = [
mock.call('grep 5006016090203181 /sys/class/fc_transport/' mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
'target10:*/node_name'),
mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan',
process_input='2 3 1', process_input='2 3 1',
root_helper=None, run_as_root=True), root_helper=None, run_as_root=True),
mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan', mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
process_input='4 5 1', process_input='4 5 1',
root_helper=None, run_as_root=True), root_helper=None, run_as_root=True),
mock.call('grep 5006016090203181 /sys/class/fc_transport/' mock.call('tee', '-a', '/sys/class/scsi_host/host7/scan',
'target11:*/node_name'),
mock.call('tee', '-a', '/sys/class/scsi_host/host11/scan',
process_input='6 7 1', 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)] root_helper=None, run_as_root=True)]
execute_mock.assert_has_calls(expected_commands) execute_mock.assert_has_calls(expected_commands)
self.assertEqual(len(expected_commands), execute_mock.call_count) 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_wildcard(self): def test_rescan_hosts_wildcard(self):
hbas = [{'host_device': 'host10', 'node_name': '5006016090203181'}, hbas, con_props = self.__get_rescan_info(zone_manager=True)
{'host_device': 'host11', 'node_name': '5006016090203181'}]
with mock.patch.object(self.lfc, '_get_hba_channel_scsi_target', with mock.patch.object(self.lfc, '_get_hba_channel_scsi_target',
return_value=None), \ side_effect=(None, [])), \
mock.patch.object(self.lfc, '_execute', mock.patch.object(self.lfc, '_execute',
return_value=None) as execute_mock: side_effect=None) as execute_mock:
self.lfc.rescan_hosts(hbas, 1) self.lfc.rescan_hosts(hbas, con_props)
expected_commands = [ expected_commands = [
mock.call('tee', '-a', '/sys/class/scsi_host/host10/scan', mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
process_input='- - 1', process_input='- - 1',
root_helper=None, run_as_root=True), root_helper=None, run_as_root=True),
mock.call('tee', '-a', '/sys/class/scsi_host/host11/scan', mock.call('tee', '-a', '/sys/class/scsi_host/host7/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', process_input='- - 1',
root_helper=None, run_as_root=True)] root_helper=None, run_as_root=True)]