Merge "Fix: FC partial target scan"

This commit is contained in:
Zuul 2024-02-15 02:09:12 +00:00 committed by Gerrit Code Review
commit 7e0a7f8f81
3 changed files with 274 additions and 83 deletions

View File

@ -33,7 +33,74 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
# Only load the sysfs attributes we care about
HBA_ATTRIBUTES = ('port_name', 'node_name', 'port_state')
def _get_hba_channel_scsi_target_lun(self, hba, conn_props):
def _get_target_fc_transport_path(self, path, wwpn, lun):
"""Scan target in the fc_transport path
Scan for target in the following path:
* /sys/class/fc_transport/target<host>*
:returns: List with [c, t, l] if the target path exists else
empty list
"""
cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpn,
'path': path}
# We need to run command in shell to expand the * glob
out, _err = self._execute(cmd, shell=True) # nosec: B604
# The grep command will only return 1 path (if found)
# associated with the target wwpn used for the search
# in the current HBA host
out_path = out.split('\n')[0]
if out_path.startswith(path):
return out_path.split('/')[4].split(':')[1:] + [lun]
return []
def _get_target_fc_remote_ports_path(self, path, wwpn, lun):
"""Scan target in the fc_remote_ports path
Scan for target in the following path:
* /sys/class/fc_remote_ports/rport-<host>*
If the path exist, we fetch the target value from the
scsi_target_id file.
Example: /sys/class/fc_remote_ports/rport-6:0-1/scsi_target_id
:returns: List with [c, t, l] if the target path exists else
empty list
"""
cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpn,
'path': path}
# We need to run command in shell to expand the * glob
out, _err = self._execute(cmd, shell=True) # nosec: B604
# The scsi_target_id file contains the target ID.
# Example path:
# /sys/class/fc_remote_ports/rport-2:0-0/scsi_target_id
target_path = os.path.dirname(out) + '/scsi_target_id'
# There could be a case where the out variable has empty string
# and we end up with a path '/scsi_target_id' so check if it
# starts with the correct path
if target_path.startswith(path):
try:
scsi_target = '-1'
with open(target_path) as scsi_target_file:
lines = scsi_target_file.read()
scsi_target = lines.split('\n')[0]
except OSError:
# We were not able to read from the scsi_target_id
# file but we can still discover other targets so
# continue
pass
# If the target value is -1, it is not a real target so
# skip it
if scsi_target != '-1':
channel = target_path.split(':')[1].split('-')[0]
return [channel, scsi_target, lun]
return []
def _get_hba_channel_scsi_target_lun(self,
hba,
conn_props):
"""Get HBA channels, SCSI targets, LUNs to FC targets for given HBA.
Given an HBA and the connection properties we look for the HBA channels
@ -48,6 +115,13 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
based on the contents of the connection information data to know which
target ports to look for.
We scan for targets in the following two paths:
* /sys/class/fc_transport/target<host>*
* /sys/class/fc_remote_ports/rport-<host>*
We search for targets in the fc_transport path first and if not
found, we search in the fc_remote_ports path
:returns: 2-Tuple with the first entry being a list of [c, t, l]
entries where the target port was found, and the second entry of the
tuple being a set of luns for ports that were not found.
@ -66,22 +140,27 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
host_device = host_device[4:]
path = '/sys/class/fc_transport/target%s:' % host_device
rpath = '/sys/class/fc_remote_ports/rport-%s:' % host_device
ctls = []
luns_not_found = set()
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) # nosec: B604
ctls += [line.split('/')[4].split(':')[1:] + [lun]
for line in out.split('\n') if line.startswith(path)]
# Search for target in the fc_transport path first and if we
# don't find ctl, search for target in the fc_remote_ports path
ctl = (self._get_target_fc_transport_path(path, wwpn, lun) or
self._get_target_fc_remote_ports_path(rpath, wwpn, lun))
if ctl:
ctls.append(ctl)
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 add it to the not found list
luns_not_found.add(lun)
return ctls, luns_not_found
def rescan_hosts(self,

View File

@ -15,11 +15,13 @@
import os.path
from unittest import mock
import ddt
from os_brick.initiator import linuxfc
from os_brick.tests import base
@ddt.ddt
class LinuxFCTestCase(base.TestCase):
def setUp(self):
@ -70,85 +72,124 @@ class LinuxFCTestCase(base.TestCase):
del connection_properties['initiator_target_lun_map']
return hbas, connection_properties
def test__get_hba_channel_scsi_target_lun_single_wwpn(self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
@staticmethod
def _get_expected_info(wwpns=["514f0c50023f6c00", "514f0c50023f6c01"],
targets=1, remote_scan=False):
execute_results = []
expected_cmds = []
for i in range(0, targets):
expected_cmds += [
mock.call(f'grep -Gil "{wwpns[i]}" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
]
if remote_scan:
execute_results += [
# We can only perform remote ports scan if the
# fc_transport path returns empty output
('', ''),
# This is returned from the fc_remote_ports path
(f'/sys/class/fc_remote_ports/rport-6:0-{i+1}'
'/port_name\n', ''),
]
expected_cmds += [
mock.call(f'grep -Gil "{wwpns[i]}" '
'/sys/class/fc_remote_ports/rport-6:*/port_name',
shell=True),
]
else:
execute_results += [
(f'/sys/class/fc_transport/target6:0:{i+1}/port_name\n',
'')
]
return execute_results, expected_cmds
@mock.patch('builtins.open')
@ddt.data(True, False)
def test__get_hba_channel_scsi_target_lun_single_wwpn(
self, remote_scan, mock_open):
execute_results, expected_cmds = self._get_expected_info(
remote_scan=remote_scan)
if remote_scan:
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\n')
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:
side_effect=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target_lun(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
execute_mock.assert_has_calls(expected_cmds)
expected = ([['0', '1', 1]], set())
self.assertEqual(expected, res)
def test__get_hba_channel_scsi_target_lun_with_initiator_target_map(self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
@mock.patch('builtins.open')
@ddt.data(True, False)
def test__get_hba_channel_scsi_target_lun_with_initiator_target_map(
self, remote_scan, mock_open):
execute_results, expected_cmds = self._get_expected_info(
wwpns=["514f0c50023f6c01"])
if remote_scan:
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\n')
hbas, con_props = self.__get_rescan_info(zone_manager=True)
con_props['target_wwn'] = con_props['target_wwn'][0]
con_props['targets'] = con_props['targets'][0:1]
hbas[0]['port_name'] = '50014380186af83e'
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_lun(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c01" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
execute_mock.assert_has_calls(expected_cmds)
expected = ([['0', '1', 1]], set())
self.assertEqual(expected, res)
@mock.patch('builtins.open')
@ddt.data(True, False)
def test__get_hba_channel_scsi_target_lun_with_initiator_target_map_none(
self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
self, remote_scan, mock_open):
execute_results, expected_cmds = self._get_expected_info()
if remote_scan:
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\n')
hbas, con_props = self.__get_rescan_info()
con_props['target_wwn'] = con_props['target_wwn'][0]
con_props['targets'] = con_props['targets'][0:1]
con_props['initiator_target_map'] = None
hbas[0]['port_name'] = '50014380186af83e'
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_lun(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
execute_mock.assert_has_calls(expected_cmds)
expected = ([['0', '1', 1]], set())
self.assertEqual(expected, res)
def test__get_hba_channel_scsi_target_lun_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', ''],
]
@mock.patch('builtins.open')
@ddt.data(True, False)
def test__get_hba_channel_scsi_target_lun_multiple_wwpn(
self, remote_scan, mock_open):
execute_results, expected_cmds = self._get_expected_info(targets=2)
if remote_scan:
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\n')
hbas, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
side_effect=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target_lun(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', 1]], set())
self.assertEqual(expected, res)
def test__get_hba_channel_scsi_target_lun_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', ''],
]
@mock.patch('builtins.open')
@ddt.data(True, False)
def test__get_hba_channel_scsi_target_lun_multiple_wwpn_and_luns(
self, remote_scan, mock_open):
execute_results, expected_cmds = self._get_expected_info(targets=2)
if remote_scan:
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\n')
hbas, con_props = self.__get_rescan_info()
con_props['target_lun'] = [1, 7]
con_props['targets'] = [
@ -158,68 +199,51 @@ class LinuxFCTestCase(base.TestCase):
with mock.patch.object(self.lfc, '_execute',
side_effect=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target_lun(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]], set())
self.assertEqual(expected, res)
def test__get_hba_channel_scsi_target_lun_zone_manager(self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
@mock.patch('builtins.open')
@ddt.data(True, False)
def test__get_hba_channel_scsi_target_lun_zone_manager(
self, remote_scan, mock_open):
execute_results, expected_cmds = self._get_expected_info()
if remote_scan:
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\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:
side_effect=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target_lun(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
execute_mock.assert_has_calls(expected_cmds)
expected = ([['0', '1', 1]], set())
self.assertEqual(expected, res)
def test__get_hba_channel_scsi_target_lun_not_found(self):
def test__get_hba_channel_scsi_target_lun_both_paths_not_found(self):
_, expected_cmds = self._get_expected_info()
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_lun(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
execute_mock.assert_has_calls(expected_cmds)
self.assertEqual(([], set()), res)
def test__get_hba_channel_scsi_target_lun_exception(self):
_, expected_cmds = self._get_expected_info()
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_lun(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
execute_mock.assert_has_calls(expected_cmds)
self.assertEqual(([], {1}), res)
def test__get_hba_channel_scsi_target_lun_some_exception(self):
def test__get_hba_channel_scsi_target_lun_fc_transport_exception(self):
execute_effects = [
('/sys/class/fc_transport/target6:0:1/port_name\n', ''),
Exception()
]
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),
]
_, expected_cmds = self._get_expected_info()
hbas, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
@ -229,6 +253,86 @@ class LinuxFCTestCase(base.TestCase):
expected = ([['0', '1', 1]], {1})
self.assertEqual(expected, res)
@mock.patch('builtins.open')
def test__get_hba_channel_scsi_target_lun_fc_remote_ports_exception(
self, mock_open):
execute_effects = [
('', ''),
('/sys/class/fc_remote_ports/rport-6:0-1/port_name\n', ''),
('', ''),
Exception()
]
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.return_value = ('1\n')
_, expected_cmds = self._get_expected_info()
hbas, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
side_effect=execute_effects) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target_lun(hbas[0], con_props)
execute_mock.assert_has_calls(expected_cmds)
expected = ([['0', '1', 1]], {1})
self.assertEqual(expected, res)
@mock.patch('builtins.open')
def test__get_hba_channel_scsi_target_open_oserror(
self, mock_open):
execute_effects, expected_cmds = self._get_expected_info(
targets=2, remote_scan=True)
mock_open = mock_open.return_value.__enter__.return_value
mock_open.read.side_effect = ['1\n', OSError()]
hbas, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
side_effect=execute_effects) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target_lun(hbas[0], con_props)
execute_mock.assert_has_calls(expected_cmds)
expected = ([['0', '1', 1]], set())
self.assertEqual(expected, res)
def test__get_target_fc_transport_path(self):
path = '/sys/class/fc_transport/target6:'
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n',
'')
_, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
return_value=execute_results) as execute_mock:
ctl = self.lfc._get_target_fc_transport_path(
path, con_props['target_wwn'][0], 1)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
self.assertEqual(['0', '1', 1], ctl)
@mock.patch('builtins.open')
def test__get_target_fc_remote_ports_path(self, mock_open):
path = '/sys/class/fc_remote_ports/rport-6:'
execute_results = [
('/sys/class/fc_remote_ports/rport-6:0-1/port_name\n', ''),
('1\n', ''),
]
scsi_target_path = (
'/sys/class/fc_remote_ports/rport-6:0-1/scsi_target_id')
mock_open.return_value.__enter__.return_value.read.return_value = (
'1\n')
hbas, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
side_effect=execute_results) as execute_mock:
ctl = self.lfc._get_target_fc_remote_ports_path(
path, con_props['target_wwn'][0], 1)
expected_cmds = [
mock.call(
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_remote_ports/rport-6:*/port_name',
shell=True),
]
execute_mock.assert_has_calls(expected_cmds)
mock_open.assert_called_once_with(scsi_target_path)
self.assertEqual(['0', '1', 1], ctl)
def test_rescan_hosts_initiator_map(self):
"""Test FC rescan with initiator map and not every HBA connected."""
get_chan_results = [([['2', '3', 1], ['4', '5', 1]], set()),

View File

@ -0,0 +1,8 @@
---
fixes:
- |
FC connector `bug #2051237
<https://bugs.launchpad.net/os-brick/+bug/2051237>`_: Fix
issue with fibre channel connector scanning partial targets.
We search for target information in sysfs, first in fc_transport
and then in fc_report_ports.