Merge "SCSI: Support non SAM LUN addressing" into stable/2023.1

This commit is contained in:
Zuul 2024-02-13 18:53:40 +00:00 committed by Gerrit Code Review
commit 9f5796f4c2
12 changed files with 237 additions and 11 deletions

25
os_brick/constants.py Normal file
View File

@ -0,0 +1,25 @@
# Copyright (c) 2023, Red Hat, Inc.
# All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# Valid SCSI addressing values for 'addressing_mode' in connection info.
# More information in os_bric.initiator.linuxscsi.LinuxSCSI.lun_for_addressing
SCSI_ADDRESSING_TRANSPARENT = 'transparent'
SCSI_ADDRESSING_SAM = 'SAM'
SCSI_ADDRESSING_SAM2 = 'SAM2'
SCSI_ADDRESSING_SAM3_FLAT = 'SAM3-flat'
SCSI_ADDRESSING_MODES = (SCSI_ADDRESSING_TRANSPARENT,
SCSI_ADDRESSING_SAM,
SCSI_ADDRESSING_SAM2,
SCSI_ADDRESSING_SAM3_FLAT)

View File

@ -161,7 +161,9 @@ class FibreChannelConnector(base.BaseLinuxConnector):
self,
connection_properties: dict, hbas) -> list[str]:
targets = connection_properties['targets']
possible_devs = self._get_possible_devices(hbas, targets)
addressing_mode = connection_properties.get('addressing_mode')
possible_devs = self._get_possible_devices(hbas, targets,
addressing_mode)
host_paths = self._get_host_devices(possible_devs)
return host_paths
@ -309,7 +311,8 @@ class FibreChannelConnector(base.BaseLinuxConnector):
host_devices.append(host_device)
return host_devices
def _get_possible_devices(self, hbas: list, targets: list) -> list:
def _get_possible_devices(self, hbas: list, targets: list,
addressing_mode: Optional[str] = None) -> list:
"""Compute the possible fibre channel device options.
:param hbas: available hba devices.
@ -328,6 +331,8 @@ class FibreChannelConnector(base.BaseLinuxConnector):
platform, pci_num = self._get_pci_num(hba)
if pci_num is not None:
for wwn, lun in targets:
lun = self._linuxscsi.lun_for_addressing(lun,
addressing_mode)
target_wwn = "0x%s" % wwn.lower()
raw_devices.append((platform, pci_num, target_wwn, lun))
return raw_devices

View File

@ -90,7 +90,9 @@ class FibreChannelConnectorS390X(fibre_channel.FibreChannelConnector):
force, exc):
hbas = self._linuxfc.get_fc_hbas_info()
targets = connection_properties['targets']
possible_devs = self._get_possible_devices(hbas, targets)
addressing_mode = connection_properties.get('addressing_mode')
possible_devs = self._get_possible_devices(hbas, targets,
addressing_mode)
for platform, pci_num, target_wwn, lun in possible_devs:
target_lun = self._get_lun_string(lun)
with exc.context(force, 'Removing device %s:%s:%s failed',

View File

@ -168,6 +168,17 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
# entry: [tcp, [1], 192.168.121.250:3260,1 ...]
return [entry[2] for entry in self._get_iscsi_sessions_full()]
def _get_all_targets(
self,
connection_properties: dict) -> list[tuple[str, str, list]]:
addressing_mode = connection_properties.get('addressing_mode')
res = super()._get_all_targets(connection_properties)
return [(portal,
iqn,
self._linuxscsi.lun_for_addressing(lun, addressing_mode))
for portal, iqn, lun in res]
def _get_ips_iqns_luns(
self,
connection_properties: dict,

View File

@ -136,8 +136,12 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
# If we didn't find any target ports use wildcards if they are enabled
process = process or skipped
addressing_mode = connection_properties.get('addressing_mode')
for hba, ctls in process:
for hba_channel, target_id, target_lun in ctls:
target_lun = self.lun_for_addressing(target_lun,
addressing_mode)
LOG.debug('Scanning %(host)s (wwnn: %(wwnn)s, c: '
'%(channel)s, t: %(target)s, l: %(lun)s)',
{'host': hba['host_device'],

View File

@ -29,6 +29,7 @@ from oslo_concurrency import processutils as putils
from oslo_log import log as logging
from oslo_utils import excutils
from os_brick import constants
from os_brick import exception
from os_brick import executor
from os_brick.privileged import rootwrap as priv_rootwrap
@ -48,6 +49,70 @@ class LinuxSCSI(executor.Executor):
# As found in drivers/scsi/scsi_lib.c
WWN_TYPES = {'t10.': '1', 'eui.': '2', 'naa.': '3'}
@staticmethod
def lun_for_addressing(lun, addressing_mode=None):
"""Convert luns to values used by the system.
How a LUN is codified depends on the standard being used by the storage
array and the mode, which is unknown by the host.
Addressing modes based on the standard:
* SAM:
- 64bit address
* SAM-2:
- Peripheral device addressing method (Code 00b)
+ Single level
+ Multi level
- Flat space addressing method (Code 01b)
- Logical unit addressing mode (Code 10b)
- Extended logical unit addressing method (Code 11b)
* SAM-3: Mostly same as SAM-2 but with some differences,
like supporting addressing LUNs < 256 with flat address space.
This means that the same LUN numbers could have different addressing
values. Examples:
* LUN 1:
- SAM representation: 1
- SAM-2 peripheral: 1
- SAM-2 flat addressing: Invalid
- SAM-3 flat addressing: 16384
* LUN 256
- SAM representation: 256
- SAM-2 peripheral: Not possible to represent
- SAM-2 flat addressing: 16640
- SAM-3 flat addressing: 16640
This method makes the transformation from the numerical LUN value to
the right addressing value based on the addressing_mode.
Acceptable values are:
- SAM: 64bit address with no translation
- transparent: Same as SAM but used by drivers that want to use non
supported addressing modes by using the addressing mode
instead of the LUN without being misleading (untested).
- SAM2: Peripheral for LUN < 256 and flat for LUN >= 256. In SAM-2
flat cannot be used for 0-255
- SAM3-flat: Force flat-space addressing
The default is SAM/transparent and nothing will be done with the LUNs.
"""
mode = addressing_mode or constants.SCSI_ADDRESSING_SAM
if mode not in constants.SCSI_ADDRESSING_MODES:
raise exception.InvalidParameterValue('Invalid addressing_mode '
f'{addressing_mode}')
if (mode == constants.SCSI_ADDRESSING_SAM3_FLAT
or (mode == constants.SCSI_ADDRESSING_SAM2 and lun >= 256)):
old_lun = lun
lun += 16384
LOG.info('Transforming LUN value for addressing: %s -> %s',
old_lun, lun)
return lun
def echo_scsi_command(self, path, content) -> None:
"""Used to echo strings to scsi subsystem."""

View File

@ -942,3 +942,53 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
True, mock.ANY)
self.assertEqual(len(expected), realpath_mock.call_count)
realpath_mock.assert_has_calls(expected)
@ddt.data(None, mock.sentinel.addressing_mode)
@mock.patch.object(fibre_channel.FibreChannelConnector,
'_get_host_devices')
@mock.patch.object(fibre_channel.FibreChannelConnector,
'_get_possible_devices')
def test__get_possible_volume_paths(self, addressing_mode,
pos_devs_mock, host_devs_mock):
conn_props = {'targets': mock.sentinel.targets}
if addressing_mode:
conn_props['addressing_mode'] = addressing_mode
res = self.connector._get_possible_volume_paths(conn_props,
mock.sentinel.hbas)
pos_devs_mock.assert_called_once_with(mock.sentinel.hbas,
mock.sentinel.targets,
addressing_mode)
host_devs_mock.assert_called_once_with(pos_devs_mock.return_value)
self.assertEqual(host_devs_mock.return_value, res)
@mock.patch.object(linuxscsi.LinuxSCSI, 'lun_for_addressing')
@mock.patch.object(fibre_channel.FibreChannelConnector, '_get_pci_num')
def test__get_possible_devices(self, pci_mock, lun_mock):
pci_mock.side_effect = [
(mock.sentinel.platform1, mock.sentinel.pci_num1),
(mock.sentinel.platform2, mock.sentinel.pci_num2)]
hbas = [mock.sentinel.hba1, mock.sentinel.hba2]
lun_mock.side_effect = [mock.sentinel.lun1B, mock.sentinel.lun2B] * 2
targets = [('wwn1', mock.sentinel.lun1), ('wwn2', mock.sentinel.lun2)]
res = self.connector._get_possible_devices(
hbas, targets, mock.sentinel.addressing_mode)
self.assertEqual(2, pci_mock.call_count)
pci_mock.assert_has_calls([mock.call(mock.sentinel.hba1),
mock.call(mock.sentinel.hba2)])
self.assertEqual(4, lun_mock.call_count)
lun_mock.assert_has_calls([
mock.call(mock.sentinel.lun1, mock.sentinel.addressing_mode),
mock.call(mock.sentinel.lun2, mock.sentinel.addressing_mode)] * 2)
expected = [
(mock.sentinel.platform1, mock.sentinel.pci_num1, '0xwwn1',
mock.sentinel.lun1B),
(mock.sentinel.platform1, mock.sentinel.pci_num1, '0xwwn2',
mock.sentinel.lun2B),
(mock.sentinel.platform2, mock.sentinel.pci_num2, '0xwwn1',
mock.sentinel.lun1B),
(mock.sentinel.platform2, mock.sentinel.pci_num2, '0xwwn2',
mock.sentinel.lun2B),
]
self.assertEqual(expected, res)

View File

@ -74,7 +74,7 @@ class FibreChannelConnectorS390XTestCase(test_connector.ConnectorTestCase):
mock_deconfigure_scsi_device.assert_called_with(3, 5,
"0x0002000000000000")
mock_get_fc_hbas_info.assert_called_once_with()
mock_get_possible_devices.assert_called_once_with([], [5, 2])
mock_get_possible_devices.assert_called_once_with([], [5, 2], None)
self.assertFalse(bool(exc))
@mock.patch.object(fibre_channel_s390x.FibreChannelConnectorS390X,
@ -94,5 +94,5 @@ class FibreChannelConnectorS390XTestCase(test_connector.ConnectorTestCase):
mock_deconfigure_scsi_device.assert_called_with(3, 5,
"0x0002000000000000")
mock_get_fc_hbas_info.assert_called_once_with()
mock_get_possible_devices.assert_called_once_with([], [5, 2])
mock_get_possible_devices.assert_called_once_with([], [5, 2], None)
self.assertTrue(bool(exc))

View File

@ -1748,3 +1748,31 @@ Setting up iSCSI targets: unused
connection_properties['data'], old_node_startup_values)
iscsiadm_update_mock.assert_called_once_with(
recover_connection['data'], "node.startup", "manual")
@ddt.data(None, 'SAM2')
@mock.patch.object(linuxscsi.LinuxSCSI, 'lun_for_addressing')
@mock.patch.object(iscsi.base_iscsi.BaseISCSIConnector, '_get_all_targets')
def test__get_all_targets_no_addressing_mode(self, addressing_mode,
get_mock, luns_mock):
get_mock.return_value = [
(mock.sentinel.portal1, mock.sentinel.iqn1, mock.sentinel.lun1),
(mock.sentinel.portal2, mock.sentinel.iqn2, mock.sentinel.lun2)
]
luns_mock.side_effect = [mock.sentinel.lun1B, mock.sentinel.lun2B]
conn_props = self.CON_PROPS.copy()
if addressing_mode:
conn_props['addressing_mode'] = addressing_mode
res = self.connector._get_all_targets(conn_props)
self.assertEqual(2, luns_mock.call_count)
luns_mock.assert_has_calls(
[mock.call(mock.sentinel.lun1, addressing_mode),
mock.call(mock.sentinel.lun2, addressing_mode)])
get_mock.assert_called_once_with(conn_props)
expected = [
(mock.sentinel.portal1, mock.sentinel.iqn1, mock.sentinel.lun1B),
(mock.sentinel.portal2, mock.sentinel.iqn2, mock.sentinel.lun2B)
]
self.assertListEqual(expected, res)

View File

@ -268,15 +268,18 @@ class LinuxFCTestCase(base.TestCase):
mock.call(hbas[1], con_props)]
mock_get_chan.assert_has_calls(expected_calls)
def test_rescan_hosts_single_wwnn(self):
@mock.patch.object(linuxfc.LinuxFibreChannel, 'lun_for_addressing')
def test_rescan_hosts_single_wwnn(self, lun_addr_mock):
"""Test FC rescan with no initiator map and single WWNN for ports."""
lun_addr_mock.return_value = 16640
get_chan_results = [
[[['2', '3', 1], ['4', '5', 1]], set()],
[[['6', '7', 1]], set()],
[[['2', '3', 256], ['4', '5', 256]], set()],
[[['6', '7', 256]], set()],
[[], {1}],
]
hbas, con_props = self.__get_rescan_info(zone_manager=False)
con_props['addressing_mode'] = 'SAM2'
# 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/'
@ -293,13 +296,13 @@ class LinuxFCTestCase(base.TestCase):
self.lfc.rescan_hosts(hbas, con_props)
expected_commands = [
mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
process_input='2 3 1',
process_input='2 3 16640',
root_helper=None, run_as_root=True),
mock.call('tee', '-a', '/sys/class/scsi_host/host6/scan',
process_input='4 5 1',
process_input='4 5 16640',
root_helper=None, run_as_root=True),
mock.call('tee', '-a', '/sys/class/scsi_host/host7/scan',
process_input='6 7 1',
process_input='6 7 16640',
root_helper=None, run_as_root=True)]
execute_mock.assert_has_calls(expected_commands)
@ -308,6 +311,7 @@ class LinuxFCTestCase(base.TestCase):
expected_calls = [mock.call(hbas[0], con_props),
mock.call(hbas[1], con_props)]
mock_get_chan.assert_has_calls(expected_calls)
lun_addr_mock.assert_has_calls([mock.call(256, 'SAM2')] * 3)
def test_rescan_hosts_initiator_map_single_wwnn(self):
"""Test FC rescan with initiator map and single WWNN."""

View File

@ -1380,3 +1380,27 @@ loop0 0"""
if real_paths:
mocked.assert_has_calls([mock.call(path),
mock.call(path_used)])
@ddt.data(None, 'SAM', 'transparent')
def test_lun_for_addressing_transparent_sam(self, mode):
lun = self.linuxscsi.lun_for_addressing(1, mode)
self.assertEqual(1, lun)
lun = self.linuxscsi.lun_for_addressing(256, mode)
self.assertEqual(256, lun)
@ddt.data(1, 'SAM3', 'TRANSPARENT', 'sam', 'sam2')
def test_lun_for_addressing_bad(self, mode):
self.assertRaises(exception.InvalidParameterValue,
self.linuxscsi.lun_for_addressing, 1, mode)
@ddt.data((1, 1), (100, 100), (256, 16640), (1010, 17394))
@ddt.unpack
def test_lun_for_addressing_sam2(self, original_lun, expected_lun):
lun = self.linuxscsi.lun_for_addressing(original_lun, 'SAM2')
self.assertEqual(expected_lun, lun)
@ddt.data((0, 16384), (100, 16484), (256, 16640), (1010, 17394))
@ddt.unpack
def test_lun_for_addressing_sam3_flat(self, original_lun, expected_lun):
lun = self.linuxscsi.lun_for_addressing(original_lun, 'SAM3-flat')
self.assertEqual(expected_lun, lun)

View File

@ -0,0 +1,8 @@
---
features:
- |
iSCSI and FCP: Support for different SCSI addressing modes: SAM, SAM-2, and
SAM-3 flat addressing. Defaults to SAM/transparent, but cinder drivers can
set key ``addressing_mode`` in the connection properties to indicate other
addressing modes using one of the constants from
``os_brick.constants.SCSI_ADDRESSING_*`` as the value.