Add NVMeoF Multipathing support
Phase 1 (Native) implementation of NVMeoF Multipathing. Change-Id: I3af33c5e43cfb104e436fb785b08fb28b50a031a
This commit is contained in:
parent
56bf0272b5
commit
03a5af79fe
|
@ -43,6 +43,8 @@ LOG = logging.getLogger(__name__)
|
|||
class NVMeOFConnector(base.BaseLinuxConnector):
|
||||
"""Connector class to attach/detach NVMe-oF volumes."""
|
||||
|
||||
native_multipath_supported = None
|
||||
|
||||
def __init__(self, root_helper, driver=None, use_multipath=False,
|
||||
device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
||||
*args, **kwargs):
|
||||
|
@ -52,6 +54,10 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
device_scan_attempts=device_scan_attempts,
|
||||
*args, **kwargs)
|
||||
self.use_multipath = use_multipath
|
||||
self._set_native_multipath_supported()
|
||||
if self.use_multipath and not \
|
||||
NVMeOFConnector.native_multipath_supported:
|
||||
LOG.warning('native multipath is not enabled')
|
||||
|
||||
@staticmethod
|
||||
def get_search_path():
|
||||
|
@ -99,7 +105,6 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
execute = kwargs.get('execute') or priv_rootwrap.execute
|
||||
nvmf = NVMeOFConnector(root_helper=root_helper, execute=execute)
|
||||
ret = {}
|
||||
|
||||
nqn = None
|
||||
uuid = nvmf._get_host_uuid()
|
||||
suuid = nvmf._get_system_uuid()
|
||||
|
@ -111,6 +116,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
ret['system uuid'] = suuid # compatibility
|
||||
if nqn:
|
||||
ret['nqn'] = nqn
|
||||
ret['nvme_native_multipath'] = cls._set_native_multipath_supported()
|
||||
return ret
|
||||
|
||||
def _get_host_uuid(self):
|
||||
|
@ -148,6 +154,22 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
out = ""
|
||||
return out.strip()
|
||||
|
||||
@classmethod
|
||||
def _set_native_multipath_supported(cls):
|
||||
if cls.native_multipath_supported is None:
|
||||
cls.native_multipath_supported = \
|
||||
cls._is_native_multipath_supported()
|
||||
return cls.native_multipath_supported
|
||||
|
||||
@staticmethod
|
||||
def _is_native_multipath_supported():
|
||||
try:
|
||||
with open('/sys/module/nvme_core/parameters/multipath', 'rt') as f:
|
||||
return f.read().strip() == 'Y'
|
||||
except Exception:
|
||||
LOG.warning("Could not find nvme_core/parameters/multipath")
|
||||
return False
|
||||
|
||||
def _get_nvme_devices(self):
|
||||
nvme_devices = []
|
||||
# match nvme devices like /dev/nvme10n10
|
||||
|
@ -321,7 +343,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
:returns: dict
|
||||
"""
|
||||
if connection_properties.get('vol_uuid'): # compatibility
|
||||
return self._connect_volume_replicated(connection_properties)
|
||||
return self._connect_volume_by_uuid(connection_properties)
|
||||
|
||||
current_nvme_devices = self._get_nvme_devices()
|
||||
device_info = {'type': 'block'}
|
||||
|
@ -425,7 +447,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
raise exception.VolumePathsNotFound()
|
||||
|
||||
@utils.trace
|
||||
def _connect_volume_replicated(self, connection_properties):
|
||||
def _connect_volume_by_uuid(self, connection_properties):
|
||||
"""connect to volume on host
|
||||
|
||||
connection_properties for NVMe-oF must include:
|
||||
|
@ -433,7 +455,6 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
target_nqn - NVMe-oF Qualified Name
|
||||
vol_uuid - UUID for volume/replica
|
||||
"""
|
||||
|
||||
volume_replicas = connection_properties.get('volume_replicas')
|
||||
replica_count = connection_properties.get('replica_count')
|
||||
volume_alias = connection_properties.get('alias')
|
||||
|
@ -499,6 +520,8 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
NVMeOFConnector.run_mdadm(
|
||||
self, ['mdadm', '--grow', '--size', 'max', device_path])
|
||||
else:
|
||||
target_nqn = None
|
||||
vol_uuid = None
|
||||
if not volume_replicas:
|
||||
target_nqn = connection_properties['target_nqn']
|
||||
vol_uuid = connection_properties['vol_uuid']
|
||||
|
@ -511,28 +534,43 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
return self._linuxscsi.get_device_size(device_path)
|
||||
|
||||
def _connect_target_volume(self, target_nqn, vol_uuid, portals):
|
||||
try:
|
||||
NVMeOFConnector._get_nvme_controller(self, target_nqn)
|
||||
NVMeOFConnector.rescan(self, target_nqn, vol_uuid)
|
||||
except exception.VolumeDeviceNotFound:
|
||||
if not NVMeOFConnector.connect_to_portals(
|
||||
self, target_nqn, portals):
|
||||
LOG.error("No successful connections to: %s", target_nqn)
|
||||
raise exception.VolumeDeviceNotFound(device=target_nqn)
|
||||
dev_path = NVMeOFConnector.get_nvme_device_path(
|
||||
self, target_nqn, vol_uuid)
|
||||
nvme_ctrls = NVMeOFConnector.rescan(self, target_nqn)
|
||||
any_new_connect = NVMeOFConnector.connect_to_portals(self, target_nqn,
|
||||
portals,
|
||||
nvme_ctrls)
|
||||
if not any_new_connect and len(nvme_ctrls) == 0:
|
||||
# no new connections and any pre-exists controllers
|
||||
LOG.error("No successful connections to: %s", target_nqn)
|
||||
raise exception.VolumeDeviceNotFound(device=target_nqn)
|
||||
if any_new_connect:
|
||||
# new connections - refresh controllers map
|
||||
nvme_ctrls = \
|
||||
NVMeOFConnector.get_live_nvme_controllers_map(self, target_nqn)
|
||||
nvme_ctrls_values = list(nvme_ctrls.values())
|
||||
dev_path = NVMeOFConnector.get_nvme_device_path(self, target_nqn,
|
||||
vol_uuid,
|
||||
nvme_ctrls_values)
|
||||
if not dev_path:
|
||||
LOG.error("Target %s volume %s not found", target_nqn, vol_uuid)
|
||||
raise exception.VolumeDeviceNotFound(device=vol_uuid)
|
||||
return dev_path
|
||||
|
||||
@staticmethod
|
||||
def connect_to_portals(executor, target_nqn, target_portals):
|
||||
"""connect to any of NVMe-oF target portals"""
|
||||
any_connect = False
|
||||
def connect_to_portals(executor, target_nqn, target_portals, nvme_ctrls):
|
||||
# connect to any of NVMe-oF target portals -
|
||||
# check if the controller exist before trying to connect
|
||||
# in multipath connect all given target portals
|
||||
any_new_connect = False
|
||||
no_multipath = not executor.use_multipath or not \
|
||||
NVMeOFConnector.native_multipath_supported
|
||||
for portal in target_portals:
|
||||
portal_address = portal[0]
|
||||
portal_port = portal[1]
|
||||
if NVMeOFConnector.is_portal_connected(portal_address, portal_port,
|
||||
nvme_ctrls):
|
||||
if no_multipath:
|
||||
break
|
||||
continue
|
||||
if portal[2] == 'RoCEv2':
|
||||
portal_transport = 'rdma'
|
||||
else:
|
||||
|
@ -542,14 +580,30 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
portal_transport, '-n', target_nqn, '-Q', '128', '-l', '-1')
|
||||
try:
|
||||
NVMeOFConnector.run_nvme_cli(executor, nvme_command)
|
||||
any_connect = True
|
||||
break
|
||||
any_new_connect = True
|
||||
if no_multipath:
|
||||
break
|
||||
except Exception:
|
||||
LOG.exception("Could not connect to portal %s", portal)
|
||||
return any_connect
|
||||
return any_new_connect
|
||||
|
||||
@staticmethod
|
||||
def _get_nvme_controller(executor, target_nqn):
|
||||
def is_portal_connected(portal_address, portal_port, nvme_ctrls):
|
||||
address = f"traddr={portal_address},trsvcid={portal_port}"
|
||||
return address in nvme_ctrls
|
||||
|
||||
@staticmethod
|
||||
def get_nvme_controllers(executor, target_nqn):
|
||||
nvme_controllers = \
|
||||
NVMeOFConnector.get_live_nvme_controllers_map(executor, target_nqn)
|
||||
if len(nvme_controllers) > 0:
|
||||
return nvme_controllers.values()
|
||||
raise exception.VolumeDeviceNotFound(device=target_nqn)
|
||||
|
||||
@staticmethod
|
||||
def get_live_nvme_controllers_map(executor, target_nqn):
|
||||
"""returns map of all live controllers and their addresses """
|
||||
nvme_controllers = dict()
|
||||
ctrls = glob.glob('/sys/class/nvme-fabrics/ctl/nvme*')
|
||||
for ctrl in ctrls:
|
||||
try:
|
||||
|
@ -561,31 +615,45 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
state, _err = executor._execute(
|
||||
'cat', ctrl + '/state', run_as_root=True,
|
||||
root_helper=executor._root_helper)
|
||||
if 'live' not in state:
|
||||
if 'live' in state:
|
||||
address_file = ctrl + '/address'
|
||||
try:
|
||||
with open(address_file, 'rt') as f:
|
||||
address = f.read().strip()
|
||||
except Exception:
|
||||
LOG.warning("Failed to read file %s",
|
||||
address_file)
|
||||
continue
|
||||
ctrl_name = os.path.basename(ctrl)
|
||||
LOG.debug("[!] address: %s|%s", address, ctrl_name)
|
||||
nvme_controllers[address] = ctrl_name
|
||||
else:
|
||||
LOG.debug("nvmeof ctrl device not live: %s", ctrl)
|
||||
raise exception.VolumeDeviceNotFound(device=ctrl)
|
||||
return ctrl[ctrl.rfind('/') + 1:]
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.exception(e)
|
||||
|
||||
raise exception.VolumeDeviceNotFound(device=target_nqn)
|
||||
return nvme_controllers
|
||||
|
||||
@staticmethod
|
||||
@utils.retry(exception.VolumeDeviceNotFound, retries=5)
|
||||
def get_nvme_device_path(executor, target_nqn, vol_uuid):
|
||||
nvme_ctrl = NVMeOFConnector._get_nvme_controller(executor, target_nqn)
|
||||
uuid_paths = glob.glob('/sys/class/block/' + nvme_ctrl + 'n*/uuid')
|
||||
for uuid_path in uuid_paths:
|
||||
try:
|
||||
uuid_lines, _err = executor._execute(
|
||||
'cat', uuid_path, run_as_root=True,
|
||||
root_helper=executor._root_helper)
|
||||
if uuid_lines.split('\n')[0] == vol_uuid:
|
||||
ignore = len('/uuid')
|
||||
return '/dev/' + uuid_path[
|
||||
uuid_path.rfind('/', 0, -ignore) + 1: -ignore]
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.exception(e)
|
||||
def get_nvme_device_path(executor, target_nqn, vol_uuid, nvme_ctrls=None):
|
||||
if not nvme_ctrls:
|
||||
nvme_ctrls = NVMeOFConnector.get_nvme_controllers(executor,
|
||||
target_nqn)
|
||||
LOG.debug("[!] nvme_ctrls: %s", nvme_ctrls)
|
||||
for nvme_ctrl in nvme_ctrls:
|
||||
uuid_paths = glob.glob('/sys/class/block/' + nvme_ctrl + 'n*/uuid')
|
||||
for uuid_path in uuid_paths:
|
||||
try:
|
||||
uuid_lines, _err = executor._execute(
|
||||
'cat', uuid_path, run_as_root=True,
|
||||
root_helper=executor._root_helper)
|
||||
if uuid_lines.split('\n')[0] == vol_uuid:
|
||||
ignore = len('/uuid')
|
||||
ns_ind = uuid_path.rfind('/', 0, -ignore)
|
||||
nvme_device = uuid_path[ns_ind + 1: -ignore]
|
||||
return '/dev/' + nvme_device
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.exception(e)
|
||||
raise exception.VolumeDeviceNotFound(device=vol_uuid)
|
||||
|
||||
def _handle_replicated_volume(self, host_device_paths,
|
||||
|
@ -758,7 +826,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
|
||||
LOG.debug('[!] cmd = ' + str(cmd))
|
||||
NVMeOFConnector.run_mdadm(executor, cmd)
|
||||
# sometimes under load, md is not created right away so we wait
|
||||
# sometimes under load, md is not created right away, so we wait
|
||||
for i in range(60):
|
||||
try:
|
||||
is_exist = os.path.exists("/dev/md/" + name)
|
||||
|
@ -836,15 +904,17 @@ class NVMeOFConnector(base.BaseLinuxConnector):
|
|||
return out, err
|
||||
|
||||
@staticmethod
|
||||
def rescan(executor, target_nqn, vol_uuid):
|
||||
ctr_device = (
|
||||
NVMeOFConnector.get_search_path() +
|
||||
NVMeOFConnector._get_nvme_controller(executor, target_nqn))
|
||||
nvme_command = ('ns-rescan', ctr_device)
|
||||
try:
|
||||
NVMeOFConnector.run_nvme_cli(executor, nvme_command)
|
||||
except Exception as e:
|
||||
raise exception.CommandExecutionFailed(e, cmd=nvme_command)
|
||||
def rescan(executor, target_nqn):
|
||||
nvme_ctrls = NVMeOFConnector.get_live_nvme_controllers_map(executor,
|
||||
target_nqn)
|
||||
for nvme_ctrl in nvme_ctrls.values():
|
||||
ctr_device = (NVMeOFConnector.get_search_path() + nvme_ctrl)
|
||||
nvme_command = ('ns-rescan', ctr_device)
|
||||
try:
|
||||
NVMeOFConnector.run_nvme_cli(executor, nvme_command)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return nvme_ctrls
|
||||
|
||||
def _get_fs_type(self, device_path):
|
||||
cmd = ['blkid', device_path, '-s', 'TYPE', '-o', 'value']
|
||||
|
|
|
@ -50,7 +50,8 @@ connection_properties = {
|
|||
'replica_count': 3
|
||||
}
|
||||
fake_portal = ('fake', 'portal', 'tcp')
|
||||
|
||||
fake_controller = '/sys/class/nvme-fabrics/ctl/nvme1'
|
||||
fake_controllers_map = {'traddr=fakeaddress,trsvcid=4430': 'nvme1'}
|
||||
nvme_list_subsystems_stdout = """
|
||||
{
|
||||
"Subsystems" : [
|
||||
|
@ -137,6 +138,9 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
uuid = self.connector._get_host_uuid()
|
||||
self.assertIsNone(uuid)
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector,
|
||||
'_is_native_multipath_supported',
|
||||
return_value=True)
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'nvme_present',
|
||||
return_value=True)
|
||||
@mock.patch.object(utils, 'get_host_nqn',
|
||||
|
@ -147,11 +151,15 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
return_value=None)
|
||||
def test_get_connector_properties_without_sysuuid(self, mock_host_uuid,
|
||||
mock_sysuuid, mock_nqn,
|
||||
mock_nvme_present):
|
||||
mock_nvme_present,
|
||||
mock_nat_mpath_support):
|
||||
props = self.connector.get_connector_properties('sudo')
|
||||
expected_props = {'nqn': 'fakenqn'}
|
||||
expected_props = {'nqn': 'fakenqn', 'nvme_native_multipath': False}
|
||||
self.assertEqual(expected_props, props)
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector,
|
||||
'_is_native_multipath_supported',
|
||||
return_value=True)
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'nvme_present')
|
||||
@mock.patch.object(utils, 'get_host_nqn', autospec=True)
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid',
|
||||
|
@ -159,14 +167,15 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid', autospec=True)
|
||||
def test_get_connector_properties_with_sysuuid(self, mock_host_uuid,
|
||||
mock_sysuuid, mock_nqn,
|
||||
mock_nvme_present):
|
||||
mock_nvme_present,
|
||||
mock_native_mpath_support):
|
||||
mock_host_uuid.return_value = HOST_UUID
|
||||
mock_sysuuid.return_value = SYS_UUID
|
||||
mock_nqn.return_value = HOST_NQN
|
||||
mock_nvme_present.return_value = True
|
||||
props = self.connector.get_connector_properties('sudo')
|
||||
expected_props = {"system uuid": SYS_UUID, "nqn": HOST_NQN,
|
||||
"uuid": HOST_UUID}
|
||||
"uuid": HOST_UUID, 'nvme_native_multipath': False}
|
||||
self.assertEqual(expected_props, props)
|
||||
|
||||
def test_get_volume_paths_unreplicated(self):
|
||||
|
@ -277,7 +286,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
self, mock_connect_target_volume):
|
||||
mock_connect_target_volume.return_value = '/dev/nvme0n1'
|
||||
self.assertEqual(
|
||||
self.connector._connect_volume_replicated(
|
||||
self.connector._connect_volume_by_uuid(
|
||||
{
|
||||
'target_nqn': 'fakenqn',
|
||||
'vol_uuid': 'fakeuuid',
|
||||
|
@ -493,34 +502,30 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
)
|
||||
mock_device_size.assert_called_with(device_path)
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'rescan')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
|
||||
def test__connect_target_volume_with_connected_device(
|
||||
self, mock_device_path, mock_rescan, mock_controller):
|
||||
self, mock_device_path, mock_rescan):
|
||||
mock_device_path.return_value = '/dev/nvme0n1'
|
||||
self.assertEqual(
|
||||
self.connector._connect_target_volume(
|
||||
'fakenqn', 'fakeuuid', [('fake', 'portal', 'tcp')]),
|
||||
'/dev/nvme0n1')
|
||||
mock_controller.assert_called_with(self.connector, 'fakenqn')
|
||||
mock_rescan.assert_called_with(self.connector, 'fakenqn', 'fakeuuid')
|
||||
mock_rescan.assert_called_with(self.connector, 'fakenqn')
|
||||
mock_device_path.assert_called_with(
|
||||
self.connector, 'fakenqn', 'fakeuuid')
|
||||
self.connector, 'fakenqn', 'fakeuuid', list({}.values()))
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
|
||||
def test__connect_target_volume_not_connected(
|
||||
self, mock_device_path, mock_portals):
|
||||
mock_device_path.side_effect = exception.VolumeDeviceNotFound()
|
||||
mock_portals.return_value = True
|
||||
mock_portals.return_value = False
|
||||
self.assertRaises(exception.VolumeDeviceNotFound,
|
||||
self.connector._connect_target_volume, TARGET_NQN,
|
||||
VOL_UUID, [('fake', 'portal', 'tcp')])
|
||||
mock_device_path.assert_called_with(
|
||||
self.connector, TARGET_NQN, VOL_UUID)
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_controllers')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
|
||||
def test__connect_target_volume_no_portals_con(
|
||||
self, mock_portals, mock_controller):
|
||||
|
@ -530,21 +535,30 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
self.connector._connect_target_volume, 'fakenqn',
|
||||
'fakeuuid', [fake_portal])
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_live_nvme_controllers_map')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'rescan')
|
||||
def test__connect_target_volume_new_device_path(
|
||||
self, mock_device_path, mock_connect_portal, mock_controller):
|
||||
mock_controller.side_effect = exception.VolumeDeviceNotFound()
|
||||
self, mock_rescan, mock_connect_portal,
|
||||
mock_get_live_nvme_controllers_map, mock_device_path):
|
||||
mock_device_path.return_value = '/dev/nvme0n1'
|
||||
mock_rescan.return_value = {}
|
||||
mock_connect_portal.return_value = True
|
||||
mock_get_live_nvme_controllers_map.return_value = fake_controllers_map
|
||||
self.assertEqual(
|
||||
self.connector._connect_target_volume(
|
||||
'fakenqn', 'fakeuuid', [('fake', 'portal', 'tcp')]),
|
||||
'/dev/nvme0n1')
|
||||
mock_rescan.assert_called_with(self.connector, 'fakenqn')
|
||||
mock_connect_portal.assert_called_with(
|
||||
self.connector, 'fakenqn', [('fake', 'portal', 'tcp')])
|
||||
self.connector, 'fakenqn', [('fake', 'portal', 'tcp')], {})
|
||||
mock_get_live_nvme_controllers_map.assert_called_with(self.connector,
|
||||
'fakenqn')
|
||||
fake_controllers_map_values = fake_controllers_map.values()
|
||||
mock_device_path.assert_called_with(
|
||||
self.connector, 'fakenqn', 'fakeuuid')
|
||||
self.connector, 'fakenqn', 'fakeuuid',
|
||||
list(fake_controllers_map_values))
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
|
||||
def test_connect_to_portals(self, mock_nvme_cli):
|
||||
|
@ -553,7 +567,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
'tcp', '-n', 'fakenqn', '-Q', '128', '-l', '-1')
|
||||
self.assertEqual(
|
||||
self.connector.connect_to_portals(
|
||||
self.connector, 'fakenqn', [('10.0.0.1', 4420, 'tcp')]),
|
||||
self.connector, 'fakenqn', [('10.0.0.1', 4420, 'tcp')], {}),
|
||||
True)
|
||||
mock_nvme_cli.assert_called_with(self.connector, nvme_command)
|
||||
|
||||
|
@ -565,7 +579,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
'rdma', '-n', 'fakenqn', '-Q', '128', '-l', '-1')
|
||||
self.assertEqual(
|
||||
self.connector.connect_to_portals(
|
||||
self.connector, 'fakenqn', [('10.0.0.1', 4420, 'RoCEv2')]),
|
||||
self.connector, 'fakenqn', [('10.0.0.1', 4420, 'RoCEv2')], {}),
|
||||
False)
|
||||
mock_nvme_cli.assert_called_with(self.connector, nvme_command)
|
||||
|
||||
|
@ -735,25 +749,28 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
self.connector, ['mdadm', '--remove', '/dev/md/md1'])
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
|
||||
def test_rescan(self, mock_get_nvme_controller, mock_run_nvme_cli):
|
||||
mock_get_nvme_controller.return_value = 'nvme1'
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_live_nvme_controllers_map')
|
||||
def test_rescan(self, mock_get_live_nvme_controllers_map,
|
||||
mock_run_nvme_cli):
|
||||
mock_get_live_nvme_controllers_map.return_value = fake_controllers_map
|
||||
mock_run_nvme_cli.return_value = None
|
||||
result = self.connector.rescan(EXECUTOR, TARGET_NQN, VOL_UUID)
|
||||
self.assertIsNone(result)
|
||||
mock_get_nvme_controller.assert_called_with(EXECUTOR, TARGET_NQN)
|
||||
result = self.connector.rescan(EXECUTOR, TARGET_NQN)
|
||||
self.assertEqual(fake_controllers_map, result)
|
||||
mock_get_live_nvme_controllers_map.assert_called_with(EXECUTOR,
|
||||
TARGET_NQN)
|
||||
nvme_command = ('ns-rescan', NVME_DEVICE_PATH)
|
||||
mock_run_nvme_cli.assert_called_with(EXECUTOR, nvme_command)
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
|
||||
def test_rescan_err(self, mock_get_nvme_controller, mock_run_nvme_cli):
|
||||
mock_get_nvme_controller.return_value = 'nvme1'
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_live_nvme_controllers_map')
|
||||
def test_rescan_err(self, mock_get_live_nvme_controllers_map,
|
||||
mock_run_nvme_cli):
|
||||
mock_get_live_nvme_controllers_map.return_value = fake_controllers_map
|
||||
mock_run_nvme_cli.side_effect = Exception()
|
||||
self.assertRaises(exception.CommandExecutionFailed,
|
||||
self.connector.rescan, EXECUTOR, TARGET_NQN,
|
||||
VOL_UUID)
|
||||
mock_get_nvme_controller.assert_called_with(EXECUTOR, TARGET_NQN)
|
||||
result = self.connector.rescan(EXECUTOR, TARGET_NQN)
|
||||
self.assertEqual(fake_controllers_map, result)
|
||||
mock_get_live_nvme_controllers_map.assert_called_with(EXECUTOR,
|
||||
TARGET_NQN)
|
||||
nvme_command = ('ns-rescan', NVME_DEVICE_PATH)
|
||||
mock_run_nvme_cli.assert_called_with(EXECUTOR, nvme_command)
|
||||
|
||||
|
@ -874,17 +891,17 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
|
||||
@mock.patch.object(executor.Executor, '_execute')
|
||||
@mock.patch.object(glob, 'glob')
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
|
||||
def test_get_nvme_device_path(self, mock_get_nvme_controller, mock_glob,
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_controllers')
|
||||
def test_get_nvme_device_path(self, mock_get_nvme_controllers, mock_glob,
|
||||
mock_execute):
|
||||
mock_get_nvme_controller.return_value = 'nvme1'
|
||||
mock_get_nvme_controllers.return_value = ['nvme1']
|
||||
block_dev_path = '/sys/class/block/nvme1n*/uuid'
|
||||
mock_glob.return_value = ['/sys/class/block/nvme1n1/uuid']
|
||||
mock_glob.side_effect = [['/sys/class/block/nvme1n1/uuid']]
|
||||
mock_execute.return_value = (VOL_UUID + "\n", "")
|
||||
cmd = ['cat', '/sys/class/block/nvme1n1/uuid']
|
||||
result = self.connector.get_nvme_device_path(EXECUTOR, TARGET_NQN,
|
||||
VOL_UUID)
|
||||
mock_get_nvme_controller.assert_called_with(EXECUTOR, TARGET_NQN)
|
||||
mock_get_nvme_controllers.assert_called_with(EXECUTOR, TARGET_NQN)
|
||||
self.assertEqual(NVME_NS_PATH, result)
|
||||
mock_glob.assert_any_call(block_dev_path)
|
||||
args, kwargs = mock_execute.call_args
|
||||
|
@ -909,29 +926,25 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
if 'state' in value:
|
||||
return 'live' + "\n", ""
|
||||
|
||||
@mock.patch.object(executor.Executor, '_execute',
|
||||
side_effect=execute_side_effect)
|
||||
@mock.patch.object(glob, 'glob')
|
||||
def test_get_nvme_controller(self, mock_glob, mock_execute):
|
||||
ctrl_path = '/sys/class/nvme-fabrics/ctl/nvme*'
|
||||
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1']]
|
||||
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/state']
|
||||
result = self.connector._get_nvme_controller(EXECUTOR, TARGET_NQN)
|
||||
self.assertEqual('nvme1', result)
|
||||
mock_glob.assert_any_call(ctrl_path)
|
||||
args, kwargs = mock_execute.call_args
|
||||
self.assertEqual(args[0], cmd[0])
|
||||
self.assertEqual(args[1], cmd[1])
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_live_nvme_controllers_map')
|
||||
def test_get_nvme_controllers(self, mock_get_live_nvme_controllers_map):
|
||||
mock_get_live_nvme_controllers_map.return_value = fake_controllers_map
|
||||
result = self.connector.get_nvme_controllers(EXECUTOR, TARGET_NQN)
|
||||
fake_controllers_map_values = fake_controllers_map.values()
|
||||
self.assertEqual(list(fake_controllers_map_values)[0][1],
|
||||
list(result)[0][1])
|
||||
mock_get_live_nvme_controllers_map.assert_called_with(EXECUTOR,
|
||||
TARGET_NQN)
|
||||
|
||||
@mock.patch.object(executor.Executor, '_execute',
|
||||
side_effect=execute_side_effect_not_live)
|
||||
@mock.patch.object(glob, 'glob')
|
||||
def test_get_nvme_controller_not_live(self, mock_glob, mock_execute):
|
||||
def test_get_nvme_controllers_not_live(self, mock_glob, mock_execute):
|
||||
ctrl_path = '/sys/class/nvme-fabrics/ctl/nvme*'
|
||||
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1']]
|
||||
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/state']
|
||||
self.assertRaises(exception.VolumeDeviceNotFound,
|
||||
self.connector._get_nvme_controller, EXECUTOR,
|
||||
self.connector.get_nvme_controllers, EXECUTOR,
|
||||
TARGET_NQN)
|
||||
mock_glob.assert_any_call(ctrl_path)
|
||||
args, kwargs = mock_execute.call_args
|
||||
|
@ -941,12 +954,12 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
@mock.patch.object(executor.Executor, '_execute',
|
||||
side_effect=execute_side_effect_not_found)
|
||||
@mock.patch.object(glob, 'glob')
|
||||
def test_get_nvme_controller_not_found(self, mock_glob, mock_execute):
|
||||
def test_get_nvme_controllers_not_found(self, mock_glob, mock_execute):
|
||||
ctrl_path = '/sys/class/nvme-fabrics/ctl/nvme*'
|
||||
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1']]
|
||||
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/state']
|
||||
self.assertRaises(exception.VolumeDeviceNotFound,
|
||||
self.connector._get_nvme_controller, EXECUTOR,
|
||||
self.connector.get_nvme_controllers, EXECUTOR,
|
||||
TARGET_NQN)
|
||||
mock_glob.assert_any_call(ctrl_path)
|
||||
args, kwargs = mock_execute.call_args
|
||||
|
@ -1086,6 +1099,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
|
|||
self.assertFalse(result)
|
||||
|
||||
def _get_host_nqn(self):
|
||||
host_nqn = None
|
||||
try:
|
||||
with open('/etc/nvme/hostnqn', 'r') as f:
|
||||
host_nqn = f.read().strip()
|
||||
|
|
|
@ -42,6 +42,9 @@ class ZeroIntervalLoopingCall(loopingcall.FixedIntervalLoopingCall):
|
|||
|
||||
class ConnectorUtilsTestCase(test_base.TestCase):
|
||||
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector,
|
||||
'_is_native_multipath_supported',
|
||||
return_value=False)
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid',
|
||||
return_value=None)
|
||||
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid',
|
||||
|
@ -64,6 +67,7 @@ class ConnectorUtilsTestCase(test_base.TestCase):
|
|||
mock_nqn,
|
||||
mock_hostuuid,
|
||||
mock_sysuuid,
|
||||
mock_native_multipath_supported,
|
||||
host='fakehost'):
|
||||
props_actual = connector.get_connector_properties('sudo',
|
||||
MY_IP,
|
||||
|
@ -76,6 +80,7 @@ class ConnectorUtilsTestCase(test_base.TestCase):
|
|||
'host': host,
|
||||
'ip': MY_IP,
|
||||
'multipath': multipath_result,
|
||||
'nvme_native_multipath': False,
|
||||
'os_type': os_type,
|
||||
'platform': platform,
|
||||
'do_local_attach': False}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Phase 1 (Native) implementation of NVMeoF Multipathing.
|
||||
See cinder-specs/specs/yoga/nvme-multipath
|
Loading…
Reference in New Issue