Merge "LVM nvmet: Add support for multiple ip addresses"
This commit is contained in:
commit
9cd834798c
@ -62,7 +62,7 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
"ngn.%s-%s" % (
|
||||
self.nvmet_subsystem_name,
|
||||
self.fake_volume_id),
|
||||
self.target_ip,
|
||||
[self.target_ip],
|
||||
self.target_port,
|
||||
self.nvme_transport_type,
|
||||
self.nvmet_ns_id
|
||||
@ -92,7 +92,7 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
mock_create_nvme_target.assert_called_once_with(
|
||||
self.fake_volume_id,
|
||||
self.configuration.target_prefix,
|
||||
self.target_ip,
|
||||
[self.target_ip],
|
||||
self.target_port,
|
||||
self.nvme_transport_type,
|
||||
self.nvmet_port_id,
|
||||
@ -117,7 +117,31 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
mock_uuid.assert_called_once_with(self.testvol)
|
||||
mock_get_conn_props.assert_called_once_with(
|
||||
f'ngn.{self.nvmet_subsystem_name}-{self.fake_volume_id}',
|
||||
self.target_ip,
|
||||
[self.target_ip],
|
||||
str(self.target_port),
|
||||
self.nvme_transport_type,
|
||||
str(self.nvmet_ns_id),
|
||||
mock_uuid.return_value)
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOF, '_get_nvme_uuid')
|
||||
@mock.patch.object(nvmeof.NVMeOF, '_get_connection_properties')
|
||||
def test__get_connection_properties_multiple_addresses(
|
||||
self, mock_get_conn_props, mock_uuid):
|
||||
"""Test connection properties from a volume with multiple ips."""
|
||||
self.testvol['provider_location'] = self.target.get_nvmeof_location(
|
||||
f"ngn.{self.nvmet_subsystem_name}-{self.fake_volume_id}",
|
||||
[self.target_ip, '127.0.0.1'],
|
||||
self.target_port,
|
||||
self.nvme_transport_type,
|
||||
self.nvmet_ns_id
|
||||
)
|
||||
|
||||
res = self.target._get_connection_properties_from_vol(self.testvol)
|
||||
self.assertEqual(mock_get_conn_props.return_value, res)
|
||||
mock_uuid.assert_called_once_with(self.testvol)
|
||||
mock_get_conn_props.assert_called_once_with(
|
||||
f'ngn.{self.nvmet_subsystem_name}-{self.fake_volume_id}',
|
||||
[self.target_ip, '127.0.0.1'],
|
||||
str(self.target_port),
|
||||
self.nvme_transport_type,
|
||||
str(self.nvmet_ns_id),
|
||||
@ -134,7 +158,7 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
'ns_id': str(self.nvmet_ns_id)
|
||||
}
|
||||
res = self.target._get_connection_properties(nqn,
|
||||
self.target_ip,
|
||||
[self.target_ip],
|
||||
str(self.target_port),
|
||||
self.nvme_transport_type,
|
||||
str(self.nvmet_ns_id),
|
||||
@ -158,7 +182,7 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
expected_transport)],
|
||||
}
|
||||
res = self.target._get_connection_properties(nqn,
|
||||
self.target_ip,
|
||||
[self.target_ip],
|
||||
str(self.target_port),
|
||||
transport,
|
||||
str(self.nvmet_ns_id),
|
||||
@ -182,6 +206,22 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
root_helper=utils.get_root_helper(),
|
||||
configuration=self.configuration)
|
||||
|
||||
def test_invalid_secondary_ips_old_conn_info_combination(self):
|
||||
"""Secondary IPS are only supported with new connection information."""
|
||||
self.configuration.target_secondary_ip_addresses = ['127.0.0.1']
|
||||
self.configuration.nvmeof_conn_info_version = 1
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
FakeNVMeOFDriver,
|
||||
root_helper=utils.get_root_helper(),
|
||||
configuration=self.configuration)
|
||||
|
||||
def test_valid_secondary_ips_old_conn_info_combination(self):
|
||||
"""Secondary IPS are supported with new connection information."""
|
||||
self.configuration.target_secondary_ip_addresses = ['127.0.0.1']
|
||||
self.configuration.nvmeof_conn_info_version = 2
|
||||
FakeNVMeOFDriver(root_helper=utils.get_root_helper(),
|
||||
configuration=self.configuration)
|
||||
|
||||
def test_are_same_connector(self):
|
||||
res = self.target.are_same_connector({'nqn': 'nvme'}, {'nqn': 'nvme'})
|
||||
self.assertTrue(res)
|
||||
@ -192,3 +232,20 @@ class TestNVMeOFDriver(tf.TargetDriverFixture):
|
||||
def test_are_same_connector_different(self, a_conn_props, b_conn_props):
|
||||
res = self.target.are_same_connector(a_conn_props, b_conn_props)
|
||||
self.assertFalse(bool(res))
|
||||
|
||||
def test_get_nvmeof_location(self):
|
||||
"""Serialize connection information into location."""
|
||||
result = self.target.get_nvmeof_location(
|
||||
'ngn.subsys_name-vol_id', ['127.0.0.1'], 4420, 'tcp', 10)
|
||||
|
||||
expected = '127.0.0.1:4420 tcp ngn.subsys_name-vol_id 10'
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_nvmeof_location_multiple_ips(self):
|
||||
"""Serialize connection information with multiple ips into location."""
|
||||
result = self.target.get_nvmeof_location(
|
||||
'ngn.subsys_name-vol_id', ['127.0.0.1', '192.168.1.1'], 4420,
|
||||
'tcp', 10)
|
||||
|
||||
expected = '127.0.0.1,192.168.1.1:4420 tcp ngn.subsys_name-vol_id 10'
|
||||
self.assertEqual(expected, result)
|
||||
|
@ -76,7 +76,7 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
mock_uuid.assert_called_once_with(vol)
|
||||
mock_get_conn_props.assert_called_once_with(
|
||||
mock.sentinel.nqn,
|
||||
self.target.target_ip,
|
||||
self.target.target_ips,
|
||||
self.target.target_port,
|
||||
self.target.nvme_transport_type,
|
||||
mock.sentinel.nsid,
|
||||
@ -119,7 +119,7 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
mock_map.assert_called_once_with(mock.sentinel.vol,
|
||||
mock.sentinel.volume_path)
|
||||
mock_location.assert_called_once_with(mock.sentinel.nqn,
|
||||
self.target.target_ip,
|
||||
self.target.target_ips,
|
||||
self.target.target_port,
|
||||
self.target.nvme_transport_type,
|
||||
mock.sentinel.nsid)
|
||||
@ -160,7 +160,7 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
mock.sentinel.volume_path,
|
||||
mock_uuid.return_value)
|
||||
mock_port.assert_called_once_with(mock_nqn.return_value,
|
||||
self.target.target_ip,
|
||||
self.target.target_ips,
|
||||
self.target.target_port,
|
||||
self.target.nvme_transport_type,
|
||||
self.target.nvmet_port_id)
|
||||
@ -197,7 +197,7 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
mock_port.assert_not_called()
|
||||
else:
|
||||
mock_port.assert_called_once_with(mock.sentinel.nqn,
|
||||
self.target.target_ip,
|
||||
self.target.target_ips,
|
||||
self.target.target_port,
|
||||
self.target.nvme_transport_type,
|
||||
self.target.nvmet_port_id)
|
||||
@ -345,13 +345,14 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
def test__ensure_port_exports_already_does(self, mock_port):
|
||||
"""Skips port creation and subsystem export since they both exist."""
|
||||
nqn = 'nqn.nvme-subsystem-1-uuid'
|
||||
port_id = 1
|
||||
mock_port.return_value.subsystems = [nqn]
|
||||
self.target._ensure_port_exports(nqn,
|
||||
mock.sentinel.addr,
|
||||
[mock.sentinel.addr],
|
||||
mock.sentinel.port,
|
||||
mock.sentinel.transport,
|
||||
mock.sentinel.port_id)
|
||||
mock_port.assert_called_once_with(mock.sentinel.port_id)
|
||||
port_id)
|
||||
mock_port.assert_called_once_with(port_id)
|
||||
mock_port.setup.assert_not_called()
|
||||
mock_port.return_value.add_subsystem.assert_not_called()
|
||||
|
||||
@ -359,13 +360,14 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
def test__ensure_port_exports_port_exists_not_exported(self, mock_port):
|
||||
"""Skips port creation if exists but exports subsystem."""
|
||||
nqn = 'nqn.nvme-subsystem-1-vol-2-uuid'
|
||||
port_id = 1
|
||||
mock_port.return_value.subsystems = ['nqn.nvme-subsystem-1-vol-1-uuid']
|
||||
self.target._ensure_port_exports(nqn,
|
||||
mock.sentinel.addr,
|
||||
[mock.sentinel.addr],
|
||||
mock.sentinel.port,
|
||||
mock.sentinel.transport,
|
||||
mock.sentinel.port_id)
|
||||
mock_port.assert_called_once_with(mock.sentinel.port_id)
|
||||
port_id)
|
||||
mock_port.assert_called_once_with(port_id)
|
||||
mock_port.setup.assert_not_called()
|
||||
mock_port.return_value.add_subsystem.assert_called_once_with(nqn)
|
||||
|
||||
@ -373,23 +375,35 @@ class TestNVMETDriver(tf.TargetDriverFixture):
|
||||
def test__ensure_port_exports_port(self, mock_port):
|
||||
"""Creates the port and export the subsystem when they don't exist."""
|
||||
nqn = 'nqn.nvme-subsystem-1-vol-2-uuid'
|
||||
port_id = 1
|
||||
mock_port.side_effect = priv_nvmet.NotFound
|
||||
self.target._ensure_port_exports(nqn,
|
||||
mock.sentinel.addr,
|
||||
[mock.sentinel.addr,
|
||||
mock.sentinel.addr2],
|
||||
mock.sentinel.port,
|
||||
mock.sentinel.transport,
|
||||
mock.sentinel.port_id)
|
||||
mock_port.assert_called_once_with(mock.sentinel.port_id)
|
||||
new_port = {'addr': {'adrfam': 'ipv4',
|
||||
'traddr': mock.sentinel.addr,
|
||||
'treq': 'not specified',
|
||||
'trsvcid': mock.sentinel.port,
|
||||
'trtype': mock.sentinel.transport},
|
||||
'portid': mock.sentinel.port_id,
|
||||
'referrals': [],
|
||||
'subsystems': [nqn]}
|
||||
mock_port.setup.assert_called_once_with(self.target._nvmet_root,
|
||||
new_port)
|
||||
port_id)
|
||||
new_port1 = {'addr': {'adrfam': 'ipv4',
|
||||
'traddr': mock.sentinel.addr,
|
||||
'treq': 'not specified',
|
||||
'trsvcid': mock.sentinel.port,
|
||||
'trtype': mock.sentinel.transport},
|
||||
'portid': port_id,
|
||||
'referrals': [],
|
||||
'subsystems': [nqn]}
|
||||
new_port2 = new_port1.copy()
|
||||
new_port2['portid'] = port_id + 1
|
||||
new_port2['addr'] = new_port1['addr'].copy()
|
||||
new_port2['addr']['traddr'] = mock.sentinel.addr2
|
||||
|
||||
self.assertEqual(2, mock_port.call_count)
|
||||
self.assertEqual(2, mock_port.setup.call_count)
|
||||
mock_port.assert_has_calls([
|
||||
mock.call(port_id),
|
||||
mock.call.setup(self.target._nvmet_root, new_port1),
|
||||
mock.call(port_id + 1),
|
||||
mock.call.setup(self.target._nvmet_root, new_port2)
|
||||
])
|
||||
mock_port.return_value.assert_not_called()
|
||||
|
||||
@mock.patch.object(nvmet.NVMET, '_locked_unmap_volume')
|
||||
|
@ -349,6 +349,7 @@ class SpdkNvmfDriverTestCase(test.TestCase):
|
||||
super(SpdkNvmfDriverTestCase, self).setUp()
|
||||
self.configuration = mock.Mock(conf.Configuration)
|
||||
self.configuration.target_ip_address = '192.168.0.1'
|
||||
self.configuration.target_secondary_ip_addresses = []
|
||||
self.configuration.target_port = '4420'
|
||||
self.configuration.target_prefix = ""
|
||||
self.configuration.nvmet_port_id = "1"
|
||||
|
@ -65,6 +65,23 @@ class LVMVolumeDriverTestCase(test_driver.BaseDriverTestCase):
|
||||
lvm.LVMVolumeDriver,
|
||||
configuration=self.configuration)
|
||||
|
||||
def test___init___secondary_ips_not_supported(self):
|
||||
"""Fail to use secondary ips if target driver doesn't support it."""
|
||||
original_import = importutils.import_object
|
||||
|
||||
def wrap_target_as_no_secondary_ips_support(*args, **kwargs):
|
||||
res = original_import(*args, **kwargs)
|
||||
self.mock_object(res, 'SECONDARY_IP_SUPPORT', False)
|
||||
return res
|
||||
|
||||
self.patch('oslo_utils.importutils.import_object',
|
||||
side_effect=wrap_target_as_no_secondary_ips_support)
|
||||
|
||||
self.configuration.target_secondary_ip_addresses = True
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
lvm.LVMVolumeDriver,
|
||||
configuration=self.configuration)
|
||||
|
||||
def test___init___share_target_supported(self):
|
||||
"""OK to use shared targets if target driver supports it."""
|
||||
original_import = importutils.import_object
|
||||
|
@ -502,6 +502,7 @@ class SpdkDriverTestCase(test.TestCase):
|
||||
self.configuration = mock.Mock(conf.Configuration)
|
||||
self.configuration.target_helper = ""
|
||||
self.configuration.target_ip_address = "192.168.0.1"
|
||||
self.configuration.target_secondary_ip_addresses = []
|
||||
self.configuration.target_port = 4420
|
||||
self.configuration.target_prefix = "nqn.2014-08.io.spdk"
|
||||
self.configuration.nvmeof_conn_info_version = 1
|
||||
@ -796,7 +797,7 @@ class SpdkDriverTestCase(test.TestCase):
|
||||
self.configuration.nvmet_subsystem_name,
|
||||
self.driver.target_driver._get_first_free_node()
|
||||
),
|
||||
self.configuration.target_ip_address,
|
||||
[self.configuration.target_ip_address],
|
||||
self.configuration.target_port, "rdma",
|
||||
self.configuration.nvmet_ns_id
|
||||
),
|
||||
|
@ -82,7 +82,7 @@ class TestWindowsISCSIDriver(test.TestCase):
|
||||
self._driver.configuration = mock.Mock()
|
||||
self._driver.configuration.target_port = iscsi_port
|
||||
self._driver.configuration.target_ip_address = requested_ips[0]
|
||||
self._driver.configuration.iscsi_secondary_ip_addresses = (
|
||||
self._driver.configuration.target_secondary_ip_addresses = (
|
||||
requested_ips[1:])
|
||||
|
||||
self._driver._tgt_utils.get_portal_locations.return_value = (
|
||||
|
@ -57,7 +57,8 @@ volume_opts = [
|
||||
default='$my_ip',
|
||||
help='The IP address that the iSCSI/NVMEoF daemon is '
|
||||
'listening on'),
|
||||
cfg.ListOpt('iscsi_secondary_ip_addresses',
|
||||
cfg.ListOpt('target_secondary_ip_addresses',
|
||||
deprecated_name='iscsi_secondary_ip_addresses',
|
||||
default=[],
|
||||
help='The list of secondary IP addresses of the '
|
||||
'iSCSI/NVMEoF daemon'),
|
||||
@ -276,7 +277,9 @@ nvmeof_opts = [
|
||||
nvmet_opts = [
|
||||
cfg.PortOpt('nvmet_port_id',
|
||||
default=1,
|
||||
help='The port that the NVMe target is listening on.'),
|
||||
help='The id of the NVMe target port definition when not '
|
||||
'sharing targets. The starting port id value when '
|
||||
'sharing, incremented for each secondary ip address.'),
|
||||
cfg.IntOpt('nvmet_ns_id',
|
||||
default=10,
|
||||
help='Namespace id for the subsystem for the LVM volume when '
|
||||
|
@ -118,6 +118,12 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
||||
and not self.target_driver.SHARED_TARGET_SUPPORT):
|
||||
raise exception.InvalidConfigurationValue(
|
||||
f"{target_driver} doesn't support shared targets")
|
||||
|
||||
if (self.configuration.target_secondary_ip_addresses
|
||||
and not self.target_driver.SECONDARY_IP_SUPPORT):
|
||||
raise exception.InvalidConfigurationValue(
|
||||
f"{target_driver} doesn't support secondary addresses")
|
||||
|
||||
self._sparse_copy_volume = False
|
||||
|
||||
@classmethod
|
||||
@ -129,7 +135,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
||||
'target_ip_address', 'target_helper', 'target_protocol',
|
||||
'volume_clear', 'volume_clear_size', 'reserved_percentage',
|
||||
'max_over_subscription_ratio', 'volume_dd_blocksize',
|
||||
'target_prefix', 'volumes_dir', 'iscsi_secondary_ip_addresses',
|
||||
'target_prefix', 'volumes_dir', 'target_secondary_ip_addresses',
|
||||
'target_port',
|
||||
'iscsi_write_cache', 'iscsi_target_flags', # TGT
|
||||
'iet_conf', 'iscsi_iotype', # IET
|
||||
|
@ -993,7 +993,7 @@ class SynoCommon(object):
|
||||
def get_provider_location(self, iqn, trg_id):
|
||||
portals = ['%(ip)s:%(port)d' % {'ip': self.get_ip(),
|
||||
'port': self.target_port}]
|
||||
sec_ips = self.config.safe_get('iscsi_secondary_ip_addresses')
|
||||
sec_ips = self.config.safe_get('target_secondary_ip_addresses')
|
||||
for ip in sec_ips:
|
||||
portals.append('%(ip)s:%(port)d' %
|
||||
{'ip': ip,
|
||||
@ -1288,7 +1288,7 @@ class SynoCommon(object):
|
||||
'access_mode': 'rw',
|
||||
'discard': False
|
||||
}
|
||||
ips = self.config.safe_get('iscsi_secondary_ip_addresses')
|
||||
ips = self.config.safe_get('target_secondary_ip_addresses')
|
||||
if ips:
|
||||
target_portals = [iscsi_properties['target_portal']]
|
||||
for ip in ips:
|
||||
|
@ -49,7 +49,7 @@ class SynoISCSIDriver(driver.ISCSIDriver):
|
||||
additional_opts = cls._get_oslo_driver_opts(
|
||||
'target_ip_address', 'target_protocol', 'target_port',
|
||||
'driver_use_ssl', 'use_chap_auth', 'chap_username',
|
||||
'chap_password', 'iscsi_secondary_ip_addresses', 'target_prefix',
|
||||
'chap_password', 'target_secondary_ip_addresses', 'target_prefix',
|
||||
'reserved_percentage', 'max_over_subscription_ratio')
|
||||
return common.cinder_opts + additional_opts
|
||||
|
||||
|
@ -93,7 +93,7 @@ class WindowsISCSIDriver(driver.ISCSIDriver):
|
||||
|
||||
iscsi_port = self.configuration.target_port
|
||||
iscsi_ips = ([self.configuration.target_ip_address] +
|
||||
self.configuration.iscsi_secondary_ip_addresses)
|
||||
self.configuration.target_secondary_ip_addresses)
|
||||
requested_portals = {':'.join([iscsi_ip, str(iscsi_port)])
|
||||
for iscsi_ip in iscsi_ips}
|
||||
|
||||
|
@ -33,6 +33,7 @@ class Target(object, metaclass=abc.ABCMeta):
|
||||
"""
|
||||
storage_protocol = None
|
||||
SHARED_TARGET_SUPPORT = False
|
||||
SECONDARY_IP_SUPPORT = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# TODO(stephenfin): Drop this in favour of using 'db' directly
|
||||
|
@ -167,8 +167,8 @@ class ISCSITarget(driver.Target):
|
||||
|
||||
def _get_portals_config(self):
|
||||
# Prepare portals configuration
|
||||
portals_ips = ([self.configuration.target_ip_address]
|
||||
+ self.configuration.iscsi_secondary_ip_addresses or [])
|
||||
portals_ips = ([self.configuration.target_ip_address] +
|
||||
self.configuration.target_secondary_ip_addresses or [])
|
||||
|
||||
return {'portals_ips': portals_ips,
|
||||
'portals_port': self.configuration.target_port}
|
||||
@ -201,7 +201,7 @@ class ISCSITarget(driver.Target):
|
||||
data = {}
|
||||
data['location'] = self._iscsi_location(
|
||||
self.configuration.target_ip_address, tid, iscsi_name, lun,
|
||||
self.configuration.iscsi_secondary_ip_addresses)
|
||||
self.configuration.target_secondary_ip_addresses)
|
||||
LOG.debug('Set provider_location to: %s', data['location'])
|
||||
data['auth'] = self._iscsi_authentication(
|
||||
'CHAP', *chap_auth)
|
||||
|
@ -40,7 +40,8 @@ class NVMeOF(driver.Target):
|
||||
"""Reads NVMeOF configurations."""
|
||||
|
||||
super(NVMeOF, self).__init__(*args, **kwargs)
|
||||
self.target_ip = self.configuration.target_ip_address
|
||||
self.target_ips = ([self.configuration.target_ip_address] +
|
||||
self.configuration.target_secondary_ip_addresses)
|
||||
self.target_port = self.configuration.target_port
|
||||
self.nvmet_port_id = self.configuration.nvmet_port_id
|
||||
self.nvmet_ns_id = self.configuration.nvmet_ns_id
|
||||
@ -57,6 +58,13 @@ class NVMeOF(driver.Target):
|
||||
protocol=target_protocol
|
||||
)
|
||||
|
||||
# Secondary ip addresses only work with new connection info
|
||||
if (self.configuration.target_secondary_ip_addresses
|
||||
and self.configuration.nvmeof_conn_info_version == 1):
|
||||
raise exception.InvalidConfigurationValue(
|
||||
'Secondary addresses need to use NVMe-oF connection properties'
|
||||
' format version 2 or greater (nvmeof_conn_info_version).')
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Returns the connection info.
|
||||
|
||||
@ -95,20 +103,22 @@ class NVMeOF(driver.Target):
|
||||
method.
|
||||
|
||||
:return: dictionary with the connection properties using one of the 2
|
||||
existing formats depending on the nvmeof_new_conn_info
|
||||
existing formats depending on the nvmeof_conn_info_version
|
||||
configuration option.
|
||||
"""
|
||||
location = volume['provider_location']
|
||||
target_connection, nvme_transport_type, nqn, nvmet_ns_id = (
|
||||
location.split(' '))
|
||||
target_portal, target_port = target_connection.split(':')
|
||||
target_portals, target_port = target_connection.split(':')
|
||||
target_portals = target_portals.split(',')
|
||||
|
||||
uuid = self._get_nvme_uuid(volume)
|
||||
return self._get_connection_properties(nqn, target_portal, target_port,
|
||||
return self._get_connection_properties(nqn,
|
||||
target_portals, target_port,
|
||||
nvme_transport_type,
|
||||
nvmet_ns_id, uuid)
|
||||
|
||||
def _get_connection_properties(self, nqn, portal, port, transport, ns_id,
|
||||
def _get_connection_properties(self, nqn, portals, port, transport, ns_id,
|
||||
uuid):
|
||||
"""Get connection properties dictionary.
|
||||
|
||||
@ -150,13 +160,13 @@ class NVMeOF(driver.Target):
|
||||
return {
|
||||
'target_nqn': nqn,
|
||||
'vol_uuid': uuid,
|
||||
'portals': [(portal, port, transport)],
|
||||
'portals': [(portal, port, transport) for portal in portals],
|
||||
'ns_id': ns_id,
|
||||
}
|
||||
|
||||
# NVMe-oF Connection Information Version 1
|
||||
result = {
|
||||
'target_portal': portal,
|
||||
'target_portal': portals[0],
|
||||
'target_port': port,
|
||||
'nqn': nqn,
|
||||
'transport_type': transport,
|
||||
@ -173,12 +183,12 @@ class NVMeOF(driver.Target):
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_nvmeof_location(self, nqn, target_ip, target_port,
|
||||
def get_nvmeof_location(self, nqn, target_ips, target_port,
|
||||
nvme_transport_type, nvmet_ns_id):
|
||||
"""Serializes driver data into single line string."""
|
||||
|
||||
return "%(ip)s:%(port)s %(transport)s %(nqn)s %(ns_id)s" % (
|
||||
{'ip': target_ip,
|
||||
{'ip': ','.join(target_ips),
|
||||
'port': target_port,
|
||||
'transport': nvme_transport_type,
|
||||
'nqn': nqn,
|
||||
@ -198,7 +208,7 @@ class NVMeOF(driver.Target):
|
||||
return self.create_nvmeof_target(
|
||||
volume['id'],
|
||||
self.configuration.target_prefix,
|
||||
self.target_ip,
|
||||
self.target_ips,
|
||||
self.target_port,
|
||||
self.nvme_transport_type,
|
||||
self.nvmet_port_id,
|
||||
@ -222,7 +232,7 @@ class NVMeOF(driver.Target):
|
||||
def create_nvmeof_target(self,
|
||||
volume_id,
|
||||
subsystem_name,
|
||||
target_ip,
|
||||
target_ips,
|
||||
target_port,
|
||||
transport_type,
|
||||
nvmet_port_id,
|
||||
|
@ -61,7 +61,7 @@ class NVMET(nvmeof.NVMeOF):
|
||||
return {
|
||||
'driver_volume_type': self.protocol,
|
||||
'data': self._get_connection_properties(nqn,
|
||||
self.target_ip,
|
||||
self.target_ips,
|
||||
self.target_port,
|
||||
self.nvme_transport_type,
|
||||
ns_id, uuid),
|
||||
@ -75,7 +75,7 @@ class NVMET(nvmeof.NVMeOF):
|
||||
else:
|
||||
nqn, ns_id = self._map_volume(volume, volume_path)
|
||||
location = self.get_nvmeof_location(nqn,
|
||||
self.target_ip,
|
||||
self.target_ips,
|
||||
self.target_port,
|
||||
self.nvme_transport_type,
|
||||
ns_id)
|
||||
@ -92,7 +92,7 @@ class NVMET(nvmeof.NVMeOF):
|
||||
|
||||
ns_id = self._ensure_subsystem_exists(nqn, volume_path, uuid)
|
||||
|
||||
self._ensure_port_exports(nqn, self.target_ip, self.target_port,
|
||||
self._ensure_port_exports(nqn, self.target_ips, self.target_port,
|
||||
self.nvme_transport_type,
|
||||
self.nvmet_port_id)
|
||||
except Exception:
|
||||
@ -195,36 +195,39 @@ class NVMET(nvmeof.NVMeOF):
|
||||
def _get_nvme_uuid(self, volume):
|
||||
return volume.name_id
|
||||
|
||||
def _ensure_port_exports(self, nqn, addr, port, transport_type, port_id):
|
||||
# Assume if port exists, it has the right configuration
|
||||
try:
|
||||
port = nvmet.Port(port_id)
|
||||
LOG.debug('Skip creating port %s as it already exists.', port_id)
|
||||
except nvmet.NotFound:
|
||||
LOG.debug('Creating port %s.', port_id)
|
||||
def _ensure_port_exports(self, nqn, addrs, port, transport_type, port_id):
|
||||
for addr in addrs:
|
||||
# Assume if port exists, it has the right configuration
|
||||
try:
|
||||
nvme_port = nvmet.Port(port_id)
|
||||
LOG.debug('Skip creating port %s as it already exists.',
|
||||
port_id)
|
||||
except nvmet.NotFound:
|
||||
LOG.debug('Creating port %s.', port_id)
|
||||
|
||||
# Port section
|
||||
port_section = {
|
||||
"addr": {
|
||||
"adrfam": "ipv4",
|
||||
"traddr": addr,
|
||||
"treq": "not specified",
|
||||
"trsvcid": port,
|
||||
"trtype": transport_type,
|
||||
},
|
||||
"portid": port_id,
|
||||
"referrals": [],
|
||||
"subsystems": [nqn]
|
||||
}
|
||||
nvmet.Port.setup(self._nvmet_root, port_section) # privsep
|
||||
LOG.debug('Added port: %s', port_id)
|
||||
# Port section
|
||||
port_section = {
|
||||
"addr": {
|
||||
"adrfam": "ipv4",
|
||||
"traddr": addr,
|
||||
"treq": "not specified",
|
||||
"trsvcid": port,
|
||||
"trtype": transport_type,
|
||||
},
|
||||
"portid": port_id,
|
||||
"referrals": [],
|
||||
"subsystems": [nqn]
|
||||
}
|
||||
nvmet.Port.setup(self._nvmet_root, port_section) # privsep
|
||||
LOG.debug('Added port: %s', port_id)
|
||||
|
||||
else:
|
||||
if nqn in port.subsystems:
|
||||
LOG.debug('%s already exported on port %s', nqn, port_id)
|
||||
else:
|
||||
port.add_subsystem(nqn) # privsep
|
||||
LOG.debug('Exported %s on port %s', nqn, port_id)
|
||||
if nqn in nvme_port.subsystems:
|
||||
LOG.debug('%s already exported on port %s', nqn, port_id)
|
||||
else:
|
||||
nvme_port.add_subsystem(nqn) # privsep
|
||||
LOG.debug('Exported %s on port %s', nqn, port_id)
|
||||
port_id += 1
|
||||
|
||||
# ####### Connection termination methods ########
|
||||
|
||||
|
@ -51,6 +51,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SpdkNvmf(nvmeof.NVMeOF):
|
||||
SECONDARY_IP_SUPPORT = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SpdkNvmf, self).__init__(*args, **kwargs)
|
||||
@ -131,7 +132,7 @@ class SpdkNvmf(nvmeof.NVMeOF):
|
||||
def create_nvmeof_target(self,
|
||||
volume_id,
|
||||
subsystem_name,
|
||||
target_ip,
|
||||
target_ips,
|
||||
target_port,
|
||||
transport_type,
|
||||
nvmet_port_id,
|
||||
@ -158,7 +159,7 @@ class SpdkNvmf(nvmeof.NVMeOF):
|
||||
|
||||
listen_address = {
|
||||
'trtype': transport_type,
|
||||
'traddr': target_ip,
|
||||
'traddr': target_ips[0],
|
||||
'trsvcid': str(target_port),
|
||||
}
|
||||
params = {
|
||||
@ -179,7 +180,7 @@ class SpdkNvmf(nvmeof.NVMeOF):
|
||||
|
||||
location = self.get_nvmeof_location(
|
||||
nqn,
|
||||
target_ip,
|
||||
target_ips,
|
||||
target_port,
|
||||
transport_type,
|
||||
ns_id)
|
||||
|
16
releasenotes/notes/nvmet-multipath-d35f55286f263e72.yaml
Normal file
16
releasenotes/notes/nvmet-multipath-d35f55286f263e72.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
nvmet target driver: Added support to serve volumes on multiple addresses
|
||||
using the ``target_secondary_ip_addresses`` configuration option. This
|
||||
allows os-brick to iterate through them in search of one connection that
|
||||
works, and once os-brick supports NVMe-oF multipathing it will be
|
||||
automatically supported.
|
||||
|
||||
This requires that ``nvmeof_conn_info_version`` configuration option is set
|
||||
to ``2`` as well.
|
||||
deprecations:
|
||||
- |
|
||||
Configuration option ``iscsi_secondary_ip_addresses`` is deprecated in
|
||||
favor of ``target_secondary_ip_addresses`` to follow the same naming
|
||||
convention of ``target_ip_address``.
|
Loading…
Reference in New Issue
Block a user