Merge "SCSI: Support non SAM LUN addressing" into stable/2023.1
This commit is contained in:
commit
9f5796f4c2
25
os_brick/constants.py
Normal file
25
os_brick/constants.py
Normal 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)
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
|
@ -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'],
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user