Lightbits LightOS driver

This commit introduces the LightOS driver for os-brick. LightOS is
a software-defined disaggregated clustered storage solution running
on commodity servers with commodity SSDs. It it developed by Lightbits
Labs (https://www.lightbitslabs.com) and is actively developed and
maintained. LightOS is proprietary but the openstack
drivers are licensed under Apache v2.0.

The Cinder driver for LightOS currently supports the following
functionality:

Create volume
Delete volume
Attach volume
Detach volume
Create image from volume
create volume from image
Live migration
Volume replication
Thin provisioning
Multi-attach
Extend volume
Create snapshot
Delete snapshot
Create volume from snapshot
Create volume from volume (clone)

This driver has been developed and has been in use for a couple of
years by Lightbits and our clients.
We have tested it extensively internally with multiple openstack
versions, including Queens, Rocky, Stein, and Train. We have
also tested it with master (19.1 xena) and we are working to extend
testing to cover additional openstack releases.

We are glad to join the openstack community and hope to get your
feedback and comments on this driver, and if it is acceptable, to
see it merged into the tree.

Signed-off-by: Yuval Brave  yuval@lightbitslabs.com
Change-Id: I2e86fa84049053b7c75421d33ad1a1af459ef4e0
This commit is contained in:
yuval brave 2021-12-13 18:09:11 +02:00
parent 7a6a09fc84
commit 627da6a40b
6 changed files with 623 additions and 0 deletions

View File

@ -56,3 +56,4 @@ GPFS = "GPFS"
STORPOOL = "STORPOOL"
NVME = "NVME"
NVMEOF = "NVMEOF"
LIGHTOS = "LIGHTOS"

View File

@ -64,6 +64,7 @@ unix_connector_list = [
'os_brick.initiator.connectors.vmware.VmdkConnector',
'os_brick.initiator.connectors.storpool.StorPoolConnector',
'os_brick.initiator.connectors.nvmeof.NVMeOFConnector',
'os_brick.initiator.connectors.lightos.LightOSConnector',
]
@ -114,6 +115,8 @@ _connector_mapping_linux = {
'os_brick.initiator.connectors.nvmeof.NVMeOFConnector',
initiator.NVMEOF:
'os_brick.initiator.connectors.nvmeof.NVMeOFConnector',
initiator.LIGHTOS:
'os_brick.initiator.connectors.lightos.LightOSConnector',
}
# Mapping for the S390X platform

View File

@ -0,0 +1,346 @@
# Copyright (C) 2016-2022 Lightbits Labs Ltd.
# Copyright (C) 2020 Intel Corporation
# 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.
import glob
import http.client
import os
import re
import tempfile
import time
from oslo_concurrency import lockutils
from oslo_concurrency import processutils as putils
from oslo_log import log as logging
from os_brick import exception
from os_brick.i18n import _
from os_brick.initiator.connectors import base
from os_brick.privileged import lightos as priv_lightos
from os_brick.privileged import nvmeof as priv_nvme
from os_brick import utils
DEVICE_SCAN_ATTEMPTS_DEFAULT = 5
DISCOVERY_CLIENT_PORT = 6060
LOG = logging.getLogger(__name__)
synchronized = lockutils.synchronized_with_prefix('os-brick-')
nvmec_pattern = ".*nvme[0-9]+[cp][0-9]+.*"
nvmec_match = re.compile(nvmec_pattern)
class LightOSConnector(base.BaseLinuxConnector):
"""Connector class to attach/detach LightOS volumes using NVMe/TCP."""
WAIT_DEVICE_TIMEOUT = 60
def __init__(self,
root_helper,
driver=None,
execute=None,
device_scan_attempts=DEVICE_SCAN_ATTEMPTS_DEFAULT,
message_queue=None,
*args,
**kwargs):
super(LightOSConnector, self).__init__(
root_helper,
driver=driver,
execute=execute,
device_scan_attempts=device_scan_attempts,
*args, **kwargs)
self.message_queue = message_queue
self.DISCOVERY_DIR_PATH = '/etc/discovery-client/discovery.d/'
@staticmethod
def get_connector_properties(root_helper, *args, **kwargs):
"""The LightOS connector properties."""
props = {}
lightos_connector = LightOSConnector(root_helper=root_helper,
message_queue=None,
execute=kwargs.get('execute'))
hostnqn = lightos_connector.get_hostnqn()
found_dsc = lightos_connector.find_dsc()
if not found_dsc:
LOG.debug('LIGHTOS: did not find dsc, continuing anyway.')
if hostnqn:
LOG.debug("LIGHTOS: finally hostnqn: %s dsc: %s",
hostnqn, found_dsc)
props['hostnqn'] = hostnqn
props['found_dsc'] = found_dsc
else:
LOG.debug('LIGHTOS: no hostnqn found.')
return props
def dsc_file_name(self, uuid):
return os.path.join(self.DISCOVERY_DIR_PATH, "%s.conf" % uuid)
def find_dsc(self):
conn = http.client.HTTPConnection("localhost", DISCOVERY_CLIENT_PORT)
try:
conn.request("HEAD", "/metrics")
resp = conn.getresponse()
return 'found' if resp.status == http.client.OK else ''
except Exception as e:
LOG.debug(f'LIGHTOS: {e}')
out = ''
return out
def dsc_need_connect(self, connection_info):
return not os.path.isfile(self.dsc_file_name(connection_info['uuid']))
def dsc_connect_volume(self, connection_info):
if not self.dsc_need_connect(connection_info):
return
subsysnqn = connection_info['nqn']
uuid = connection_info['uuid']
hostnqn = self.get_hostnqn()
with tempfile.NamedTemporaryFile(mode='w', delete=False) as dscfile:
dscfile.write('# os_brick connector dsc file for LightOS'
' volume: {}\n'.format(uuid))
for (ip, node) in connection_info['lightos_nodes'].items():
transport = node['transport_type']
host = node['target_portal']
port = node['target_port']
dscfile.write('-t {} -a {} -s {} -q {} -n {}\n'.format(
transport, host, port, hostnqn, subsysnqn))
dscfile.flush()
try:
dest_name = self.dsc_file_name(uuid)
priv_lightos.move_dsc_file(dscfile.name, dest_name)
except Exception:
LOG.warning(
"LIGHTOS: Failed to create dsc file for connection with"
f" uuid:{uuid}")
raise
def dsc_disconnect_volume(self, connection_info):
uuid = connection_info['uuid']
try:
priv_lightos.delete_dsc_file(self.dsc_file_name(uuid))
except Exception:
LOG.warning("LIGHTOS: Failed delete dsc file uuid:{}".format(uuid))
raise
def monitor_db(self, lightos_db):
for connection_info in lightos_db.values():
self.dsc_connect_volume(connection_info)
def monitor_message_queue(self, message_queue, lightos_db):
while not message_queue.empty():
msg = message_queue.get()
op, connection = msg
LOG.debug("LIGHTOS: queue got op: %s, connection: %s",
op, connection)
if op == 'delete':
LOG.info("LIGHTOS: Removing volume: %s from db",
connection['uuid'])
if connection['uuid'] in lightos_db:
del lightos_db[connection['uuid']]
else:
LOG.warning("LIGHTOS: No volume: %s found in db",
connection['uuid'])
elif op == 'add':
LOG.info("LIGHTOS: Adding volume: %s to db",
connection['uuid'])
lightos_db[connection['uuid']] = connection
def lightos_monitor(self, lightos_db, message_queue):
'''Bookkeeping lightos connections.
This is useful when the connector is comming up to a running node with
connected volumes already exists.
This is used in the Nova driver to restore connections after reboot
'''
first_time = True
while True:
self.monitor_db(lightos_db)
# give us some time before trying to access the MQ
# for the first time
if first_time:
time.sleep(5)
first_time = False
else:
time.sleep(1)
self.monitor_message_queue(message_queue, lightos_db)
# This is part of our abstract interface
def get_search_path(self):
return '/dev'
# This is part of our abstract interface
def get_volume_paths(self, connection_properties):
path = connection_properties['device_path']
return [path]
def _check_device_exists_using_dev_lnk(self, uuid):
lnk_path = f"/dev/disk/by-id/nvme-uuid.{uuid}"
if os.path.exists(lnk_path):
devname = os.path.realpath(lnk_path)
if devname.startswith("/dev/nvme"):
LOG.info("LIGHTOS: devpath %s detected for uuid %s",
devname, uuid)
return devname
return None
def _check_device_exists_reading_block_class(self, uuid):
file_path = "/sys/class/block/*/wwid"
wwid = "uuid." + uuid
for match_path in glob.glob(file_path):
try:
with open(match_path, "r") as f:
match_wwid = f.readline()
except Exception:
LOG.warning("LIGHTOS: Failed to read file %s",
match_path)
continue
if wwid != match_wwid.strip():
continue
# skip slave nvme devices, for example: nvme0c0n1
if nvmec_match.match(match_path.split("/")[-2]):
continue
LOG.info("LIGHTOS: matching uuid %s was found"
" for device path %s", uuid, match_path)
return os.path.join("/dev", match_path.split("/")[-2])
return None
@utils.trace
def _get_device_by_uuid(self, uuid):
endtime = time.time() + self.WAIT_DEVICE_TIMEOUT
while time.time() < endtime:
try:
device = self._check_device_exists_using_dev_lnk(uuid)
if device:
return device
except Exception as e:
LOG.debug(f'LIGHTOS: {e}')
device = self._check_device_exists_reading_block_class(uuid)
if device:
return device
time.sleep(1)
return None
def _get_size_by_uuid(self, uuid):
devpath = self._get_device_by_uuid(uuid)
devname = devpath.split("/")[-1]
try:
size_path_name = os.path.join("/sys/class/block/", devname, "size")
with open(size_path_name, "r") as f:
size_blks = f.read().strip()
bytesize = int(size_blks) * 512
return bytesize
except Exception:
LOG.warning("LIGHTOS: Could not find the size at for"
" uuid %s in %s", uuid, devpath)
return None
@utils.trace
@synchronized('volume_op')
def connect_volume(self, connection_properties):
"""Discover and attach the volume.
:param connection_properties: The dictionary that describes all
of the target volume attributes.
connection_properties must include:
nqn - NVMe subsystem name to the volume to be connected
target_port - NVMe target port that hosts the nqn sybsystem
target_portal - NVMe target ip that hosts the nqn sybsystem
:type connection_properties: dict
:returns: dict
"""
device_info = {'type': 'block'}
uuid = connection_properties['uuid']
LOG.info("LIGHTOS: connect_volume called for volume %s, connection"
" properties: %s",
uuid, connection_properties)
self.dsc_connect_volume(connection_properties)
device_path = self._get_device_by_uuid(uuid)
if not device_path:
msg = _("Device with uuid %s did not show up" % uuid)
priv_lightos.delete_dsc_file(self.dsc_file_name(uuid))
raise exception.BrickException(message=msg)
device_info['path'] = device_path
# bookkeeping lightos connections - add connection
if self.message_queue:
self.message_queue.put(('add', connection_properties))
return device_info
@utils.trace
@synchronized('volume_op')
def disconnect_volume(self, connection_properties, device_info,
force=False, ignore_errors=False):
"""Disconnect a volume from the local host.
The connection_properties are the same as from connect_volume.
The device_info is returned from connect_volume.
:param connection_properties: The dictionary that describes all
of the target volume attributes.
:type connection_properties: dict
:param device_info: historical difference, but same as connection_props
:type device_info: dict
:param force: Whether to forcefully disconnect even if flush fails.
:type force: bool
:param ignore_errors: When force is True, this will decide whether to
ignore errors or raise an exception once finished
the operation. Default is False.
:type ignore_errors: bool
"""
if force:
# if operator want to force detach - we just return and cinder
# driver will terminate the connection by updating the ACL
return
uuid = connection_properties['uuid']
LOG.debug('LIGHTOS: disconnect_volume called for volume %s', uuid)
device_path = self._get_device_by_uuid(uuid)
try:
if device_path:
self._linuxscsi.flush_device_io(device_path)
except putils.ProcessExecutionError:
if not ignore_errors:
raise
self.dsc_disconnect_volume(connection_properties)
# bookkeeping lightos connections - delete connection
if self.message_queue:
self.message_queue.put(('delete', connection_properties))
@utils.trace
@synchronized('volume_op')
def extend_volume(self, connection_properties):
uuid = connection_properties['uuid']
new_size = self._get_size_by_uuid(uuid)
return new_size
def get_hostnqn(self):
try:
with open('/etc/nvme/hostnqn', 'r') as f:
host_nqn = f.read().strip()
except IOError:
host_nqn = priv_nvme.create_hostnqn()
except Exception:
host_nqn = None
return host_nqn

View File

@ -0,0 +1,30 @@
# Copyright (C) 2016-2022 Lightbits Labs Ltd.
# 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.
import shutil
from oslo_utils import fileutils
import os_brick.privileged
@os_brick.privileged.default.entrypoint
def delete_dsc_file(file_name):
return fileutils.delete_if_exists(file_name)
@os_brick.privileged.default.entrypoint
def move_dsc_file(src, dst):
return shutil.move(src, dst)

View File

@ -0,0 +1,239 @@
# Copyright (C) 2016-2020 Lightbits Labs Ltd.
# 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.
import glob
import http.client
import queue
from unittest import mock
from unittest.mock import mock_open
from os_brick import exception
from os_brick.initiator.connectors import lightos
from os_brick.initiator import linuxscsi
from os_brick.privileged import lightos as priv_lightos
from os_brick.tests.initiator import test_connector
FAKE_NQN = "nqn.fake.qnq"
FAKE_LIGHTOS_CLUSTER_NODES = {
"nodes": [
{"UUID": "926e6df8-73e1-11ec-a624-000000000001",
"nvmeEndpoint": "192.168.75.10:4420"},
{"UUID": "926e6df8-73e1-11ec-a624-000000000002",
"nvmeEndpoint": "192.168.75.11:4420"},
{"UUID": "926e6df8-73e1-11ec-a624-000000000003",
"nvmeEndpoint": "192.168.75.12:4420"}
]
}
FAKE_SUBSYSNQN = "nqn.2014-08.org.nvmexpress:NVMf:uuid:"
FAKE_LIGHTOS_CLUSTER_INFO = {
'UUID': "926e6df8-73e1-11ec-a624-07ba3880f6cc",
'subsystemNQN': "nqn.2014-08.org.nvmexpress:NVMf:uuid:"
"f4a89ce0-9fc2-4900-bfa3-00ad27995e7b",
'nodes_ips': ["10.17.167.4", "10.17.167.5", "10.17.167.6"]
}
FAKE_VOLUME_UUID = "926e6df8-73e1-11ec-a624-07ba3880f6cd"
NUM_BLOCKS_IN_GIB = 2097152
BLOCK_SIZE = 512
def get_http_response_mock(status):
resp = mock.Mock()
resp.status = status
return resp
class LightosConnectorTestCase(test_connector.ConnectorTestCase):
"""Test cases for NVMe initiator class."""
def setUp(self):
super(LightosConnectorTestCase, self).setUp()
self.connector = lightos.LightOSConnector(None,
execute=self.fake_execute)
@staticmethod
def _get_connection_info():
lightos_nodes = {}
for ip in FAKE_LIGHTOS_CLUSTER_INFO['nodes_ips']:
lightos_nodes[ip] = dict(
transport_type='tcp',
target_portal=ip,
target_port=8009
)
return dict(
nqn=FAKE_LIGHTOS_CLUSTER_INFO['subsystemNQN'],
uuid=FAKE_LIGHTOS_CLUSTER_INFO['UUID'],
lightos_nodes=lightos_nodes
)
@mock.patch.object(lightos.LightOSConnector, 'get_hostnqn',
return_value=FAKE_NQN)
@mock.patch.object(lightos.LightOSConnector, 'find_dsc',
return_value=True)
def test_get_connector_properties(self, mock_nqn, mock_dsc):
props = self.connector.get_connector_properties(None)
expected_props = {"hostnqn": FAKE_NQN, "found_dsc": True}
self.assertEqual(expected_props, props)
@mock.patch.object(lightos.http.client.HTTPConnection, "request",
return_value=None)
@mock.patch.object(lightos.http.client.HTTPConnection, "getresponse",
return_value=get_http_response_mock(http.client.OK))
def test_find_dsc_success(self, mocked_connection, mocked_response):
mocked_connection.request.return_value = None
mocked_response.getresponse.return_value = get_http_response_mock(
http.client.OK)
self.assertEqual(self.connector.find_dsc(), 'found')
@mock.patch.object(lightos.http.client.HTTPConnection, "request",
return_value=None)
@mock.patch.object(lightos.http.client.HTTPConnection, "getresponse",
return_value=get_http_response_mock(
http.client.NOT_FOUND))
def test_find_dsc_failure(self, mocked_connection, mocked_response):
mocked_connection.request.return_value = None
mocked_response.getresponse.return_value = get_http_response_mock(
http.client.OK)
self.assertEqual(self.connector.find_dsc(), '')
@mock.patch.object(lightos.LightOSConnector, 'get_hostnqn',
return_value=FAKE_NQN)
@mock.patch.object(lightos.priv_lightos, 'move_dsc_file',
return_value="/etc/discovery_client/discovery.d/v0")
@mock.patch.object(lightos.LightOSConnector,
'_check_device_exists_using_dev_lnk',
return_value="/dev/nvme0n1")
def test_connect_volume_succeed(self, mock_nqn, mock_move_file,
mock_check_device):
self.connector.connect_volume(self._get_connection_info())
@mock.patch.object(lightos.LightOSConnector, 'get_hostnqn',
return_value=FAKE_NQN)
@mock.patch.object(lightos.priv_lightos, 'move_dsc_file',
return_value="/etc/discovery_client/discovery.d/v0")
@mock.patch.object(lightos.priv_lightos, 'delete_dsc_file',
return_value=None)
@mock.patch.object(lightos.LightOSConnector, '_get_device_by_uuid',
return_value=None)
def test_connect_volume_failure(self, mock_nqn, mock_move_file,
mock_delete_file, mock_get_device):
self.assertRaises(exception.BrickException,
self.connector.connect_volume,
self._get_connection_info())
@mock.patch.object(priv_lightos, 'delete_dsc_file', return_value=True)
def test_dsc_disconnect_volume_succeed(self, mock_priv_lightos):
self.connector.dsc_disconnect_volume(self._get_connection_info())
@mock.patch.object(priv_lightos, 'delete_dsc_file',
side_effect=OSError("failed to delete file"))
def test_dsc_disconnect_volume_failure(self, execute_mock):
self.assertRaises(OSError,
self.connector.dsc_disconnect_volume,
self._get_connection_info())
@mock.patch.object(lightos.LightOSConnector,
'_check_device_exists_using_dev_lnk',
return_value=("/dev/nvme0n1"))
def test_get_device_by_uuid_succeed_with_link(self, execute_mock):
self.assertEqual(self.connector._get_device_by_uuid(FAKE_VOLUME_UUID),
"/dev/nvme0n1")
@mock.patch.object(lightos.LightOSConnector,
'_check_device_exists_reading_block_class',
return_value=("/dev/nvme0n1"))
def test_get_device_by_uuid_succeed_with_block_class(self, execute_mock):
self.assertEqual(self.connector._get_device_by_uuid(FAKE_VOLUME_UUID),
"/dev/nvme0n1")
@mock.patch.object(lightos.LightOSConnector,
'_check_device_exists_using_dev_lnk',
side_effect=[None, False, "/dev/nvme0n1"])
@mock.patch.object(lightos.LightOSConnector,
'_check_device_exists_reading_block_class',
side_effect=[None, False, "/dev/nvme0n1"])
def test_get_device_by_uuid_many_attempts(self, execute_mock, glob_mock):
self.assertEqual(self.connector._get_device_by_uuid(FAKE_VOLUME_UUID),
'/dev/nvme0n1')
@mock.patch.object(lightos.LightOSConnector, 'dsc_connect_volume',
return_value=None)
@mock.patch.object(lightos.LightOSConnector, '_get_device_by_uuid',
return_value="/dev/nvme0n1")
def test_connect_volume(self, dsc_connect, path):
connection_properties = {"hostnqn": FAKE_NQN, "found_dsc": True,
"uuid": "123"}
expected_device_info = {'type': 'block', "path": "/dev/nvme0n1"}
device_info = self.connector.connect_volume(connection_properties)
self.assertEqual(expected_device_info, device_info)
@mock.patch.object(linuxscsi.LinuxSCSI, 'flush_device_io', autospec=True)
@mock.patch.object(lightos.LightOSConnector, '_get_device_by_uuid',
return_value="/dev/nvme0n1")
@mock.patch.object(lightos.LightOSConnector, 'dsc_disconnect_volume')
def test_disconnect_volume(self, mock_disconnect, mock_uuid, mock_flush):
connection_properties = {"hostnqn": FAKE_NQN, "found_dsc": True,
"uuid": "123"}
self.connector.disconnect_volume(connection_properties, None)
mock_disconnect.assert_called_once_with(connection_properties)
mock_flush.assert_called_once_with(mock.ANY, "/dev/nvme0n1")
@mock.patch.object(lightos.LightOSConnector, '_get_device_by_uuid',
return_value="/dev/nvme0n1")
@mock.patch("builtins.open", new_callable=mock_open,
read_data=f"{str(NUM_BLOCKS_IN_GIB)}\n")
def test_extend_volume(self, mock_execute, m_open):
connection_properties = {'uuid': FAKE_VOLUME_UUID}
self.assertEqual(self.connector.extend_volume(connection_properties),
NUM_BLOCKS_IN_GIB * BLOCK_SIZE)
def test_monitor_message_queue_delete(self):
message_queue = queue.Queue()
connection = {"uuid": "123"}
message_queue.put(("delete", connection))
lightos_db = {"123": "fake_connection"}
self.connector.monitor_message_queue(message_queue, lightos_db)
self.assertEqual(len(lightos_db), 0)
def test_monitor_message_queue_add(self):
message_queue = queue.Queue()
connection = {"uuid": "123"}
lightos_db = {}
message_queue.put(("add", connection))
self.connector.monitor_message_queue(message_queue, lightos_db)
self.assertEqual(len(lightos_db), 1)
@mock.patch.object(lightos.os.path, 'exists', return_value=True)
@mock.patch.object(lightos.os.path, 'realpath',
return_value="/dev/nvme0n1")
def test_check_device_exists_using_dev_lnk_succeed(self, mock_path_exists,
mock_realpath):
found_dev = self.connector._check_device_exists_using_dev_lnk(
FAKE_VOLUME_UUID)
self.assertEqual("/dev/nvme0n1", found_dev)
def test_check_device_exists_using_dev_lnk_false(self):
self.assertIsNone(self.connector._check_device_exists_using_dev_lnk(
FAKE_VOLUME_UUID))
@mock.patch.object(glob, "glob", return_value=['/path/nvme0n1/wwid'])
@mock.patch("builtins.open", new_callable=mock_open,
read_data=f"uuid.{FAKE_VOLUME_UUID}\n")
def test_check_device_exists_reading_block_class(self, mock_glob, m_open):
found_dev = self.connector._check_device_exists_reading_block_class(
FAKE_VOLUME_UUID)
self.assertEqual("/dev/nvme0n1", found_dev)

View File

@ -24,6 +24,7 @@ from os_brick.initiator import connector
from os_brick.initiator.connectors import base
from os_brick.initiator.connectors import fake
from os_brick.initiator.connectors import iscsi
from os_brick.initiator.connectors import lightos
from os_brick.initiator.connectors import nvmeof
from os_brick.initiator import linuxfc
from os_brick.privileged import rootwrap as priv_rootwrap
@ -41,6 +42,8 @@ class ZeroIntervalLoopingCall(loopingcall.FixedIntervalLoopingCall):
class ConnectorUtilsTestCase(test_base.TestCase):
@mock.patch.object(lightos.LightOSConnector, 'get_hostnqn',
return_value=None)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid',
return_value=None)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid',
@ -63,6 +66,7 @@ class ConnectorUtilsTestCase(test_base.TestCase):
mock_nqn,
mock_hostuuid,
mock_sysuuid,
mock_lightos_hostnqn,
host='fakehost'):
props_actual = connector.get_connector_properties('sudo',
MY_IP,