diff -Nru python-os-brick-5.1.0/AUTHORS python-os-brick-5.2.0/AUTHORS --- python-os-brick-5.1.0/AUTHORS 2021-11-25 15:24:09.000000000 +0000 +++ python-os-brick-5.2.0/AUTHORS 2022-02-18 15:38:36.000000000 +0000 @@ -54,6 +54,7 @@ Kendall Nelson Lee Yarwood Liang Fang +Lior Friedman LisaLi Liu Qing Lucian Petrut @@ -71,6 +72,7 @@ Mike Durnosvystov Mike Perez Monty Taylor +Muli Ben-Yehuda Naga Venkata Nate Potter Ondřej Nový @@ -148,6 +150,7 @@ lisali liuyamin melissaml +olegnest pengyuesheng qingszhao qiufossen @@ -161,6 +164,7 @@ xianming mao xuanyandong yenai +yuval brave yuyafei zengjia zhangboye diff -Nru python-os-brick-5.1.0/ChangeLog python-os-brick-5.2.0/ChangeLog --- python-os-brick-5.1.0/ChangeLog 2021-11-25 15:24:08.000000000 +0000 +++ python-os-brick-5.2.0/ChangeLog 2022-02-18 15:38:36.000000000 +0000 @@ -1,6 +1,23 @@ CHANGES ======= +5.2.0 +----- + +* Failure to generate hostnqn in case missing "show-hostnqn" sub-command +* Add "known issues" note to yoga os-brick release +* nvmeof connector check controller already connected +* Update requirements minima for Yoga release +* Updating python testing as per Yoga testing runtime +* Lightos connector - refactor disconnect volume +* Robust md creation/ending +* Reuse get\_host\_nqn code +* nvmeof connector utilize replica\_count +* NVMeOF connector support multipath-enabled kernels +* releasenotes: add Lightbits LightOS connector release note +* Lightbits LightOS driver +* Prevent CI from running on irrelevant files + 5.1.0 ----- @@ -8,6 +25,9 @@ * Update notes about hacking on os-brick * multipath/iscsi: iSCSI connections are not reinitiated after reboot * Fix paths for NVMe devices (non-nguid) +* Fix PowerFlex connector HTTPS certificate validation +* mypy: utils.py +* mypy: exception.py * Add Python3 yoga unit tests * Update master for stable/xena diff -Nru python-os-brick-5.1.0/debian/changelog python-os-brick-5.2.0/debian/changelog --- python-os-brick-5.1.0/debian/changelog 2022-01-12 19:30:09.000000000 +0000 +++ python-os-brick-5.2.0/debian/changelog 2022-03-02 19:26:12.000000000 +0000 @@ -1,3 +1,10 @@ +python-os-brick (5.2.0-0ubuntu1) jammy; urgency=medium + + * New upstream release for OpenStack Yoga. + * d/control: Align (Build-)Depends with upstream. + + -- Corey Bryant Wed, 02 Mar 2022 14:26:12 -0500 + python-os-brick (5.1.0-0ubuntu1) jammy; urgency=medium * New upstream release for OpenStack Yoga. diff -Nru python-os-brick-5.1.0/debian/control python-os-brick-5.2.0/debian/control --- python-os-brick-5.1.0/debian/control 2022-01-12 19:30:09.000000000 +0000 +++ python-os-brick-5.2.0/debian/control 2022-03-02 19:26:12.000000000 +0000 @@ -11,32 +11,32 @@ dh-python, openstack-pkg-tools, python3-all, - python3-pbr (>= 5.5.1), + python3-pbr (>= 5.8.0), python3-setuptools, python3-sphinx (>= 3.5.1), Build-Depends-Indep: python3-babel (>= 2.3.4), - python3-castellan (>= 3.7.0), + python3-castellan (>= 3.10.0), python3-coverage, python3-ddt (>= 1.4.1), python3-eventlet (>= 0.30.1), python3-hacking, python3-openstackdocstheme , - python3-os-win (>= 5.4.0), - python3-oslo.concurrency (>= 4.4.0), - python3-oslo.context (>= 1:3.1.1), - python3-oslo.i18n (>= 5.0.1), - python3-oslo.log (>= 4.4.0), - python3-oslo.privsep (>= 2.4.0), - python3-oslo.serialization (>= 4.1.0), - python3-oslo.service (>= 2.5.0), - python3-oslo.utils (>= 4.8.0), - python3-oslo.vmware (>= 3.8.0), + python3-os-win (>= 5.5.0), + python3-oslo.concurrency (>= 4.5.0), + python3-oslo.context (>= 1:3.4.0), + python3-oslo.i18n (>= 5.1.0), + python3-oslo.log (>= 4.6.1), + python3-oslo.privsep (>= 2.6.2), + python3-oslo.serialization (>= 4.2.0), + python3-oslo.service (>= 2.8.0), + python3-oslo.utils (>= 4.12.1), + python3-oslo.vmware (>= 3.10.0), python3-oslotest , - python3-reno (>= 2.11.2), + python3-reno, python3-requests (>= 2.25.1), python3-retrying (>= 1.2.3), - python3-sphinx-feature-classification (>= 1.0.1), + python3-sphinx-feature-classification (>= 1.1.0), python3-sphinxcontrib.apidoc (>= 0.3.0), python3-stestr , python3-tenacity (>= 6.3.1), @@ -83,16 +83,16 @@ os-brick-common (= ${binary:Version}), python3-babel (>= 2.3.4), python3-eventlet (>= 0.30.1), - python3-os-win (>= 5.4.0), - python3-oslo.concurrency (>= 4.4.0), - python3-oslo.context (>= 1:3.1.1), - python3-oslo.i18n (>= 5.0.1), - python3-oslo.log (>= 4.4.0), - python3-oslo.privsep (>= 2.4.0), - python3-oslo.serialization (>= 4.1.0), - python3-oslo.service (>= 2.5.0), - python3-oslo.utils (>= 4.8.0), - python3-pbr (>= 5.5.1), + python3-os-win (>= 5.5.0), + python3-oslo.concurrency (>= 4.5.0), + python3-oslo.context (>= 1:3.4.0), + python3-oslo.i18n (>= 5.1.0), + python3-oslo.log (>= 4.6.1), + python3-oslo.privsep (>= 2.6.2), + python3-oslo.serialization (>= 4.2.0), + python3-oslo.service (>= 2.8.0), + python3-oslo.utils (>= 4.12.1), + python3-pbr (>= 5.8.0), python3-requests (>= 2.25.1), python3-retrying (>= 1.2.3), python3-tenacity (>= 6.3.1), diff -Nru python-os-brick-5.1.0/mypy-files.txt python-os-brick-5.2.0/mypy-files.txt --- python-os-brick-5.1.0/mypy-files.txt 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/mypy-files.txt 2022-02-18 15:37:53.000000000 +0000 @@ -1,4 +1,7 @@ +os_brick/exception.py os_brick/executor.py +os_brick/i18n.py +os_brick/utils.py os_brick/initiator/linuxscsi.py os_brick/initiator/connectors/base.py os_brick/initiator/connectors/base_iscsi.py diff -Nru python-os-brick-5.1.0/os_brick/exception.py python-os-brick-5.2.0/os_brick/exception.py --- python-os-brick-5.1.0/os_brick/exception.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/exception.py 2022-02-18 15:37:53.000000000 +0000 @@ -15,6 +15,7 @@ """Exceptions for the Brick library.""" import traceback +from typing import Iterable, List, Optional # noqa: H301 from oslo_concurrency import processutils as putils from oslo_log import log as logging @@ -34,7 +35,7 @@ """ message = _("An unknown exception occurred.") code = 500 - headers = {} + headers: dict = {} safe = False def __init__(self, message=None, **kwargs): @@ -183,8 +184,9 @@ logged with warning level. """ def __init__(self, *args, **kwargs): - self._exceptions = [] - self._repr = None + self._exceptions: List[tuple] = [] + self._repr: Optional[str] = None + self._exc_msg_args = [] super(ExceptionChainer, self).__init__(*args, **kwargs) def __repr__(self): @@ -199,21 +201,24 @@ __str__ = __repr__ - def __nonzero__(self): + def __nonzero__(self) -> bool: # We want to be able to do boolean checks on the exception return bool(self._exceptions) __bool__ = __nonzero__ # For Python 3 - def add_exception(self, exc_type, exc_val, exc_tb): + def add_exception(self, exc_type, exc_val, exc_tb) -> None: # Clear the representation cache self._repr = None self._exceptions.append((exc_type, exc_val, exc_tb)) - def context(self, catch_exception, msg='', *msg_args): + def context(self, + catch_exception: bool, + msg: str = '', + *msg_args: Iterable): self._catch_exception = catch_exception self._exc_msg = msg - self._exc_msg_args = msg_args + self._exc_msg_args = list(msg_args) return self def __enter__(self): diff -Nru python-os-brick-5.1.0/os_brick/initiator/connector.py python-os-brick-5.2.0/os_brick/initiator/connector.py --- python-os-brick-5.1.0/os_brick/initiator/connector.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/initiator/connector.py 2022-02-18 15:37:53.000000000 +0000 @@ -64,6 +64,7 @@ '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 @@ '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 diff -Nru python-os-brick-5.1.0/os_brick/initiator/connectors/lightos.py python-os-brick-5.2.0/os_brick/initiator/connectors/lightos.py --- python-os-brick-5.1.0/os_brick/initiator/connectors/lightos.py 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/initiator/connectors/lightos.py 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,340 @@ +# 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 +import traceback + +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 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 = utils.get_host_nqn() + 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['nqn'] = 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['subsysnqn'] + uuid = connection_info['uuid'] + hostnqn = utils.get_host_nqn() + 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 + """ + # bookkeeping lightos connections - delete connection + if self.message_queue: + self.message_queue.put(('delete', connection_properties)) + uuid = connection_properties['uuid'] + LOG.debug('LIGHTOS: disconnect_volume called for volume %s', uuid) + device_path = self._get_device_by_uuid(uuid) + exc = exception.ExceptionChainer() + try: + if device_path: + self._linuxscsi.flush_device_io(device_path) + except putils.ProcessExecutionError as e: + exc.add_exception(type(e), e, traceback.format_exc()) + if not (force or ignore_errors): + raise + try: + self.dsc_disconnect_volume(connection_properties) + except Exception as e: + exc.add_exception(type(e), e, traceback.format_exc()) + if exc: + if not ignore_errors: + raise exc + + @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 diff -Nru python-os-brick-5.1.0/os_brick/initiator/connectors/nvmeof.py python-os-brick-5.2.0/os_brick/initiator/connectors/nvmeof.py --- python-os-brick-5.1.0/os_brick/initiator/connectors/nvmeof.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/initiator/connectors/nvmeof.py 2022-02-18 15:37:53.000000000 +0000 @@ -28,7 +28,6 @@ from os_brick.initiator.connectors import nvmeof_agent except ImportError: nvmeof_agent = None -from os_brick.privileged import nvmeof as priv_nvme from os_brick.privileged import rootwrap as priv_rootwrap from os_brick import utils @@ -65,11 +64,12 @@ volume_replicas = connection_properties.get('volume_replicas') if not volume_replicas: # compatibility return [] + replica_count = connection_properties.get('replica_count') try: - if volume_replicas and len(volume_replicas) > 1: + if volume_replicas and replica_count > 1: return ['/dev/md/' + connection_properties.get('alias')] - if volume_replicas and len(volume_replicas) == 1: + if volume_replicas and replica_count == 1: return [NVMeOFConnector.get_nvme_device_path( self, volume_replicas[0]['target_nqn'], volume_replicas[0]['vol_uuid'])] @@ -104,7 +104,7 @@ uuid = nvmf._get_host_uuid() suuid = nvmf._get_system_uuid() if cls.nvme_present(): - nqn = nvmf._get_host_nqn() + nqn = utils.get_host_nqn() if uuid: ret['uuid'] = uuid if suuid: @@ -128,16 +128,6 @@ "Process execution error in _get_host_uuid: %s" % str(e)) return None - def _get_host_nqn(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 - def _get_system_uuid(self): # RSD requires system_uuid to let Cinder RSD Driver identify # Nova node for later RSD volume attachment. @@ -445,6 +435,7 @@ """ volume_replicas = connection_properties.get('volume_replicas') + replica_count = connection_properties.get('replica_count') volume_alias = connection_properties.get('alias') if volume_replicas: @@ -463,9 +454,9 @@ raise exception.VolumeDeviceNotFound( device=volume_replicas) - if len(volume_replicas) > 1: + if replica_count > 1: device_path = self._handle_replicated_volume( - host_device_paths, volume_alias, len(volume_replicas)) + host_device_paths, volume_alias, replica_count) else: device_path = self._handle_single_replica( host_device_paths, volume_alias) @@ -485,14 +476,15 @@ force=False, ignore_errors=False): device_path = None volume_replicas = connection_properties.get('volume_replicas') + replica_count = connection_properties.get('replica_count') if device_info and device_info.get('path'): device_path = device_info['path'] elif connection_properties.get('device_path'): device_path = connection_properties['device_path'] - elif volume_replicas and len(volume_replicas) > 1: + elif volume_replicas and replica_count > 1: device_path = '/dev/md/' + connection_properties['alias'] - if volume_replicas and len(volume_replicas) > 1: + if volume_replicas and replica_count > 1: NVMeOFConnector.end_raid(self, device_path) else: if self._get_fs_type(device_path) == 'linux_raid_member': @@ -500,8 +492,9 @@ def _extend_volume_replicated(self, connection_properties): volume_replicas = connection_properties.get('volume_replicas') + replica_count = connection_properties.get('replica_count') - if volume_replicas and len(volume_replicas) > 1: + if volume_replicas and replica_count > 1: device_path = '/dev/md/' + connection_properties['alias'] NVMeOFConnector.run_mdadm( self, ['mdadm', '--grow', '--size', 'max', device_path]) @@ -519,33 +512,19 @@ def _connect_target_volume(self, target_nqn, vol_uuid, portals): try: - host_device_path = NVMeOFConnector.get_nvme_device_path( - self, target_nqn, vol_uuid) + NVMeOFConnector._get_nvme_controller(self, target_nqn) + NVMeOFConnector.rescan(self, target_nqn, vol_uuid) except exception.VolumeDeviceNotFound: - host_device_path = None - - if not host_device_path: - any_connect = NVMeOFConnector.connect_to_portals( - self, target_nqn, portals) - if not any_connect: - LOG.error( - "No successful connections: %(host_devices)s", - {'host_devices': target_nqn}) + 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) - - host_device_path = NVMeOFConnector.get_nvme_device_path( - self, target_nqn, vol_uuid) - if not host_device_path: - LOG.error( - "No accessible volume device: %(host_devices)s", - {'host_devices': target_nqn}) - raise exception.VolumeDeviceNotFound(device=target_nqn) - else: - NVMeOFConnector.rescan(self, target_nqn, vol_uuid) - host_device_path = NVMeOFConnector.get_nvme_device_path( - self, target_nqn, vol_uuid) - - return host_device_path + dev_path = NVMeOFConnector.get_nvme_device_path( + self, target_nqn, vol_uuid) + 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): @@ -592,22 +571,21 @@ raise exception.VolumeDeviceNotFound(device=target_nqn) @staticmethod - @utils.retry(exception.VolumeDeviceNotFound) + @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) - try: - blocks = glob.glob( - '/sys/class/nvme-fabrics/ctl/' + nvme_ctrl + - '/' + nvme_ctrl + 'n*') - for block in blocks: + uuid_paths = glob.glob('/sys/class/block/' + nvme_ctrl + 'n*/uuid') + for uuid_path in uuid_paths: + try: uuid_lines, _err = executor._execute( - 'cat', block + '/uuid', run_as_root=True, + 'cat', uuid_path, run_as_root=True, root_helper=executor._root_helper) if uuid_lines.split('\n')[0] == vol_uuid: - return '/dev/' + block[block.rfind('/') + 1:] - except putils.ProcessExecutionError as e: - LOG.exception(e) - + ignore = len('/uuid') + return '/dev/' + uuid_path[ + uuid_path.rfind('/', 0, -ignore) + 1: -ignore] + except putils.ProcessExecutionError as e: + LOG.exception(e) raise exception.VolumeDeviceNotFound(device=vol_uuid) def _handle_replicated_volume(self, host_device_paths, @@ -780,6 +758,19 @@ LOG.debug('[!] cmd = ' + str(cmd)) NVMeOFConnector.run_mdadm(executor, cmd) + # 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) + LOG.debug("[!] md is_exist = %s", is_exist) + if is_exist: + return + time.sleep(1) + except Exception: + LOG.debug('[!] Exception_wait_raid!') + msg = _("md: /dev/md/%s not found.") % name + LOG.error(msg) + raise exception.NotFound(message=msg) @staticmethod def end_raid(executor, device_path): @@ -788,12 +779,11 @@ for i in range(10): try: cmd_out = NVMeOFConnector.stop_raid( - executor, device_path) + executor, device_path, True) if not cmd_out: break except Exception: - break - time.sleep(1) + time.sleep(1) try: is_exist = os.path.exists(device_path) LOG.debug("[!] is_exist = %s", is_exist) @@ -804,10 +794,10 @@ LOG.debug('[!] Exception_stop_raid!') @staticmethod - def stop_raid(executor, md_path): + def stop_raid(executor, md_path, raise_exception=False): cmd = ['mdadm', '--stop', md_path] LOG.debug("[!] cmd = " + str(cmd)) - cmd_out = NVMeOFConnector.run_mdadm(executor, cmd) + cmd_out = NVMeOFConnector.run_mdadm(executor, cmd, raise_exception) return cmd_out @staticmethod diff -Nru python-os-brick-5.1.0/os_brick/initiator/connectors/scaleio.py python-os-brick-5.2.0/os_brick/initiator/connectors/scaleio.py --- python-os-brick-5.1.0/os_brick/initiator/connectors/scaleio.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/initiator/connectors/scaleio.py 2022-02-18 15:37:53.000000000 +0000 @@ -75,6 +75,8 @@ self.volume_path = None self.iops_limit = None self.bandwidth_limit = None + self.verify_certificate = None + self.certificate_path = None def _get_guid(self): try: @@ -204,7 +206,7 @@ r = requests.get( request, auth=(self.server_username, self.server_token), - verify=False + verify=self._verify_cert() ) r = self._check_response(r, request) @@ -250,7 +252,7 @@ r = requests.get(request, auth=(self.server_username, self.server_token), - verify=False) + verify=self._verify_cert()) r = self._check_response(r, request) @@ -290,7 +292,7 @@ r = requests.get( login_request, auth=(self.server_username, self.server_password), - verify=False + verify=self._verify_cert() ) token = r.json() @@ -301,7 +303,7 @@ if is_get_request: res = requests.get(request, auth=(self.server_username, token), - verify=False) + verify=self._verify_cert()) else: headers = {'content-type': 'application/json'} res = requests.post( @@ -309,7 +311,7 @@ data=json.dumps(params), headers=headers, auth=(self.server_username, token), - verify=False + verify=self._verify_cert() ) self.server_token = token @@ -317,6 +319,12 @@ return response + def _verify_cert(self): + verify_cert = self.verify_certificate + if self.verify_certificate and self.certificate_path: + verify_cert = self.certificate_path + return verify_cert + def get_config(self, connection_properties): self.local_sdc_ip = connection_properties['hostIP'] self.volume_name = connection_properties['scaleIO_volname'] @@ -331,6 +339,10 @@ connection_properties) self.iops_limit = connection_properties['iopsLimit'] self.bandwidth_limit = connection_properties['bandwidthLimit'] + self.verify_certificate = ( + connection_properties.get('verify_certificate') + ) + self.certificate_path = connection_properties.get('certificate_path') device_info = {'type': 'block', 'path': self.volume_path} return device_info @@ -382,7 +394,7 @@ data=json.dumps(params), headers=headers, auth=(self.server_username, self.server_token), - verify=False + verify=self._verify_cert() ) r = self._check_response(r, request, False, params) @@ -431,7 +443,7 @@ data=json.dumps(params), headers=headers, auth=(self.server_username, self.server_token), - verify=False + verify=self._verify_cert() ) r = self._check_response(r, request, False, params) if r.status_code != self.OK_STATUS_CODE: @@ -495,7 +507,7 @@ data=json.dumps(params), headers=headers, auth=(self.server_username, self.server_token), - verify=False + verify=self._verify_cert() ) r = self._check_response(r, request, False, params) diff -Nru python-os-brick-5.1.0/os_brick/initiator/__init__.py python-os-brick-5.2.0/os_brick/initiator/__init__.py --- python-os-brick-5.1.0/os_brick/initiator/__init__.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/initiator/__init__.py 2022-02-18 15:37:53.000000000 +0000 @@ -56,3 +56,4 @@ STORPOOL = "STORPOOL" NVME = "NVME" NVMEOF = "NVMEOF" +LIGHTOS = "LIGHTOS" diff -Nru python-os-brick-5.1.0/os_brick/privileged/lightos.py python-os-brick-5.2.0/os_brick/privileged/lightos.py --- python-os-brick-5.1.0/os_brick/privileged/lightos.py 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/privileged/lightos.py 2022-02-18 15:37:53.000000000 +0000 @@ -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) diff -Nru python-os-brick-5.1.0/os_brick/privileged/nvmeof.py python-os-brick-5.2.0/os_brick/privileged/nvmeof.py --- python-os-brick-5.1.0/os_brick/privileged/nvmeof.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/privileged/nvmeof.py 2022-02-18 15:37:53.000000000 +0000 @@ -47,9 +47,16 @@ # This is different from OSError's ENOENT, which is missing nvme # command. This ENOENT is when nvme says there isn't an nqn. except putils.ProcessExecutionError as e: - if e.exit_code != errno.ENOENT: + err_msg = e.stdout[:e.stdout.find('\n')] + show_hostnqn_subcmd_missing = ( + "ERROR: Invalid sub-command".casefold() in err_msg.casefold()) + if show_hostnqn_subcmd_missing: + LOG.debug('Version too old cannot check current hostnqn.') + elif e.exit_code == errno.ENOENT: + LOG.debug('No nqn could be formed from dmi or systemd.') + else: + LOG.debug('Unknown error from nvme show-hostnqn: %s', err_msg) raise - LOG.debug('No nqn could be formed from dmi or systemd.') if not host_nqn: LOG.debug('Generating nqn') diff -Nru python-os-brick-5.1.0/os_brick/tests/initiator/connectors/test_lightos.py python-os-brick-5.2.0/os_brick/tests/initiator/connectors/test_lightos.py --- python-os-brick-5.1.0/os_brick/tests/initiator/connectors/test_lightos.py 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/tests/initiator/connectors/test_lightos.py 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,240 @@ +# 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 +from os_brick import utils + +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( + subsysnqn=FAKE_LIGHTOS_CLUSTER_INFO['subsystemNQN'], + uuid=FAKE_LIGHTOS_CLUSTER_INFO['UUID'], + lightos_nodes=lightos_nodes + ) + + @mock.patch.object(utils, 'get_host_nqn', + 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 = {"nqn": 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(utils, 'get_host_nqn', + 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(utils, 'get_host_nqn', + 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 = {"nqn": 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 = {"nqn": 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) diff -Nru python-os-brick-5.1.0/os_brick/tests/initiator/connectors/test_nvmeof.py python-os-brick-5.2.0/os_brick/tests/initiator/connectors/test_nvmeof.py --- python-os-brick-5.1.0/os_brick/tests/initiator/connectors/test_nvmeof.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/tests/initiator/connectors/test_nvmeof.py 2022-02-18 15:37:53.000000000 +0000 @@ -24,6 +24,7 @@ from os_brick.initiator import linuxscsi from os_brick.privileged import rootwrap as priv_rootwrap from os_brick.tests.initiator import test_connector +from os_brick import utils TARGET_NQN = 'target.nqn' @@ -45,7 +46,8 @@ connection_properties = { 'alias': 'fakealias', 'vol_uuid': 'fakevoluuid', - 'volume_replicas': volume_replicas + 'volume_replicas': volume_replicas, + 'replica_count': 3 } fake_portal = ('fake', 'portal', 'tcp') @@ -137,7 +139,7 @@ @mock.patch.object(nvmeof.NVMeOFConnector, 'nvme_present', return_value=True) - @mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_nqn', + @mock.patch.object(utils, 'get_host_nqn', return_value='fakenqn') @mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid', return_value=None) @@ -151,7 +153,7 @@ self.assertEqual(expected_props, props) @mock.patch.object(nvmeof.NVMeOFConnector, 'nvme_present') - @mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_nqn', autospec=True) + @mock.patch.object(utils, 'get_host_nqn', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid', autospec=True) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid', autospec=True) @@ -177,7 +179,8 @@ mock_get_device_path.return_value = '/dev/nvme1n1' connection_properties = { 'alias': 'fakealias', - 'volume_replicas': [volume_replicas[0]] + 'volume_replicas': [volume_replicas[0]], + 'replica_count': 1 } self.assertEqual(self.connector.get_volume_paths( connection_properties), @@ -257,7 +260,8 @@ connection_properties1 = { 'target_nqn': 'fakenqn', 'vol_uuid': 'fakeuuid', - 'volume_replicas': [volume_replicas[0]] + 'volume_replicas': [volume_replicas[0]], + 'replica_count': 1 } mock_connect_target_volume.return_value = '/dev/nvme0n1' self.assertEqual( @@ -401,6 +405,7 @@ connection_properties = { 'vol_uuid': 'fakeuuid', 'volume_replicas': volume_replicas, + 'replica_count': 3, 'device_path': '/dev/md/md1' } self.connector.disconnect_volume(connection_properties, None) @@ -431,7 +436,8 @@ connection_properties = { 'target_nqn': 'fakenqn', 'vol_uuid': 'fakeuuid', - 'volume_replicas': [volume_replicas[0]] + 'volume_replicas': [volume_replicas[0]], + 'replica_count': 1 } mock_device_path.return_value = '/dev/nvme0n1' mock_device_size.return_value = 100 @@ -487,15 +493,18 @@ ) 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): + self, mock_device_path, mock_rescan, mock_controller): 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_device_path.assert_called_with( self.connector, 'fakenqn', 'fakeuuid') @@ -511,23 +520,23 @@ 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, 'connect_to_portals') - @mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path') def test__connect_target_volume_no_portals_con( - self, mock_device_path, mock_portals): - mock_device_path.return_value = None + self, mock_portals, mock_controller): + mock_controller.side_effect = exception.VolumeDeviceNotFound() mock_portals.return_value = None self.assertRaises(exception.VolumeDeviceNotFound, self.connector._connect_target_volume, 'fakenqn', 'fakeuuid', [fake_portal]) - mock_device_path.assert_called_with( - self.connector, 'fakenqn', 'fakeuuid') + @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') def test__connect_target_volume_new_device_path( - self, mock_device_path, mock_connect_portal): - mock_device_path.side_effect = (None, '/dev/nvme0n1') + self, mock_device_path, mock_connect_portal, mock_controller): + mock_controller.side_effect = exception.VolumeDeviceNotFound() + mock_device_path.return_value = '/dev/nvme0n1' self.assertEqual( self.connector._connect_target_volume( 'fakenqn', 'fakeuuid', [('fake', 'portal', 'tcp')]), @@ -661,8 +670,10 @@ ['mdadm', '--assemble', '--run', '/dev/md/md1', '-o', '/dev/sda'], True) + @mock.patch.object(os.path, 'exists') @mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm') - def test_create_raid_cmd_simple(self, mock_run_mdadm): + def test_create_raid_cmd_simple(self, mock_run_mdadm, mock_os): + mock_os.return_value = True self.assertIsNone(self.connector.create_raid( self.connector, ['/dev/sda'], '1', 'md1', 'name', True)) mock_run_mdadm.assert_called_with( @@ -670,6 +681,7 @@ ['mdadm', '-C', '-o', 'md1', '-R', '-N', 'name', '--level', '1', '--raid-devices=1', '--bitmap=internal', '--homehost=any', '--failfast', '--assume-clean', '/dev/sda']) + mock_os.assert_called_with('/dev/md/name') @mock.patch.object(nvmeof.NVMeOFConnector, 'stop_raid') @mock.patch.object(nvmeof.NVMeOFConnector, 'is_raid_exists') @@ -679,7 +691,7 @@ self.assertIsNone(self.connector.end_raid( self.connector, '/dev/md/md1')) mock_raid_exists.assert_called_with(self.connector, '/dev/md/md1') - mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1') + mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1', True) @mock.patch.object(os.path, 'exists') @mock.patch.object(nvmeof.NVMeOFConnector, 'stop_raid') @@ -691,7 +703,7 @@ self.assertIsNone(self.connector.end_raid( self.connector, '/dev/md/md1')) mock_raid_exists.assert_called_with(self.connector, '/dev/md/md1') - mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1') + mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1', True) mock_os.assert_called_with('/dev/md/md1') @mock.patch.object(os.path, 'exists') @@ -704,16 +716,16 @@ self.assertIsNone(self.connector.end_raid( self.connector, '/dev/md/md1')) mock_raid_exists.assert_called_with(self.connector, '/dev/md/md1') - mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1') + mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1', True) mock_os.assert_called_with('/dev/md/md1') @mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm') def test_stop_raid_simple(self, mock_run_mdadm): mock_run_mdadm.return_value = 'mdadm output' self.assertEqual(self.connector.stop_raid( - self.connector, '/dev/md/md1'), 'mdadm output') + self.connector, '/dev/md/md1', True), 'mdadm output') mock_run_mdadm.assert_called_with( - self.connector, ['mdadm', '--stop', '/dev/md/md1']) + self.connector, ['mdadm', '--stop', '/dev/md/md1'], True) @mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm') def test_remove_raid_simple(self, mock_run_mdadm): @@ -866,10 +878,10 @@ def test_get_nvme_device_path(self, mock_get_nvme_controller, mock_glob, mock_execute): mock_get_nvme_controller.return_value = 'nvme1' - block_dev_path = '/sys/class/nvme-fabrics/ctl/nvme1/nvme1n*' - mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1/nvme1n1']] + block_dev_path = '/sys/class/block/nvme1n*/uuid' + mock_glob.return_value = ['/sys/class/block/nvme1n1/uuid'] mock_execute.return_value = (VOL_UUID + "\n", "") - cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/nvme1n1/uuid'] + 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) @@ -948,21 +960,21 @@ mock_open.assert_called_once_with('/etc/nvme/hostnqn', 'r') self.assertEqual(HOST_NQN, host_nqn) - @mock.patch.object(nvmeof.priv_nvme, 'create_hostnqn') + @mock.patch.object(utils.priv_nvme, 'create_hostnqn') @mock.patch.object(builtins, 'open') def test_get_host_nqn_io_err(self, mock_open, mock_create): mock_create.return_value = mock.sentinel.nqn mock_open.side_effect = IOError() - result = self.connector._get_host_nqn() + result = utils.get_host_nqn() mock_open.assert_called_once_with('/etc/nvme/hostnqn', 'r') mock_create.assert_called_once_with() self.assertEqual(mock.sentinel.nqn, result) - @mock.patch.object(nvmeof.priv_nvme, 'create_hostnqn') + @mock.patch.object(utils.priv_nvme, 'create_hostnqn') @mock.patch.object(builtins, 'open') def test_get_host_nqn_err(self, mock_open, mock_create): mock_open.side_effect = Exception() - result = self.connector._get_host_nqn() + result = utils.get_host_nqn() mock_open.assert_called_once_with('/etc/nvme/hostnqn', 'r') mock_create.assert_not_called() self.assertIsNone(result) diff -Nru python-os-brick-5.1.0/os_brick/tests/initiator/test_connector.py python-os-brick-5.2.0/os_brick/tests/initiator/test_connector.py --- python-os-brick-5.1.0/os_brick/tests/initiator/test_connector.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/tests/initiator/test_connector.py 2022-02-18 15:37:53.000000000 +0000 @@ -28,6 +28,7 @@ from os_brick.initiator import linuxfc from os_brick.privileged import rootwrap as priv_rootwrap from os_brick.tests import base as test_base +from os_brick import utils MY_IP = '10.0.0.1' FAKE_SCSI_WWN = '1234567890' @@ -45,7 +46,7 @@ return_value=None) @mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid', return_value=None) - @mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_nqn', + @mock.patch.object(utils, 'get_host_nqn', return_value=None) @mock.patch.object(iscsi.ISCSIConnector, 'get_initiator', return_value='fakeinitiator') diff -Nru python-os-brick-5.1.0/os_brick/tests/privileged/test_nvmeof.py python-os-brick-5.2.0/os_brick/tests/privileged/test_nvmeof.py --- python-os-brick-5.1.0/os_brick/tests/privileged/test_nvmeof.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/tests/privileged/test_nvmeof.py 2022-02-18 15:37:53.000000000 +0000 @@ -63,7 +63,37 @@ mock_chmod): hostnqn = mock.Mock() mock_exec.side_effect = [ - putils.ProcessExecutionError(exit_code=errno.ENOENT), + putils.ProcessExecutionError(exit_code=errno.ENOENT, + stdout="totally exist sub-command"), + (hostnqn, mock.sentinel.err) + ] + + res = privsep_nvme.create_hostnqn() + + mock_mkdirs.assert_called_once_with('/etc/nvme', + mode=0o755, + exist_ok=True) + self.assertEqual(2, mock_exec.call_count) + mock_exec.assert_has_calls([mock.call('nvme', 'show-hostnqn'), + mock.call('nvme', 'gen-hostnqn')]) + + mock_open.assert_called_once_with('/etc/nvme/hostnqn', 'w') + stripped_hostnqn = hostnqn.strip.return_value + mock_open().write.assert_called_once_with(stripped_hostnqn) + mock_chmod.assert_called_once_with('/etc/nvme/hostnqn', 0o644) + self.assertEqual(stripped_hostnqn, res) + + @mock.patch('os.chmod') + @mock.patch.object(builtins, 'open', new_callable=mock.mock_open) + @mock.patch('os.makedirs') + @mock.patch.object(rootwrap, 'custom_execute') + def test_create_hostnqn_generate_old_nvme_cli(self, mock_exec, mock_mkdirs, + mock_open, mock_chmod): + hostnqn = mock.Mock() + mock_exec.side_effect = [ + putils.ProcessExecutionError( + exit_code=231, + stdout="error: Invalid sub-command\n"), (hostnqn, mock.sentinel.err) ] diff -Nru python-os-brick-5.1.0/os_brick/utils.py python-os-brick-5.2.0/os_brick/utils.py --- python-os-brick-5.1.0/os_brick/utils.py 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/os_brick/utils.py 2022-02-18 15:37:53.000000000 +0000 @@ -16,17 +16,19 @@ import inspect import logging as py_logging import time +from typing import Callable, Tuple, Type, Union # noqa: H301 from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import strutils from os_brick.i18n import _ +from os_brick.privileged import nvmeof as priv_nvme _time_sleep = time.sleep -def _sleep(duration): +def _sleep(secs: float) -> None: """Helper class to make it easier to work around tenacity's sleep calls. Apparently we are all idiots for wanting to test our code here [0], so this @@ -34,7 +36,7 @@ [0] https://github.com/jd/tenacity/issues/25 """ - _time_sleep(duration) + _time_sleep(secs) time.sleep = _sleep @@ -47,17 +49,25 @@ class retry_if_exit_code(tenacity.retry_if_exception): """Retry on ProcessExecutionError specific exit codes.""" - def __init__(self, codes): + def __init__(self, codes: Union[int, Tuple[int, ...]]): self.codes = (codes,) if isinstance(codes, int) else codes super(retry_if_exit_code, self).__init__(self._check_exit_code) - def _check_exit_code(self, exc): - return (exc and isinstance(exc, processutils.ProcessExecutionError) and + def _check_exit_code(self, exc: Type[Exception]) -> bool: + return (bool(exc) and + isinstance(exc, processutils.ProcessExecutionError) and exc.exit_code in self.codes) -def retry(retry_param, interval=1, retries=3, backoff_rate=2, - retry=tenacity.retry_if_exception_type): +def retry(retry_param: Union[None, + Type[Exception], + Tuple[Type[Exception], ...], + int, + Tuple[int, ...]], + interval: float = 1, + retries: int = 3, + backoff_rate: float = 2, + retry: Callable = tenacity.retry_if_exception_type) -> Callable: if retries < 1: raise ValueError(_('Retries must be greater than or ' @@ -82,7 +92,7 @@ return _decorator -def platform_matches(current_platform, connector_platform): +def platform_matches(current_platform: str, connector_platform: str) -> bool: curr_p = current_platform.upper() conn_p = connector_platform.upper() if conn_p == 'ALL': @@ -95,7 +105,7 @@ return False -def os_matches(current_os, connector_os): +def os_matches(current_os: str, connector_os: str) -> bool: curr_os = current_os.upper() conn_os = connector_os.upper() if conn_os == 'ALL': @@ -109,7 +119,7 @@ return False -def merge_dict(dict1, dict2): +def merge_dict(dict1: dict, dict2: dict) -> dict: """Try to safely merge 2 dictionaries.""" if type(dict1) is not dict: raise Exception("dict1 is not a dictionary") @@ -121,7 +131,7 @@ return dict3 -def trace(f): +def trace(f: Callable) -> Callable: """Trace calls to the decorated function. This decorator should always be defined as the outermost decorator so it @@ -189,7 +199,7 @@ return trace_logging_wrapper -def convert_str(text): +def convert_str(text: Union[bytes, str]) -> str: """Convert to native string. Convert bytes and Unicode strings to native strings: @@ -200,3 +210,14 @@ return text.decode('utf-8') else: return text + + +def get_host_nqn(): + 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 diff -Nru python-os-brick-5.1.0/os_brick.egg-info/pbr.json python-os-brick-5.2.0/os_brick.egg-info/pbr.json --- python-os-brick-5.1.0/os_brick.egg-info/pbr.json 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/os_brick.egg-info/pbr.json 2022-02-18 15:38:36.000000000 +0000 @@ -0,0 +1 @@ +{"git_version": "feb62fa", "is_release": true} \ No newline at end of file diff -Nru python-os-brick-5.1.0/os_brick.egg-info/PKG-INFO python-os-brick-5.2.0/os_brick.egg-info/PKG-INFO --- python-os-brick-5.1.0/os_brick.egg-info/PKG-INFO 2021-11-25 15:24:09.000000000 +0000 +++ python-os-brick-5.2.0/os_brick.egg-info/PKG-INFO 2022-02-18 15:38:36.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: os-brick -Version: 5.1.0 +Version: 5.2.0 Summary: OpenStack Cinder brick library for managing local volume attaches Home-page: https://docs.openstack.org/os-brick/ Author: OpenStack @@ -70,4 +70,5 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Requires-Python: >=3.6 diff -Nru python-os-brick-5.1.0/os_brick.egg-info/requires.txt python-os-brick-5.2.0/os_brick.egg-info/requires.txt --- python-os-brick-5.1.0/os_brick.egg-info/requires.txt 2021-11-25 15:24:09.000000000 +0000 +++ python-os-brick-5.2.0/os_brick.egg-info/requires.txt 2022-02-18 15:38:36.000000000 +0000 @@ -1,13 +1,13 @@ -eventlet>=0.30.1 -os-win>=5.4.0 -oslo.concurrency>=4.4.0 -oslo.context>=3.1.1 -oslo.i18n>=5.0.1 -oslo.log>=4.4.0 -oslo.privsep>=2.4.0 -oslo.serialization>=4.1.0 -oslo.service>=2.5.0 -oslo.utils>=4.8.0 -pbr>=5.5.1 +eventlet!=0.32.0,>=0.30.1 +os-win>=5.5.0 +oslo.concurrency>=4.5.0 +oslo.context>=3.4.0 +oslo.i18n>=5.1.0 +oslo.log>=4.6.1 +oslo.privsep>=2.6.2 +oslo.serialization>=4.2.0 +oslo.service>=2.8.0 +oslo.utils>=4.12.1 +pbr>=5.8.0 requests>=2.25.1 tenacity>=6.3.1 diff -Nru python-os-brick-5.1.0/os_brick.egg-info/SOURCES.txt python-os-brick-5.2.0/os_brick.egg-info/SOURCES.txt --- python-os-brick-5.1.0/os_brick.egg-info/SOURCES.txt 2021-11-25 15:24:09.000000000 +0000 +++ python-os-brick-5.2.0/os_brick.egg-info/SOURCES.txt 2022-02-18 15:38:37.000000000 +0000 @@ -38,6 +38,7 @@ os_brick.egg-info/SOURCES.txt os_brick.egg-info/dependency_links.txt os_brick.egg-info/not-zip-safe +os_brick.egg-info/pbr.json os_brick.egg-info/requires.txt os_brick.egg-info/top_level.txt os_brick/caches/__init__.py @@ -66,6 +67,7 @@ os_brick/initiator/connectors/gpfs.py os_brick/initiator/connectors/huawei.py os_brick/initiator/connectors/iscsi.py +os_brick/initiator/connectors/lightos.py os_brick/initiator/connectors/local.py os_brick/initiator/connectors/nvmeof.py os_brick/initiator/connectors/rbd.py @@ -82,6 +84,7 @@ os_brick/local_dev/__init__.py os_brick/local_dev/lvm.py os_brick/privileged/__init__.py +os_brick/privileged/lightos.py os_brick/privileged/nvmeof.py os_brick/privileged/rbd.py os_brick/privileged/rootwrap.py @@ -120,6 +123,7 @@ os_brick/tests/initiator/connectors/test_huawei.py os_brick/tests/initiator/connectors/test_iscsi.py os_brick/tests/initiator/connectors/test_iser.py +os_brick/tests/initiator/connectors/test_lightos.py os_brick/tests/initiator/connectors/test_local.py os_brick/tests/initiator/connectors/test_nvmeof.py os_brick/tests/initiator/connectors/test_rbd.py @@ -152,6 +156,7 @@ releasenotes/notes/add-windows-iscsi-15d6b1392695f978.yaml releasenotes/notes/add-windows-smbfs-d86edaa003130a31.yaml releasenotes/notes/add_custom_keyring_for_rbd_connection-eccbaae9ee5f3491.yaml +releasenotes/notes/bp-lightbits-lightos-clustered-nvmetcp-connector-fd8dfd73330973e9.yaml releasenotes/notes/bug-1722432-2408dab55c903c5b.yaml releasenotes/notes/bug-1823200-scaleio-upgrade-3e83b5c9dd148714.yaml releasenotes/notes/bug-1823200-victoria-b414a1806cba3998.yaml @@ -160,8 +165,11 @@ releasenotes/notes/bug-1884052-798094496dccf23c.yaml releasenotes/notes/bug-1915678-901a6bd24ecede72.yaml releasenotes/notes/bug-1924652-2323f905f62ef8ba.yaml +releasenotes/notes/bug-1929223-powerflex-connector-certificate-validation-cf9ffc98391115d5.yaml +releasenotes/notes/bug-1938870-af85c420d1a108a9.yaml releasenotes/notes/bug-1944474-55c5ebb3a37801aa.yaml releasenotes/notes/bug-1945323-4140f5aff3558082.yaml +releasenotes/notes/bug-nvmeof-connector-support-multipath-kernels-ff6f1f27fdea2c8e.yaml releasenotes/notes/default-timeout-26c838af8b7af9fc.yaml releasenotes/notes/delay-legacy-encryption-provider-name-deprecation-c0d07be3f0d92afd.yaml releasenotes/notes/deprecate-plain-cryptsetup-encryptor-0a279abc0b0d718c.yaml @@ -171,6 +179,7 @@ releasenotes/notes/fc-always-check-single-wwnn-1595689da0eb673b.yaml releasenotes/notes/fc-flush-single-path-22ed6cc7b56a6d9b.yaml releasenotes/notes/fix-fc-scan-too-broad-3c576e1846b7f05f.yaml +releasenotes/notes/fix-generate-hostnqn-in-case-old-nvmecli.yaml releasenotes/notes/fix-multipath-disconnect-819d01e6e981883e.yaml releasenotes/notes/fix-nvme-issues-8dfc15cb691389fe.yaml releasenotes/notes/improve-get_sysfs_wwn-df38ea88cdcdcc94.yaml @@ -204,6 +213,7 @@ releasenotes/notes/ussuri-release-979d709dfa7df068.yaml releasenotes/notes/veritas-hyperscale-connector-fe56cec68b1947cd.yaml releasenotes/notes/vmware-vmdk-connector-19e6999e6cae43cd.yaml +releasenotes/notes/yoga-known-issues-f1248af0e328d63e.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst diff -Nru python-os-brick-5.1.0/PKG-INFO python-os-brick-5.2.0/PKG-INFO --- python-os-brick-5.1.0/PKG-INFO 2021-11-25 15:24:09.261075700 +0000 +++ python-os-brick-5.2.0/PKG-INFO 2022-02-18 15:38:37.087080500 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: os-brick -Version: 5.1.0 +Version: 5.2.0 Summary: OpenStack Cinder brick library for managing local volume attaches Home-page: https://docs.openstack.org/os-brick/ Author: OpenStack @@ -70,4 +70,5 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Requires-Python: >=3.6 diff -Nru python-os-brick-5.1.0/releasenotes/notes/bp-lightbits-lightos-clustered-nvmetcp-connector-fd8dfd73330973e9.yaml python-os-brick-5.2.0/releasenotes/notes/bp-lightbits-lightos-clustered-nvmetcp-connector-fd8dfd73330973e9.yaml --- python-os-brick-5.1.0/releasenotes/notes/bp-lightbits-lightos-clustered-nvmetcp-connector-fd8dfd73330973e9.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/releasenotes/notes/bp-lightbits-lightos-clustered-nvmetcp-connector-fd8dfd73330973e9.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,10 @@ +--- +features: + - | + Lightbits LightOS connector: new os-brick connector for + Lightbits(TM) LightOS(R). Lightbits Labs + (https://www.lightbitslabs.com) LightOS is software-defined, cloud + native, high-performance, clustered scale-out and redundant + NVMe/TCP storage that performs like local NVMe flash. This + connector requires the Lightbits discovery-client, available from + Lightbits Labs. diff -Nru python-os-brick-5.1.0/releasenotes/notes/bug-1929223-powerflex-connector-certificate-validation-cf9ffc98391115d5.yaml python-os-brick-5.2.0/releasenotes/notes/bug-1929223-powerflex-connector-certificate-validation-cf9ffc98391115d5.yaml --- python-os-brick-5.1.0/releasenotes/notes/bug-1929223-powerflex-connector-certificate-validation-cf9ffc98391115d5.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/releasenotes/notes/bug-1929223-powerflex-connector-certificate-validation-cf9ffc98391115d5.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,5 @@ +--- +fixes: + - | + `Bug #1929223 `_: Fixed + HTTPS certificate validation was disabled in PowerFlex connector. diff -Nru python-os-brick-5.1.0/releasenotes/notes/bug-1938870-af85c420d1a108a9.yaml python-os-brick-5.2.0/releasenotes/notes/bug-1938870-af85c420d1a108a9.yaml --- python-os-brick-5.1.0/releasenotes/notes/bug-1938870-af85c420d1a108a9.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/releasenotes/notes/bug-1938870-af85c420d1a108a9.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,5 @@ +--- +fixes: + - | + `Bug #1938870 `_: Fixed + KumoScale Driver replicated volume missing portals attaches without raid. diff -Nru python-os-brick-5.1.0/releasenotes/notes/bug-nvmeof-connector-support-multipath-kernels-ff6f1f27fdea2c8e.yaml python-os-brick-5.2.0/releasenotes/notes/bug-nvmeof-connector-support-multipath-kernels-ff6f1f27fdea2c8e.yaml --- python-os-brick-5.1.0/releasenotes/notes/bug-nvmeof-connector-support-multipath-kernels-ff6f1f27fdea2c8e.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/releasenotes/notes/bug-nvmeof-connector-support-multipath-kernels-ff6f1f27fdea2c8e.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,7 @@ +--- +fixes: + - | + NVMe-oF connector `bug #1943615 + `_: Fixed get nvme + device failing on kernels with multipath enabled by using the generic + form ``/sys/class/block/n*`` for finding nvme devices. diff -Nru python-os-brick-5.1.0/releasenotes/notes/fix-generate-hostnqn-in-case-old-nvmecli.yaml python-os-brick-5.2.0/releasenotes/notes/fix-generate-hostnqn-in-case-old-nvmecli.yaml --- python-os-brick-5.1.0/releasenotes/notes/fix-generate-hostnqn-in-case-old-nvmecli.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/releasenotes/notes/fix-generate-hostnqn-in-case-old-nvmecli.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,8 @@ +--- +fixes: + - | + The nvmeof connector relies on nvme-cli to query the host-nqn. + Versions of nvme-cli < 1.10 do not have the 'show-hostnqn' command, + which could cause the connector to fail to generate the hostnqn. + Fixed the connector to either get or generate the hostnqn with versions + of nvme-cli<1.10 that do not have 'show-hostnqn'. diff -Nru python-os-brick-5.1.0/releasenotes/notes/yoga-known-issues-f1248af0e328d63e.yaml python-os-brick-5.2.0/releasenotes/notes/yoga-known-issues-f1248af0e328d63e.yaml --- python-os-brick-5.1.0/releasenotes/notes/yoga-known-issues-f1248af0e328d63e.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-os-brick-5.2.0/releasenotes/notes/yoga-known-issues-f1248af0e328d63e.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -0,0 +1,10 @@ +--- +prelude: > + Welcome to the Yoga release of the os-brick library. +issues: + - | + At release time, we became aware that recent changes to the + nvmeof connector were tested only in configurations where the + NVMe storage backend supports namespace AER. One issue arising + from this is being tracked by `Bug #1961102 + `_. diff -Nru python-os-brick-5.1.0/requirements.txt python-os-brick-5.2.0/requirements.txt --- python-os-brick-5.1.0/requirements.txt 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/requirements.txt 2022-02-18 15:37:53.000000000 +0000 @@ -2,16 +2,16 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=5.5.1 # Apache-2.0 -eventlet>=0.30.1 # MIT -oslo.concurrency>=4.4.0 # Apache-2.0 -oslo.context>=3.1.1 # Apache-2.0 -oslo.log>=4.4.0 # Apache-2.0 -oslo.i18n>=5.0.1 # Apache-2.0 -oslo.privsep>=2.4.0 # Apache-2.0 -oslo.serialization>=4.1.0 # Apache-2.0 -oslo.service>=2.5.0 # Apache-2.0 -oslo.utils>=4.8.0 # Apache-2.0 +pbr>=5.8.0 # Apache-2.0 +eventlet>=0.30.1,!=0.32.0 # MIT +oslo.concurrency>=4.5.0 # Apache-2.0 +oslo.context>=3.4.0 # Apache-2.0 +oslo.log>=4.6.1 # Apache-2.0 +oslo.i18n>=5.1.0 # Apache-2.0 +oslo.privsep>=2.6.2 # Apache-2.0 +oslo.serialization>=4.2.0 # Apache-2.0 +oslo.service>=2.8.0 # Apache-2.0 +oslo.utils>=4.12.1 # Apache-2.0 requests>=2.25.1 # Apache-2.0 tenacity>=6.3.1 # Apache-2.0 -os-win>=5.4.0 # Apache-2.0 +os-win>=5.5.0 # Apache-2.0 diff -Nru python-os-brick-5.1.0/setup.cfg python-os-brick-5.2.0/setup.cfg --- python-os-brick-5.1.0/setup.cfg 2021-11-25 15:24:09.261075700 +0000 +++ python-os-brick-5.2.0/setup.cfg 2022-02-18 15:38:37.091080700 +0000 @@ -20,6 +20,7 @@ Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 [files] packages = diff -Nru python-os-brick-5.1.0/test-requirements.txt python-os-brick-5.2.0/test-requirements.txt --- python-os-brick-5.1.0/test-requirements.txt 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/test-requirements.txt 2022-02-18 15:37:53.000000000 +0000 @@ -6,12 +6,12 @@ flake8-import-order # LGPLv3 coverage>=5.5 # Apache-2.0 ddt>=1.4.1 # MIT -oslotest>=4.4.1 # Apache-2.0 +oslotest>=4.5.0 # Apache-2.0 testscenarios>=0.5.0 # Apache-2.0/BSD testtools>=2.4.0 # MIT -stestr>=3.1.0 # Apache-2.0 -oslo.vmware>=3.8.0 # Apache-2.0 -castellan>=3.7.0 # Apache-2.0 +stestr>=3.2.1 # Apache-2.0 +oslo.vmware>=3.10.0 # Apache-2.0 +castellan>=3.10.0 # Apache-2.0 pycodestyle==2.6.0 # MIT doc8>=0.8.1 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD diff -Nru python-os-brick-5.1.0/.zuul.yaml python-os-brick-5.2.0/.zuul.yaml --- python-os-brick-5.1.0/.zuul.yaml 2021-11-25 15:23:34.000000000 +0000 +++ python-os-brick-5.2.0/.zuul.yaml 2022-02-18 15:37:53.000000000 +0000 @@ -26,14 +26,12 @@ name: os-brick-code-coverage parent: openstack-tox-cover timeout: 2400 - irrelevant-files: + irrelevant-files: &non-code-files - ^(test-|)requirements.txt$ - ^.*\.rst$ - ^doc/.*$ - ^releasenotes/.*$ - - ^setup.cfg$ - ^tools/.*$ - - ^tox.ini$ - job: name: os-brick-src-devstack-plugin-ceph @@ -44,6 +42,10 @@ parent: cinder-plugin-ceph-tempest required-projects: - opendev.org/openstack/os-brick + irrelevant-files: &doc-files + - ^.*\.rst$ + - ^doc/.*$ + - ^releasenotes/.*$ - job: name: os-brick-src-tempest-lvm-lio-barbican @@ -55,15 +57,18 @@ * legacy-tempest-dsvm-full-lio-src-os-brick required-projects: - opendev.org/openstack/os-brick + irrelevant-files: *doc-files - job: name: os-brick-src-tempest-nfs parent: devstack-plugin-nfs-tempest-full required-projects: - opendev.org/openstack/os-brick + irrelevant-files: *doc-files - job: name: os-brick-mypy parent: openstack-tox vars: tox_envlist: mypy + irrelevant-files: *non-code-files