From 3c419c48769f3aab4d08629b4f70ee8b7e0f881a Mon Sep 17 00:00:00 2001 From: Gil Bregman Date: Mon, 30 Dec 2024 11:14:29 +0200 Subject: [PATCH] Verify python source files with flake8. Fixes #994 Signed-off-by: Gil Bregman --- .github/workflows/build-container.yml | 6 + Makefile | 4 + control/cephutils.py | 62 +- control/cli.py | 618 ++++----- control/config.py | 5 +- control/discovery.py | 285 ++-- control/grpc.py | 1669 ++++++++++++++---------- control/prometheus.py | 3 +- control/rebalance.py | 128 +- control/server.py | 76 +- control/state.py | 136 +- control/utils.py | 65 +- tests/conftest.py | 2 - tests/test_cli.py | 219 ++-- tests/test_cli_change_keys.py | 2 + tests/test_cli_change_lb.py | 86 +- tests/test_cli_change_ns_visibility.py | 13 +- tests/test_dhchap.py | 49 +- tests/test_grpc.py | 18 +- tests/test_log_files.py | 18 +- tests/test_multi_gateway.py | 22 +- tests/test_nsid.py | 16 +- tests/test_omap_lock.py | 87 +- tests/test_psk.py | 42 +- tests/test_server.py | 25 +- tests/test_state.py | 6 +- tox.ini | 4 + 27 files changed, 2032 insertions(+), 1634 deletions(-) create mode 100644 tox.ini diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index 3d7bead8..7bb5e5a3 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -31,6 +31,12 @@ jobs: with: submodules: recursive + - name: Install Flake8 + run: pip install flake8 + + - name: Verify Python source files + run: make verify + - name: Build container images - spdk run: make build SVC="spdk" SPDK_TARGET_ARCH=x86-64-v2 diff --git a/Makefile b/Makefile index 0b44bdda..ede2adc8 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,10 @@ include mk/autohelp.mk .DEFAULT_GOAL := all all: setup $(ALL) +verify: ## Run Python source files through flake8 + @echo Verifying Python source files + flake8 control/*.py tests/*.py + setup: ## Configure huge-pages (requires sudo/root password) @echo Setup core dump pattern as /tmp/coredump/core.* diff --git a/control/cephutils.py b/control/cephutils.py index 7452a008..be0be408 100644 --- a/control/cephutils.py +++ b/control/cephutils.py @@ -13,6 +13,7 @@ import json from .utils import GatewayLogger + class CephUtils: """Miscellaneous functions which connect to Ceph """ @@ -27,11 +28,12 @@ def __init__(self, config): self.last_sent = time.time() def execute_ceph_monitor_command(self, cmd): - self.logger.debug(f"Execute monitor command: {cmd}") - with rados.Rados(conffile=self.ceph_conf, rados_id=self.rados_id) as cluster: + self.logger.debug(f"Execute monitor command: {cmd}") + with rados.Rados(conffile=self.ceph_conf, rados_id=self.rados_id) as cluster: rply = cluster.mon_command(cmd, b'') self.logger.debug(f"Monitor reply: {rply}") return rply + def get_gw_id_owner_ana_group(self, pool, group, anagrp): str = '{' + f'"prefix":"nvme-gw show", "pool":"{pool}", "group":"{group}"' + '}' self.logger.debug(f"nvme-show string: {str}") @@ -45,23 +47,23 @@ def get_gw_id_owner_ana_group(self, pool, group, anagrp): comp_str = f"{anagrp}: ACTIVE" for gateway in data["Created Gateways:"]: if comp_str in gateway["ana states"]: - gw_id = gateway["gw-id"] - self.logger.debug(f"found gw owner of anagrp {anagrp}: gw {gw_id}") - break + gw_id = gateway["gw-id"] + self.logger.debug(f"found gw owner of anagrp {anagrp}: gw {gw_id}") + break return gw_id def is_rebalance_supported(self): - return self.rebalance_supported + return self.rebalance_supported def get_rebalance_ana_group(self): - return self.rebalance_ana_group + return self.rebalance_ana_group def get_number_created_gateways(self, pool, group): now = time.time() - if (now - self.last_sent) < 10 and self.anagroup_list : - self.logger.info(f"Caching response of the monitor: {self.anagroup_list}") - return self.anagroup_list - else : + if (now - self.last_sent) < 10 and self.anagroup_list: + self.logger.info(f"Caching response of the monitor: {self.anagroup_list}") + return self.anagroup_list + else: try: self.anagroup_list = [] self.last_sent = now @@ -76,12 +78,12 @@ def get_number_created_gateways(self, pool, group): self.rebalance_supported = True self.rebalance_ana_group = data.get("rebalance_ana_group", None) self.logger.debug(f"Rebalance ana_group: {self.rebalance_ana_group}") - else : + else: self.rebalance_supported = False pos = conv_str.find("[") if pos != -1: - new_str = conv_str[pos + len("[") :] - pos = new_str.find("]") + new_str = conv_str[pos + len("["):] + pos = new_str.find("]") new_str = new_str[: pos].strip() int_str_list = new_str.split(' ') self.logger.debug(f"new_str : {new_str}") @@ -92,7 +94,7 @@ def get_number_created_gateways(self, pool, group): self.logger.warning("GWs not found") except Exception: - self.logger.exception(f"Failure get number created gateways:") + self.logger.exception("Failure get number created gateways") self.anagroup_list = [] return self.anagroup_list @@ -104,7 +106,7 @@ def fetch_and_display_ceph_version(self): ceph_ver = ceph_ver.removeprefix("ceph version ") self.logger.info(f"Connected to Ceph with version \"{ceph_ver}\"") except Exception: - self.logger.exception(f"Failure fetching Ceph version:") + self.logger.exception("Failure fetching Ceph version") pass def fetch_ceph_fsid(self) -> str: @@ -113,7 +115,7 @@ def fetch_ceph_fsid(self) -> str: with rados.Rados(conffile=self.ceph_conf, rados_id=self.rados_id) as cluster: fsid = cluster.get_fsid() except Exception: - self.logger.exception(f"Failure fetching Ceph fsid:") + self.logger.exception("Failure fetching Ceph fsid") return fsid @@ -130,24 +132,24 @@ def pool_exists(self, pool) -> bool: def service_daemon_register(self, cluster, metadata): try: - if cluster: # rados client + if cluster: # rados client daemon_name = metadata['id'] cluster.service_daemon_register("nvmeof", daemon_name, metadata) self.logger.info(f"Registered {daemon_name} to service_map!") except Exception: - self.logger.exception(f"Can't register daemon to service_map!") + self.logger.exception("Can't register daemon to service_map!") def service_daemon_update(self, cluster, status_buffer): try: if cluster and status_buffer: cluster.service_daemon_update(status_buffer) except Exception: - self.logger.exception(f"Can't update daemon status to service_map!") + self.logger.exception("Can't update daemon status to service_map!") def create_image(self, pool_name, image_name, size) -> bool: # Check for pool existence in advance as we don't create it if it's not there if not self.pool_exists(pool_name): - raise rbd.ImageNotFound(f"Pool {pool_name} doesn't exist", errno = errno.ENODEV) + raise rbd.ImageNotFound(f"Pool {pool_name} doesn't exist", errno=errno.ENODEV) image_exists = False try: @@ -160,7 +162,7 @@ def create_image(self, pool_name, image_name, size) -> bool: if image_exists: if image_size != size: raise rbd.ImageExists(f"Image {pool_name}/{image_name} already exists with a size of {image_size} bytes which differs from the requested size of {size} bytes", - errno = errno.EEXIST) + errno=errno.EEXIST) return False # Image exists with an idetical size, there is nothing to do here with rados.Rados(conffile=self.ceph_conf, rados_id=self.rados_id) as cluster: @@ -168,29 +170,29 @@ def create_image(self, pool_name, image_name, size) -> bool: rbd_inst = rbd.RBD() try: rbd_inst.create(ioctx, image_name, size) - except rbd.ImageExists as ex: + except rbd.ImageExists: self.logger.exception(f"Image {pool_name}/{image_name} was created just now") raise rbd.ImageExists(f"Image {pool_name}/{image_name} was just created by someone else, please retry", - errno = errno.EAGAIN) - except Exception as ex: + errno=errno.EAGAIN) + except Exception: self.logger.exception(f"Can't create image {pool_name}/{image_name}") - raise ex + raise return True def get_image_size(self, pool_name, image_name) -> int: image_size = 0 if not self.pool_exists(pool_name): - raise rbd.ImageNotFound(f"Pool {pool_name} doesn't exist", errno = errno.ENODEV) + raise rbd.ImageNotFound(f"Pool {pool_name} doesn't exist", errno=errno.ENODEV) with rados.Rados(conffile=self.ceph_conf, rados_id=self.rados_id) as cluster: with cluster.open_ioctx(pool_name) as ioctx: - rbd_inst = rbd.RBD() + rbd.RBD() try: with rbd.Image(ioctx, image_name) as img: image_size = img.size() except rbd.ImageNotFound: - raise rbd.ImageNotFound(f"Image {pool_name}/{image_name} doesn't exist", errno = errno.ENODEV) + raise rbd.ImageNotFound(f"Image {pool_name}/{image_name} doesn't exist", errno=errno.ENODEV) except Exception as ex: self.logger.exception(f"Error while trying to get the size of image {pool_name}/{image_name}") raise ex @@ -205,6 +207,6 @@ def get_rbd_exception_details(self, ex): if msg.startswith("["): pos = msg.find("]") if pos >= 0: - msg = msg[pos + 1 :].strip() + msg = msg[pos + 1:].strip() ex_details = (ex.errno, msg) return ex_details diff --git a/control/cli.py b/control/cli.py index 12284baa..8274ee70 100644 --- a/control/cli.py +++ b/control/cli.py @@ -25,16 +25,19 @@ from .utils import GatewayUtils from .utils import GatewayEnumUtils -BASE_GATEWAY_VERSION="1.1.0" +BASE_GATEWAY_VERSION = "1.1.0" + def errprint(msg): - print(msg, file = sys.stderr) + print(msg, file=sys.stderr) + def argument(*name_or_flags, **kwargs): """Helper function to format arguments for argparse command decorator.""" return (list(name_or_flags), kwargs) -def get_enum_keys_list(e_type, include_first = True): + +def get_enum_keys_list(e_type, include_first=True): k_list = [] for k in e_type.keys(): k_list.append(k.lower()) @@ -44,6 +47,7 @@ def get_enum_keys_list(e_type, include_first = True): return k_list + def break_string(s, delim, count): start = 0 for i in range(count): @@ -53,12 +57,13 @@ def break_string(s, delim, count): start = ind + 1 return s[0:ind + 1] + "\n" + s[ind + 1:] + class ErrorCatchingArgumentParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): self.logger = logging.getLogger(__name__) super(ErrorCatchingArgumentParser, self).__init__(*args, **kwargs) - def exit(self, status = 0, message = None): + def exit(self, status=0, message=None): if status != 0: if message: self.logger.error(message) @@ -73,6 +78,7 @@ def error(self, message): self.logger.error(f"error: {message}") exit(2) + class Parser: """Class to simplify creation of client CLI. @@ -321,9 +327,9 @@ def gw_get_info(self): gw_info = self.stub.get_gateway_info(req) if gw_info.status == 0: base_ver = self.parse_version_string(BASE_GATEWAY_VERSION) - assert base_ver != None + assert base_ver is not None gw_ver = self.parse_version_string(gw_info.version) - if gw_ver == None: + if gw_ver is None: gw_info.status = errno.EINVAL gw_info.bool_status = False gw_info.error_message = f"Can't parse gateway version \"{gw_info.version}\"." @@ -340,7 +346,7 @@ def gw_info(self, args): try: gw_info = self.gw_get_info() except Exception as ex: - gw_info = pb2.gateway_info(status = errno.EINVAL, error_message = f"Failure getting gateway's information:\n{ex}") + gw_info = pb2.gateway_info(status=errno.EINVAL, error_message=f"Failure getting gateway's information:\n{ex}") if args.format == "text" or args.format == "plain": if gw_info.status == 0: @@ -368,19 +374,17 @@ def gw_info(self, args): if gw_info.spdk_version: out_func(f"SPDK version: {gw_info.spdk_version}") if not gw_info.bool_status: - err_func(f"Getting gateway's information returned status mismatch") + err_func("Getting gateway's information returned status mismatch") else: - err_func(f"{gw_info.error_message}") + err_func(gw_info.error_message) if gw_info.bool_status: - err_func(f"Getting gateway's information returned status mismatch") + err_func("Getting gateway's information returned status mismatch") elif args.format == "json" or args.format == "yaml": - gw_info_str = json_format.MessageToJson( - gw_info, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + gw_info_str = json_format.MessageToJson(gw_info, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{gw_info_str}") + out_func(gw_info_str) elif args.format == "yaml": obj = json.loads(gw_info_str) out_func(yaml.dump(obj)) @@ -398,7 +402,7 @@ def gw_version(self, args): try: gw_info = self.gw_get_info() except Exception as ex: - gw_info = pb2.gateway_info(status = errno.EINVAL, error_message = f"Failure getting gateway's version:\n{ex}") + gw_info = pb2.gateway_info(status=errno.EINVAL, error_message=f"Failure getting gateway's version:\n{ex}") if args.format == "text" or args.format == "plain": if gw_info.status == 0: @@ -431,7 +435,7 @@ def gw_get_log_level(self, args): try: ret = self.stub.get_gateway_log_level(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure getting gateway log level:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure getting gateway log level:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -440,12 +444,11 @@ def gw_get_log_level(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - out_log_level = json_format.MessageToJson(ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + out_log_level = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{out_log_level}") + out_func(out_log_level) elif args.format == "yaml": obj = json.loads(out_log_level) out_func(yaml.dump(obj)) @@ -473,7 +476,7 @@ def gw_set_log_level(self, args): try: ret = self.stub.set_gateway_log_level(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure setting gateway log level:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure setting gateway log level:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -481,13 +484,11 @@ def gw_set_log_level(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -501,10 +502,10 @@ def gw_set_log_level(self, args): type=str, choices=get_enum_keys_list(pb2.GwLogLevel, False)), ] gw_actions = [] - gw_actions.append({"name" : "version", "args" : [], "help" : "Display gateway's version"}) - gw_actions.append({"name" : "info", "args" : [], "help" : "Display gateway's information"}) - gw_actions.append({"name" : "get_log_level", "args" : [], "help" : "Get gateway's log level"}) - gw_actions.append({"name" : "set_log_level", "args" : gw_set_log_level_args, "help" : "Set gateway's log level"}) + gw_actions.append({"name": "version", "args": [], "help": "Display gateway's version"}) + gw_actions.append({"name": "info", "args": [], "help": "Display gateway's information"}) + gw_actions.append({"name": "get_log_level", "args": [], "help": "Get gateway's log level"}) + gw_actions.append({"name": "set_log_level", "args": gw_set_log_level_args, "help": "Set gateway's log level"}) gw_choices = get_actions(gw_actions) @cli.cmd(gw_actions) @@ -531,21 +532,19 @@ def spdk_log_level_disable(self, args): try: ret = self.stub.disable_spdk_nvmf_logs(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure disabling SPDK nvmf log flags:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure disabling SPDK nvmf log flags:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: - out_func(f"Disable SPDK nvmf log flags: Successful") + out_func("Disable SPDK nvmf log flags: Successful") else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -565,7 +564,7 @@ def spdk_log_level_get(self, args): try: ret = self.stub.get_spdk_nvmf_log_flags_and_level(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure getting SPDK log levels and nvmf log flags:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure getting SPDK log levels and nvmf log flags:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -579,12 +578,10 @@ def spdk_log_level_get(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - out_log_level = json_format.MessageToJson(ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + out_log_level = json_format.MessageToJson(ret, indent=4, including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{out_log_level}") + out_func(out_log_level) elif args.format == "yaml": obj = json.loads(out_log_level) out_func(yaml.dump(obj)) @@ -616,21 +613,19 @@ def spdk_log_level_set(self, args): try: ret = self.stub.set_spdk_nvmf_logs(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure setting SPDK log levels and nvmf log flags:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure setting SPDK log levels and nvmf log flags:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: - out_func(f"Set SPDK log levels and nvmf log flags: Successful") + out_func("Set SPDK log levels and nvmf log flags: Successful") else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -650,9 +645,9 @@ def spdk_log_level_set(self, args): ] spdk_log_disable_args = [] spdk_log_actions = [] - spdk_log_actions.append({"name" : "get", "args" : spdk_log_get_args, "help" : "Get SPDK log levels and nvmf log flags"}) - spdk_log_actions.append({"name" : "set", "args" : spdk_log_set_args, "help" : "Set SPDK log levels and nvmf log flags"}) - spdk_log_actions.append({"name" : "disable", "args" : spdk_log_disable_args, "help" : "Disable SPDK nvmf log flags"}) + spdk_log_actions.append({"name": "get", "args": spdk_log_get_args, "help": "Get SPDK log levels and nvmf log flags"}) + spdk_log_actions.append({"name": "set", "args": spdk_log_set_args, "help": "Set SPDK log levels and nvmf log flags"}) + spdk_log_actions.append({"name": "disable", "args": spdk_log_disable_args, "help": "Disable SPDK nvmf log flags"}) spdk_log_choices = get_actions(spdk_log_actions) @cli.cmd(spdk_log_actions) @@ -671,28 +666,28 @@ def subsystem_add(self, args): """Create a subsystem""" out_func, err_func = self.get_output_functions(args) - if args.max_namespaces != None and args.max_namespaces <= 0: + if args.max_namespaces is not None and args.max_namespaces <= 0: self.cli.parser.error("--max-namespaces value must be positive") if args.subsystem == GatewayUtils.DISCOVERY_NQN: self.cli.parser.error("Can't add a discovery subsystem") req = pb2.create_subsystem_req(subsystem_nqn=args.subsystem, - serial_number=args.serial_number, - max_namespaces=args.max_namespaces, - enable_ha=True, - no_group_append=args.no_group_append, - dhchap_key=args.dhchap_key) + serial_number=args.serial_number, + max_namespaces=args.max_namespaces, + enable_ha=True, + no_group_append=args.no_group_append, + dhchap_key=args.dhchap_key) try: ret = self.stub.create_subsystem(req) except Exception as ex: - ret = pb2.subsys_status(status = errno.EINVAL, error_message = f"Failure adding subsystem {args.subsystem}:\n{ex}", - nqn = args.subsystem) + ret = pb2.subsys_status(status=errno.EINVAL, error_message=f"Failure adding subsystem {args.subsystem}:\n{ex}", + nqn=args.subsystem) new_nqn = "" try: new_nqn = ret.nqn except Exception: # In case of an old gateway the returned value wouldn't have the nqn field - pass + pass if not new_nqn: new_nqn = args.subsystem @@ -702,13 +697,11 @@ def subsystem_add(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -730,7 +723,7 @@ def subsystem_del(self, args): try: ret = self.stub.delete_subsystem(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure deleting subsystem {args.subsystem}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure deleting subsystem {args.subsystem}:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -738,13 +731,11 @@ def subsystem_del(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -764,17 +755,17 @@ def subsystem_list(self, args): try: subsystems = self.stub.list_subsystems(pb2.list_subsystems_req(subsystem_nqn=args.subsystem, serial_number=args.serial_number)) except Exception as ex: - subsystems = pb2.subsystems_info_cli(status = errno.EINVAL, error_message = f"Failure listing subsystems:\n{ex}") + subsystems = pb2.subsystems_info_cli(status=errno.EINVAL, error_message=f"Failure listing subsystems:\n{ex}") if args.format == "text" or args.format == "plain": if subsystems.status == 0: subsys_list = [] for s in subsystems.subsystems: if args.subsystem and args.subsystem != s.nqn: - err_func("Failure listing subsystem {args.subsystem}: Got subsystem {s.nqn} instead") + err_func(f"Failure listing subsystem {args.subsystem}: Got subsystem {s.nqn} instead") return errno.ENODEV if args.serial_number and args.serial_number != s.serial_number: - err_func("Failure listing subsystem with serial number {args.serial_number}: Got serial number {s.serial_number} instead") + err_func(f"Failure listing subsystem with serial number {args.serial_number}: Got serial number {s.serial_number} instead") return errno.ENODEV ctrls_id = f"{s.min_cntlid}-{s.max_cntlid}" has_dhchap = "Yes" if s.has_dhchap_key else "No" @@ -787,9 +778,9 @@ def subsystem_list(self, args): else: table_format = "plain" subsys_out = tabulate(subsys_list, - headers = ["Subtype", "NQN", "Serial\nNumber", "Controller IDs", - "Namespace\nCount", "Max\nNamespaces", "Allow\nAny Host", "DHCHAP\nKey"], - tablefmt=table_format) + headers=["Subtype", "NQN", "Serial\nNumber", "Controller IDs", + "Namespace\nCount", "Max\nNamespaces", "Allow\nAny Host", "DHCHAP\nKey"], + tablefmt=table_format) prefix = "Subsystems" if args.subsystem: prefix = f"Subsystem {args.subsystem}" @@ -802,17 +793,15 @@ def subsystem_list(self, args): elif args.serial_number: out_func(f"No subsystem with serial number {args.serial_number}") else: - out_func(f"No subsystems") + out_func("No subsystems") else: err_func(f"{subsystems.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - subsystems, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(subsystems, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -833,7 +822,7 @@ def subsystem_change_key(self, args): ret = self.stub.change_subsystem_key(req) except Exception as ex: errmsg = f"Failure changing key for subsystem {args.subsystem}" - ret = pb2.req_status(status = errno.EINVAL, error_message = f"{errmsg}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"{errmsg}:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -841,13 +830,11 @@ def subsystem_change_key(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -878,10 +865,10 @@ def subsystem_change_key(self, args): argument("--dhchap-key", "-k", help="Subsystem DH-HMAC-CHAP key", required=False), ] subsystem_actions = [] - subsystem_actions.append({"name" : "add", "args" : subsys_add_args, "help" : "Create a subsystem"}) - subsystem_actions.append({"name" : "del", "args" : subsys_del_args, "help" : "Delete a subsystem"}) - subsystem_actions.append({"name" : "list", "args" : subsys_list_args, "help" : "List subsystems"}) - subsystem_actions.append({"name" : "change_key", "args" : subsys_change_key_args, "help" : "Change subsystem key"}) + subsystem_actions.append({"name": "add", "args": subsys_add_args, "help": "Create a subsystem"}) + subsystem_actions.append({"name": "del", "args": subsys_del_args, "help": "Delete a subsystem"}) + subsystem_actions.append({"name": "list", "args": subsys_list_args, "help": "List subsystems"}) + subsystem_actions.append({"name": "change_key", "args": subsys_change_key_args, "help": "Change subsystem key"}) subsystem_choices = get_actions(subsystem_actions) @cli.cmd(subsystem_actions) @@ -903,7 +890,7 @@ def listener_add(self, args): out_func, err_func = self.get_output_functions(args) - if args.trsvcid == None: + if args.trsvcid is None: args.trsvcid = 4420 elif args.trsvcid <= 0: self.cli.parser.error("trsvcid value must be positive") @@ -929,8 +916,8 @@ def listener_add(self, args): try: ret = self.stub.create_listener(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, - error_message = f"Failure adding {args.subsystem} listener at {traddr}:{args.trsvcid}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, + error_message=f"Failure adding {args.subsystem} listener at {traddr}:{args.trsvcid}:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -938,13 +925,11 @@ def listener_add(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -986,8 +971,8 @@ def listener_del(self, args): try: ret = self.stub.delete_listener(req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, - error_message = f"Failure deleting listener {traddr}:{args.trsvcid} from {args.subsystem}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, + error_message=f"Failure deleting listener {traddr}:{args.trsvcid} from {args.subsystem}:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -996,13 +981,11 @@ def listener_del(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1021,7 +1004,7 @@ def listener_list(self, args): try: listeners_info = self.stub.list_listeners(pb2.list_listeners_req(subsystem=args.subsystem)) except Exception as ex: - listeners_info = pb2.listeners_info(status = errno.EINVAL, error_message = f"Failure listing listeners:\n{ex}", listeners=[]) + listeners_info = pb2.listeners_info(status=errno.EINVAL, error_message=f"Failure listing listeners:\n{ex}", listeners=[]) if args.format == "text" or args.format == "plain": if listeners_info.status == 0: @@ -1037,21 +1020,19 @@ def listener_list(self, args): else: table_format = "plain" listeners_out = tabulate(listeners_list, - headers = ["Host", "Transport", "Address Family", "Address", "Secure"], - tablefmt=table_format) + headers=["Host", "Transport", "Address Family", "Address", "Secure"], + tablefmt=table_format) out_func(f"Listeners for {args.subsystem}:\n{listeners_out}") else: out_func(f"No listeners for {args.subsystem}") else: err_func(f"{listeners_info.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - listeners_info, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(listeners_info, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1082,9 +1063,9 @@ def listener_list(self, args): listener_list_args = listener_common_args + [ ] listener_actions = [] - listener_actions.append({"name" : "add", "args" : listener_add_args, "help" : "Create a listener"}) - listener_actions.append({"name" : "del", "args" : listener_del_args, "help" : "Delete a listener"}) - listener_actions.append({"name" : "list", "args" : listener_list_args, "help" : "List listeners"}) + listener_actions.append({"name": "add", "args": listener_add_args, "help": "Create a listener"}) + listener_actions.append({"name": "del", "args": listener_del_args, "help": "Delete a listener"}) + listener_actions.append({"name": "list", "args": listener_list_args, "help": "List listeners"}) listener_choices = get_actions(listener_actions) @cli.cmd(listener_actions) @@ -1108,18 +1089,18 @@ def host_add(self, args): if args.psk: if len(args.host_nqn) > 1: - self.cli.parser.error(f"Can't have more than one host NQN when PSK keys are used") + self.cli.parser.error("Can't have more than one host NQN when PSK keys are used") if args.dhchap_key: if len(args.host_nqn) > 1: - self.cli.parser.error(f"Can't have more than one host NQN when DH-HMAC-CHAP keys are used") + self.cli.parser.error("Can't have more than one host NQN when DH-HMAC-CHAP keys are used") for one_host_nqn in args.host_nqn: if one_host_nqn == "*" and args.psk: - self.cli.parser.error(f"PSK key is only allowed for specific hosts") + self.cli.parser.error("PSK key is only allowed for specific hosts") if one_host_nqn == "*" and args.dhchap_key: - self.cli.parser.error(f"DH-HMAC-CHAP key is only allowed for specific hosts") + self.cli.parser.error("DH-HMAC-CHAP key is only allowed for specific hosts") req = pb2.add_host_req(subsystem_nqn=args.subsystem, host_nqn=one_host_nqn, psk=args.psk, dhchap_key=args.dhchap_key) @@ -1130,7 +1111,7 @@ def host_add(self, args): errmsg = f"Failure allowing open host access to {args.subsystem}" else: errmsg = f"Failure adding host {one_host_nqn} to {args.subsystem}" - ret = pb2.req_status(status = errno.EINVAL, error_message = f"{errmsg}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"{errmsg}:\n{ex}") if not rc: rc = ret.status @@ -1144,13 +1125,11 @@ def host_add(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1180,7 +1159,7 @@ def host_del(self, args): errmsg = f"Failure disabling open host access to {args.subsystem}" else: errmsg = f"Failure removing host {one_host_nqn} access to {args.subsystem}" - ret = pb2.req_status(status = errno.EINVAL, error_message = f"{errmsg}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"{errmsg}:\n{ex}") if not rc: rc = ret.status @@ -1194,13 +1173,11 @@ def host_del(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1220,7 +1197,7 @@ def host_change_key(self, args): out_func, err_func = self.get_output_functions(args) if args.host_nqn == "*": - self.cli.parser.error(f"Can't change keys for host NQN '*', please use a real NQN") + self.cli.parser.error("Can't change keys for host NQN '*', please use a real NQN") req = pb2.change_host_key_req(subsystem_nqn=args.subsystem, host_nqn=args.host_nqn, dhchap_key=args.dhchap_key) @@ -1228,21 +1205,19 @@ def host_change_key(self, args): ret = self.stub.change_host_key(req) except Exception as ex: errmsg = f"Failure changing key for host {args.host_nqn} on subsystem {args.subsystem}" - ret = pb2.req_status(status = errno.EINVAL, error_message = f"{errmsg}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"{errmsg}:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: out_func(f"Changing key for host {args.host_nqn} on subsystem {args.subsystem}: Successful") else: - err_func(f"{ret.error_message}") + err_func(ret.error_message) elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1262,7 +1237,7 @@ def host_list(self, args): try: hosts_info = self.stub.list_hosts(pb2.list_hosts_req(subsystem=args.subsystem)) except Exception as ex: - hosts_info = pb2.hosts_info(status = errno.EINVAL, error_message = f"Failure listing hosts:\n{ex}", hosts=[]) + hosts_info = pb2.hosts_info(status=errno.EINVAL, error_message=f"Failure listing hosts:\n{ex}", hosts=[]) if args.format == "text" or args.format == "plain": if hosts_info.status == 0: @@ -1279,21 +1254,19 @@ def host_list(self, args): else: table_format = "plain" hosts_out = tabulate(hosts_list, - headers = ["Host NQN", "Uses PSK", "Uses DHCHAP"], - tablefmt=table_format, stralign="center") + headers=["Host NQN", "Uses PSK", "Uses DHCHAP"], + tablefmt=table_format, stralign="center") out_func(f"Hosts allowed to access {args.subsystem}:\n{hosts_out}") else: out_func(f"No hosts are allowed to access {args.subsystem}") else: err_func(f"{hosts_info.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - hosts_info, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(hosts_info, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1322,10 +1295,10 @@ def host_list(self, args): argument("--dhchap-key", "-k", help="Host DH-HMAC-CHAP key", required=False), ] host_actions = [] - host_actions.append({"name" : "add", "args" : host_add_args, "help" : "Add host access to a subsystem"}) - host_actions.append({"name" : "del", "args" : host_del_args, "help" : "Remove host access from a subsystem"}) - host_actions.append({"name" : "list", "args" : host_list_args, "help" : "List subsystem's host access"}) - host_actions.append({"name" : "change_key", "args" : host_change_key_args, "help" : "Change host's inband authentication keys"}) + host_actions.append({"name": "add", "args": host_add_args, "help": "Add host access to a subsystem"}) + host_actions.append({"name": "del", "args": host_del_args, "help": "Remove host access from a subsystem"}) + host_actions.append({"name": "list", "args": host_list_args, "help": "List subsystem's host access"}) + host_actions.append({"name": "change_key", "args": host_change_key_args, "help": "Change host's inband authentication keys"}) host_choices = get_actions(host_actions) @cli.cmd(host_actions) @@ -1350,8 +1323,8 @@ def connection_list(self, args): try: connections_info = self.stub.list_connections(pb2.list_connections_req(subsystem=args.subsystem)) except Exception as ex: - connections_info = pb2.connections_info(status = errno.EINVAL, - error_message = f"Failure listing hosts:\n{ex}", connections=[]) + connections_info = pb2.connections_info(status=errno.EINVAL, + error_message=f"Failure listing hosts:\n{ex}", connections=[]) if args.format == "text" or args.format == "plain": if connections_info.status == 0: @@ -1363,34 +1336,33 @@ def connection_list(self, args): if conn.connected: conn_secure = "Yes" if conn.secure else "No" connections_list.append([conn.nqn, - f"{conn.traddr}:{conn.trsvcid}" if conn.connected else "", - "Yes" if conn.connected else "No", - conn.qpairs_count if conn.connected else "", - conn.controller_id if conn.connected else "", - conn_secure, - conn_psk, - conn_dhchap]) + f"{conn.traddr}:{conn.trsvcid}" if conn.connected else "", + "Yes" if conn.connected else "No", + conn.qpairs_count if conn.connected else "", + conn.controller_id if conn.connected else "", + conn_secure, + conn_psk, + conn_dhchap]) if len(connections_list) > 0: if args.format == "text": table_format = "fancy_grid" else: table_format = "plain" connections_out = tabulate(connections_list, - headers = ["Host NQN", "Address", "Connected", "QPairs Count", "Controller ID", "Secure", "Uses\nPSK", "Uses\nDHCHAP"], - tablefmt=table_format) + headers=["Host NQN", "Address", "Connected", "QPairs Count", + "Controller ID", "Secure", "Uses\nPSK", "Uses\nDHCHAP"], + tablefmt=table_format) out_func(f"Connections for {args.subsystem}:\n{connections_out}") else: out_func(f"No connections for {args.subsystem}") else: err_func(f"{connections_info.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - connections_info, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(connections_info, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1405,7 +1377,7 @@ def connection_list(self, args): argument("--subsystem", "-n", help="Subsystem NQN", required=True), ] connection_actions = [] - connection_actions.append({"name" : "list", "args" : connection_list_args, "help" : "List active connections"}) + connection_actions.append({"name": "list", "args": connection_list_args, "help": "List active connections"}) connection_choices = get_actions(connection_actions) @cli.cmd(connection_actions) @@ -1421,17 +1393,17 @@ def ns_add(self, args): img_size = 0 out_func, err_func = self.get_output_functions(args) - if args.block_size == None: + if args.block_size is None: args.block_size = 512 if args.block_size <= 0: self.cli.parser.error("block-size value must be positive") if args.load_balancing_group < 0: - self.cli.parser.error("load-balancing-group value must be positive") - if args.nsid != None and args.nsid <= 0: + self.cli.parser.error("load-balancing-group value must be positive") + if args.nsid is not None and args.nsid <= 0: self.cli.parser.error("nsid value must be positive") if args.rbd_create_image: - if args.size == None: + if args.size is None: self.cli.parser.error("--size argument is mandatory for add command when RBD image creation is enabled") img_size = self.get_size_in_bytes(args.size) if img_size <= 0: @@ -1440,20 +1412,20 @@ def ns_add(self, args): if img_size % mib: self.cli.parser.error("size value must be aligned to MiBs") else: - if args.size != None: + if args.size is not None: self.cli.parser.error("--size argument is not allowed for add command when RBD image creation is disabled") req = pb2.namespace_add_req(rbd_pool_name=args.rbd_pool, - rbd_image_name=args.rbd_image, - subsystem_nqn=args.subsystem, - nsid=args.nsid, - block_size=args.block_size, - uuid=args.uuid, - anagrpid=args.load_balancing_group, - create_image=args.rbd_create_image, - size=img_size, - force=args.force, - no_auto_visible=args.no_auto_visible) + rbd_image_name=args.rbd_image, + subsystem_nqn=args.subsystem, + nsid=args.nsid, + block_size=args.block_size, + uuid=args.uuid, + anagrpid=args.load_balancing_group, + create_image=args.rbd_create_image, + size=img_size, + force=args.force, + no_auto_visible=args.no_auto_visible) try: ret = self.stub.namespace_add(req) except Exception as ex: @@ -1461,21 +1433,19 @@ def ns_add(self, args): if args.nsid: nsid_msg = f"using NSID {args.nsid} " errmsg = f"Failure adding namespace {nsid_msg}to {args.subsystem}" - ret = pb2.req_status(status = errno.EINVAL, error_message = f"{errmsg}:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"{errmsg}:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: out_func(f"Adding namespace {ret.nsid} to {args.subsystem}: Successful") else: - err_func(f"{ret.error_message}") + err_func(ret.error_message) elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1496,7 +1466,7 @@ def ns_del(self, args): try: ret = self.stub.namespace_delete(pb2.namespace_delete_req(subsystem_nqn=args.subsystem, nsid=args.nsid)) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure deleting namespace:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure deleting namespace:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -1504,13 +1474,11 @@ def ns_del(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1539,7 +1507,7 @@ def ns_resize(self, args): try: ret = self.stub.namespace_resize(pb2.namespace_resize_req(subsystem_nqn=args.subsystem, nsid=args.nsid, new_size=ns_size)) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure resizing namespace:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure resizing namespace:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -1548,13 +1516,11 @@ def ns_resize(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1613,14 +1579,14 @@ def ns_list(self, args): """Lists namespaces on a subsystem.""" out_func, err_func = self.get_output_functions(args) - if args.nsid != None and args.nsid <= 0: + if args.nsid is not None and args.nsid <= 0: self.cli.parser.error("nsid value must be positive") try: namespaces_info = self.stub.list_namespaces(pb2.list_namespaces_req(subsystem=args.subsystem, nsid=args.nsid, uuid=args.uuid)) except Exception as ex: - namespaces_info = pb2.namespaces_info(status = errno.EINVAL, error_message = f"Failure listing namespaces:\n{ex}") + namespaces_info = pb2.namespaces_info(status=errno.EINVAL, error_message=f"Failure listing namespaces:\n{ex}") if args.format == "text" or args.format == "plain": if namespaces_info.status == 0: @@ -1669,11 +1635,11 @@ def ns_list(self, args): else: table_format = "plain" namespaces_out = tabulate(namespaces_list, - headers = ["NSID", "Bdev\nName", "RBD\nImage", - "Image\nSize", "Block\nSize", "UUID", "Load\nBalancing\nGroup", "Visibility", - "R/W IOs\nper\nsecond", "R/W MBs\nper\nsecond", - "Read MBs\nper\nsecond", "Write MBs\nper\nsecond"], - tablefmt=table_format) + headers=["NSID", "Bdev\nName", "RBD\nImage", + "Image\nSize", "Block\nSize", "UUID", "Load\nBalancing\nGroup", "Visibility", + "R/W IOs\nper\nsecond", "R/W MBs\nper\nsecond", + "Read MBs\nper\nsecond", "Write MBs\nper\nsecond"], + tablefmt=table_format) if args.nsid: prefix = f"Namespace {args.nsid} in" elif args.uuid: @@ -1691,13 +1657,11 @@ def ns_list(self, args): else: err_func(f"{namespaces_info.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - namespaces_info, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(namespaces_info, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1719,7 +1683,7 @@ def ns_get_io_stats(self, args): get_stats_req = pb2.namespace_get_io_stats_req(subsystem_nqn=args.subsystem, nsid=args.nsid) ns_io_stats = self.stub.namespace_get_io_stats(get_stats_req) except Exception as ex: - ns_io_stats = pb2.namespace_io_stats_info(status = errno.EINVAL, error_message = f"Failure getting namespace's IO stats:\n{ex}") + ns_io_stats = pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=f"Failure getting namespace's IO stats:\n{ex}") if ns_io_stats.status == 0: if ns_io_stats.subsystem_nqn != args.subsystem: @@ -1731,32 +1695,32 @@ def ns_get_io_stats(self, args): # only show IO errors in verbose mode if not args.verbose: - io_stats = pb2.namespace_io_stats_info(status = ns_io_stats.status, - error_message = ns_io_stats.error_message, - subsystem_nqn = ns_io_stats.subsystem_nqn, - nsid = ns_io_stats.nsid, - uuid = ns_io_stats.uuid, - bdev_name = ns_io_stats.bdev_name, - tick_rate = ns_io_stats.tick_rate, - ticks = ns_io_stats.ticks, - bytes_read = ns_io_stats.bytes_read, - num_read_ops = ns_io_stats.num_read_ops, - bytes_written = ns_io_stats.bytes_written, - num_write_ops = ns_io_stats.num_write_ops, - bytes_unmapped = ns_io_stats.bytes_unmapped, - num_unmap_ops = ns_io_stats.num_unmap_ops, - read_latency_ticks = ns_io_stats.read_latency_ticks, - max_read_latency_ticks = ns_io_stats.max_read_latency_ticks, - min_read_latency_ticks = ns_io_stats.min_read_latency_ticks, - write_latency_ticks = ns_io_stats.write_latency_ticks, - max_write_latency_ticks = ns_io_stats.max_write_latency_ticks, - min_write_latency_ticks = ns_io_stats.min_write_latency_ticks, - unmap_latency_ticks = ns_io_stats.unmap_latency_ticks, - max_unmap_latency_ticks = ns_io_stats.max_unmap_latency_ticks, - min_unmap_latency_ticks = ns_io_stats.min_unmap_latency_ticks, - copy_latency_ticks = ns_io_stats.copy_latency_ticks, - max_copy_latency_ticks = ns_io_stats.max_copy_latency_ticks, - min_copy_latency_ticks = ns_io_stats.min_copy_latency_ticks) + io_stats = pb2.namespace_io_stats_info(status=ns_io_stats.status, + error_message=ns_io_stats.error_message, + subsystem_nqn=ns_io_stats.subsystem_nqn, + nsid=ns_io_stats.nsid, + uuid=ns_io_stats.uuid, + bdev_name=ns_io_stats.bdev_name, + tick_rate=ns_io_stats.tick_rate, + ticks=ns_io_stats.ticks, + bytes_read=ns_io_stats.bytes_read, + num_read_ops=ns_io_stats.num_read_ops, + bytes_written=ns_io_stats.bytes_written, + num_write_ops=ns_io_stats.num_write_ops, + bytes_unmapped=ns_io_stats.bytes_unmapped, + num_unmap_ops=ns_io_stats.num_unmap_ops, + read_latency_ticks=ns_io_stats.read_latency_ticks, + max_read_latency_ticks=ns_io_stats.max_read_latency_ticks, + min_read_latency_ticks=ns_io_stats.min_read_latency_ticks, + write_latency_ticks=ns_io_stats.write_latency_ticks, + max_write_latency_ticks=ns_io_stats.max_write_latency_ticks, + min_write_latency_ticks=ns_io_stats.min_write_latency_ticks, + unmap_latency_ticks=ns_io_stats.unmap_latency_ticks, + max_unmap_latency_ticks=ns_io_stats.max_unmap_latency_ticks, + min_unmap_latency_ticks=ns_io_stats.min_unmap_latency_ticks, + copy_latency_ticks=ns_io_stats.copy_latency_ticks, + max_copy_latency_ticks=ns_io_stats.max_copy_latency_ticks, + min_copy_latency_ticks=ns_io_stats.min_copy_latency_ticks) ns_io_stats = io_stats if args.format == "text" or args.format == "plain": @@ -1790,18 +1754,16 @@ def ns_get_io_stats(self, args): table_format = "fancy_grid" else: table_format = "plain" - stats_out = tabulate(stats_list, headers = ["Stat", "Value"], tablefmt=table_format) + stats_out = tabulate(stats_list, headers=["Stat", "Value"], tablefmt=table_format) out_func(f"IO statistics for namespace {args.nsid} in {args.subsystem}, bdev {ns_io_stats.bdev_name}:\n{stats_out}") else: err_func(f"{ns_io_stats.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ns_io_stats, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ns_io_stats, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1826,7 +1788,7 @@ def ns_change_load_balancing_group(self, args): nsid=args.nsid, anagrpid=args.load_balancing_group) ret = self.stub.namespace_change_load_balancing_group(change_lb_group_req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure changing namespace load balancing group:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure changing namespace load balancing group:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -1834,13 +1796,11 @@ def ns_change_load_balancing_group(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1863,7 +1823,7 @@ def ns_set_qos(self, args): out_func, err_func = self.get_output_functions(args) if args.nsid <= 0: self.cli.parser.error("nsid value must be positive") - if args.rw_ios_per_second == None and args.rw_megabytes_per_second == None and args.r_megabytes_per_second == None and args.w_megabytes_per_second == None: + if args.rw_ios_per_second is None and args.rw_megabytes_per_second is None and args.r_megabytes_per_second is None and args.w_megabytes_per_second is None: self.cli.parser.error("At least one QOS limit should be set") if args.format == "text" or args.format == "plain": @@ -1875,19 +1835,19 @@ def ns_set_qos(self, args): qos_args["subsystem_nqn"] = args.subsystem if args.nsid: qos_args["nsid"] = args.nsid - if args.rw_ios_per_second != None: + if args.rw_ios_per_second is not None: qos_args["rw_ios_per_second"] = args.rw_ios_per_second - if args.rw_megabytes_per_second != None: + if args.rw_megabytes_per_second is not None: qos_args["rw_mbytes_per_second"] = args.rw_megabytes_per_second - if args.r_megabytes_per_second != None: + if args.r_megabytes_per_second is not None: qos_args["r_mbytes_per_second"] = args.r_megabytes_per_second - if args.w_megabytes_per_second != None: + if args.w_megabytes_per_second is not None: qos_args["w_mbytes_per_second"] = args.w_megabytes_per_second try: set_qos_req = pb2.namespace_set_qos_req(**qos_args) ret = self.stub.namespace_set_qos_limits(set_qos_req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure setting namespaces QOS limits:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure setting namespaces QOS limits:\n{ex}") if args.format == "text" or args.format == "plain": if ret.status == 0: @@ -1895,13 +1855,11 @@ def ns_set_qos(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1927,7 +1885,7 @@ def ns_add_host(self, args): add_host_req = pb2.namespace_add_host_req(subsystem_nqn=args.subsystem, nsid=args.nsid, host_nqn=one_host_nqn) ret = self.stub.namespace_add_host(add_host_req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure adding host to namespace:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure adding host to namespace:\n{ex}") if not rc: rc = ret.status @@ -1938,13 +1896,11 @@ def ns_add_host(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -1973,7 +1929,7 @@ def ns_del_host(self, args): del_host_req = pb2.namespace_delete_host_req(subsystem_nqn=args.subsystem, nsid=args.nsid, host_nqn=one_host_nqn) ret = self.stub.namespace_delete_host(del_host_req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure deleting host from namespace:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure deleting host from namespace:\n{ex}") if not rc: rc = ret.status @@ -1984,13 +1940,11 @@ def ns_del_host(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -2026,11 +1980,11 @@ def ns_change_visibility(self, args): try: change_visibility_req = pb2.namespace_change_visibility_req(subsystem_nqn=args.subsystem, - nsid=args.nsid, auto_visible=auto_visible, - force=args.force) + nsid=args.nsid, auto_visible=auto_visible, + force=args.force) ret = self.stub.namespace_change_visibility(change_visibility_req) except Exception as ex: - ret = pb2.req_status(status = errno.EINVAL, error_message = f"Failure changing namespace visibility:\n{ex}") + ret = pb2.req_status(status=errno.EINVAL, error_message=f"Failure changing namespace visibility:\n{ex}") if auto_visible: vis_text = "\"visible to all hosts\"" @@ -2042,13 +1996,11 @@ def ns_change_visibility(self, args): else: err_func(f"{ret.error_message}") elif args.format == "json" or args.format == "yaml": - ret_str = json_format.MessageToJson( - ret, - indent=4, - including_default_value_fields=True, - preserving_proto_field_name=True) + ret_str = json_format.MessageToJson(ret, indent=4, + including_default_value_fields=True, + preserving_proto_field_name=True) if args.format == "json": - out_func(f"{ret_str}") + out_func(ret_str) elif args.format == "yaml": obj = json.loads(ret_str) out_func(yaml.dump(obj)) @@ -2114,16 +2066,16 @@ def ns_change_visibility(self, args): argument("--host-nqn", "-t", help="Host NQN list", nargs="+", required=True), ] ns_actions = [] - ns_actions.append({"name" : "add", "args" : ns_add_args_list, "help" : "Create a namespace"}) - ns_actions.append({"name" : "del", "args" : ns_del_args_list, "help" : "Delete a namespace"}) - ns_actions.append({"name" : "resize", "args" : ns_resize_args_list, "help" : "Resize a namespace"}) - ns_actions.append({"name" : "list", "args" : ns_list_args_list, "help" : "List namespaces"}) - ns_actions.append({"name" : "get_io_stats", "args" : ns_get_io_stats_args_list, "help" : "Get I/O stats for a namespace"}) - ns_actions.append({"name" : "change_load_balancing_group", "args" : ns_change_load_balancing_group_args_list, "help" : "Change load balancing group for a namespace"}) - ns_actions.append({"name" : "set_qos", "args" : ns_set_qos_args_list, "help" : "Set QOS limits for a namespace"}) - ns_actions.append({"name" : "add_host", "args" : ns_add_host_args_list, "help" : "Add a host to a namespace"}) - ns_actions.append({"name" : "del_host", "args" : ns_del_host_args_list, "help" : "Delete a host from a namespace"}) - ns_actions.append({"name" : "change_visibility", "args" : ns_change_visibility_args_list, "help" : "Change visibility for a namespace"}) + ns_actions.append({"name": "add", "args": ns_add_args_list, "help": "Create a namespace"}) + ns_actions.append({"name": "del", "args": ns_del_args_list, "help": "Delete a namespace"}) + ns_actions.append({"name": "resize", "args": ns_resize_args_list, "help": "Resize a namespace"}) + ns_actions.append({"name": "list", "args": ns_list_args_list, "help": "List namespaces"}) + ns_actions.append({"name": "get_io_stats", "args": ns_get_io_stats_args_list, "help": "Get I/O stats for a namespace"}) + ns_actions.append({"name": "change_load_balancing_group", "args": ns_change_load_balancing_group_args_list, "help": "Change load balancing group for a namespace"}) + ns_actions.append({"name": "set_qos", "args": ns_set_qos_args_list, "help": "Set QOS limits for a namespace"}) + ns_actions.append({"name": "add_host", "args": ns_add_host_args_list, "help": "Add a host to a namespace"}) + ns_actions.append({"name": "del_host", "args": ns_del_host_args_list, "help": "Delete a host from a namespace"}) + ns_actions.append({"name": "change_visibility", "args": ns_change_visibility_args_list, "help": "Change visibility for a namespace"}) ns_choices = get_actions(ns_actions) @cli.cmd(ns_actions, ["ns"]) @@ -2160,12 +2112,12 @@ def get_subsystems(self, args): subsystems = self.stub.get_subsystems(pb2.get_subsystems_req()) if args.format == "python": return subsystems - subsystems_out = json_format.MessageToJson( - subsystems, - indent=4, including_default_value_fields=True, - preserving_proto_field_name=True) + subsystems_out = json_format.MessageToJson(subsystems, + indent=4, including_default_value_fields=True, + preserving_proto_field_name=True) out_func(f"Get subsystems:\n{subsystems_out}") + def main_common(client, args): client.logger.setLevel(GatewayEnumUtils.get_value_from_key(pb2.GwLogLevel, args.log_level.lower())) server_address = args.server_address @@ -2178,6 +2130,7 @@ def main_common(client, args): rc = call_function(args) return rc + def main_test(args): if not args: return None @@ -2194,6 +2147,7 @@ def main_test(args): return main_common(client, parsed_args) + def main(args=None) -> int: client = GatewayClient() parsed_args = client.cli.parser.parse_args(args) diff --git a/control/config.py b/control/config.py index 947ad87c..8aecb004 100644 --- a/control/config.py +++ b/control/config.py @@ -10,6 +10,7 @@ import configparser import os + class GatewayConfig: """Loads and returns config file settings. @@ -59,12 +60,12 @@ def dump_config_file(self, logger): logger.info(f"Using configuration file {self.filepath}") with open(self.filepath) as f: logger.info( - f"====================================== Configuration file content ======================================") + "====================================== Configuration file content ======================================") for line in f: line = line.rstrip() logger.info(f"{line}") logger.info( - f"========================================================================================================") + "========================================================================================================") self.conffile_logged = True except Exception: pass diff --git a/control/discovery.py b/control/discovery.py index eae1a504..29ec34e7 100644 --- a/control/discovery.py +++ b/control/discovery.py @@ -8,15 +8,12 @@ # import argparse -import grpc import json from .config import GatewayConfig from .state import GatewayState, LocalGatewayState, OmapGatewayState, GatewayStateHandler from .utils import GatewayLogger -from .proto import gateway_pb2 as pb2 -import rados -from typing import Dict, Optional +from typing import Dict import socket import threading @@ -27,8 +24,8 @@ import selectors import os from dataclasses import dataclass, field -from ctypes import Structure, LittleEndianStructure, c_bool, c_ubyte, c_uint8, c_uint16, c_uint32, c_uint64, c_float -from google.protobuf import json_format +from ctypes import LittleEndianStructure, c_ubyte, c_uint8, c_uint16, c_uint32, c_uint64 + # NVMe tcp pdu type class NVME_TCP_PDU(enum.IntFlag): @@ -42,6 +39,7 @@ class NVME_TCP_PDU(enum.IntFlag): C2H_DATA = 0x7 TCP_R2T = 0x9 + # NVMe tcp opcode class NVME_TCP_OPC(enum.IntFlag): DELETE_SQ = 0x0 @@ -61,6 +59,7 @@ class NVME_TCP_OPC(enum.IntFlag): KEEP_ALIVE = 0x18 FABRIC_TYPE = 0x7F + # NVMe tcp fabric command type class NVME_TCP_FCTYPE(enum.IntFlag): PROP_SET = 0x0 @@ -70,6 +69,7 @@ class NVME_TCP_FCTYPE(enum.IntFlag): AUTH_RECV = 0x6 DISCONNECT = 0x8 + # NVMe controller register space offsets class NVME_CTL(enum.IntFlag): CAPABILITIES = 0x0 @@ -85,6 +85,7 @@ class NVMF_SUBTYPE(enum.IntFlag): # NVMe type for NVM subsystem NVME = 0x2 + # NVMe over Fabrics transport types class TRANSPORT_TYPES(enum.IntFlag): RDMA = 0x1 @@ -92,6 +93,7 @@ class TRANSPORT_TYPES(enum.IntFlag): TCP = 0x3 INTRA_HOST = 0xfe + # Address family types class ADRFAM_TYPES(enum.IntFlag): ipv4 = 0x1 @@ -100,6 +102,7 @@ class ADRFAM_TYPES(enum.IntFlag): fc = 0x4 intra_host = 0xfe + # Transport requirement, secure channel requirements # Connections shall be made over a fabric secure channel class NVMF_TREQ_SECURE_CHANNEL(enum.IntFlag): @@ -107,6 +110,7 @@ class NVMF_TREQ_SECURE_CHANNEL(enum.IntFlag): REQUIRED = 0x1 NOT_REQUIRED = 0x2 + # maximum number of connections MAX_CONNECTION = 10240 @@ -116,6 +120,7 @@ class NVMF_TREQ_SECURE_CHANNEL(enum.IntFlag): # Max SQ head pointer SQ_HEAD_MAX = 128 + @dataclass class Connection: """Data used multiple times in each connection.""" @@ -124,15 +129,15 @@ class Connection: allow_listeners: list = field(default_factory=list) log_page: bytearray = field(default_factory=bytearray) recv_buffer: bytearray = field(default_factory=bytearray) - nvmeof_connect_data_hostid: tuple = tuple((c_ubyte *16)()) + nvmeof_connect_data_hostid: tuple = tuple((c_ubyte * 16)()) nvmeof_connect_data_cntlid: int = 0 - nvmeof_connect_data_subnqn: tuple = tuple((c_ubyte *256)()) - nvmeof_connect_data_hostnqn: tuple = tuple((c_ubyte *256)()) + nvmeof_connect_data_subnqn: tuple = tuple((c_ubyte * 256)()) + nvmeof_connect_data_hostnqn: tuple = tuple((c_ubyte * 256)()) sq_head_ptr: int = 0 unsent_log_page_len: int = 0 # NVM ExpressTM Revision 1.4, page 47 # see Figure 78: Offset 14h: CC – Controller Configuration - property_configuration: tuple = tuple((c_ubyte *8)()) + property_configuration: tuple = tuple((c_ubyte * 8)()) shutdown_now: bool = False controller_id: uuid = None gen_cnt: int = 0 @@ -141,6 +146,7 @@ class Connection: keep_alive_time: float = 0.0 keep_alive_timeout: int = 0 + class AutoSerializableStructure(LittleEndianStructure): def __add__(self, other): if isinstance(other, LittleEndianStructure): @@ -150,6 +156,7 @@ def __add__(self, other): else: raise ValueError("error message format.") + class Pdu(AutoSerializableStructure): _fields_ = [ ("type", c_uint8), @@ -159,6 +166,7 @@ class Pdu(AutoSerializableStructure): ("packet_length", c_uint32), ] + class ICResp(AutoSerializableStructure): _fields_ = [ # pdu version format @@ -171,6 +179,7 @@ class ICResp(AutoSerializableStructure): ("maximum_data_capsules", c_uint32) ] + class CqeConnect(AutoSerializableStructure): _fields_ = [ ("controller_id", c_uint16), @@ -182,6 +191,7 @@ class CqeConnect(AutoSerializableStructure): ("status", c_uint16) ] + class CqePropertyGetSet(AutoSerializableStructure): _fields_ = [ # property data for property get, reserved for property set @@ -192,6 +202,7 @@ class CqePropertyGetSet(AutoSerializableStructure): ("status", c_uint16) ] + class NVMeTcpDataPdu(AutoSerializableStructure): _fields_ = [ ("cmd_id", c_uint16), @@ -201,6 +212,7 @@ class NVMeTcpDataPdu(AutoSerializableStructure): ("reserved", c_uint32) ] + class NVMeIdentify(AutoSerializableStructure): _fields_ = [ # skip some fields, include VID, SSVID, SN, MN @@ -251,8 +263,9 @@ class NVMeIdentify(AutoSerializableStructure): ("vendor_specific", c_ubyte * 1024) ] + # for set feature, keep alive and async -class CqeNVMe(AutoSerializableStructure): +class CqeNVMe(AutoSerializableStructure): _fields_ = [ ("dword0", c_uint32), ("dword1", c_uint32), @@ -262,17 +275,19 @@ class CqeNVMe(AutoSerializableStructure): ("status", c_uint16) ] + class NVMeGetLogPage(AutoSerializableStructure): _fields_ = [ # generation counter ("genctr", c_uint64), # number of records ("numrec", c_uint64), - #record format + # record format ("recfmt", c_uint16), ("reserved", c_ubyte * 1006) ] + class DiscoveryLogEntry(AutoSerializableStructure): _fields_ = [ ("trtype", c_uint8), @@ -292,6 +307,7 @@ class DiscoveryLogEntry(AutoSerializableStructure): ("tsas", c_ubyte * 256) ] + class DiscoveryService: """Implements discovery controller. @@ -345,28 +361,28 @@ def __exit__(self, exc_type, exc_value, traceback): for key in self.conn_vals: try: self.selector.unregister(self.conn_vals[key].connection) - except Except as ex: + except Exception: pass try: self.conn_vals[key].connection.close() - except Except as ex: + except Exception: pass self.conn_vals = {} if self.sock: try: self.selector.unregister(self.sock) - except Exception as ex: + except Exception: pass try: self.sock.close() - except Exception as ex: + except Exception: pass self.sock = None try: self.selector.close() - except Exception as ex: + except Exception: pass self.selector = None @@ -379,8 +395,7 @@ def _read_all(self) -> Dict[str, str]: def _get_vals(self, omap_dict, prefix): """Read values from the OMAP dict.""" - return [json.loads(val.decode('utf-8')) for (key, val) in omap_dict.items() - if key.startswith(prefix)] + return [json.loads(val.decode('utf-8')) for (key, val) in omap_dict.items() if key.startswith(prefix)] def reply_initialize(self, conn): """Reply initialize request.""" @@ -407,21 +422,21 @@ def reply_fc_cmd_connect(self, conn, data, cmd_id): self.logger.debug("handle connect request.") self_conn = self.conn_vals[conn.fileno()] - hf_nvmeof_cmd_connect_rsvd1 = struct.unpack_from('<19B', data, 13) + hf_nvmeof_cmd_connect_rsvd1 = struct.unpack_from('<19B', data, 13) # noqa: F841 SIGL1 = struct.unpack_from('> 8) & 0x1F - get_logpage_lsi = nvme_get_logpage_dword11 >> 16 - get_logpage_uid_idx = nvme_get_logpage_dword14 & 0x3F + get_logpage_lsp = (nvme_get_logpage_dword10 >> 8) & 0x1F # noqa: F841 + get_logpage_lsi = nvme_get_logpage_dword11 >> 16 # noqa: F841 + get_logpage_uid_idx = nvme_get_logpage_dword14 & 0x3F # noqa: F841 if get_logpage_lid != 0x70: self.logger.error("request type error, not discovery request.") @@ -747,7 +755,6 @@ def reply_get_log_page(self, conn, data, cmd_id): allow_listeners = self_conn.allow_listeners if len(allow_listeners) == 0: for host in hosts: - a = host["host_nqn"] if host["host_nqn"] == '*' or host["host_nqn"] == hostnqn: for listener in listeners: # TODO: It is better to change nqn in the "listener" @@ -784,23 +791,22 @@ def reply_get_log_page(self, conn, data, cmd_id): log_entry.asqsz = 128 # transport service indentifier str_trsvcid = str(allow_listeners[log_entry_counter]["trsvcid"]) - log_entry.trsvcid = (c_ubyte * 32)(*[c_ubyte(x) for x \ - in str_trsvcid.encode()]) + log_entry.trsvcid = (c_ubyte * 32)(*[c_ubyte(x) for x in str_trsvcid.encode()]) log_entry.trsvcid[len(str_trsvcid):] = \ [c_ubyte(0x20)] * (32 - len(str_trsvcid)) # NVM subsystem qualified name - log_entry.subnqn = (c_ubyte * 256)(*[c_ubyte(x) for x \ - in allow_listeners[log_entry_counter]["nqn"].encode()]) + log_entry.subnqn = (c_ubyte * 256)(*[c_ubyte(x) for x + in allow_listeners[log_entry_counter]["nqn"].encode()]) log_entry.subnqn[len(allow_listeners[log_entry_counter]["nqn"]):] = \ [c_ubyte(0x00)] * (256 - len(allow_listeners[log_entry_counter]["nqn"])) # Transport address - log_entry.traddr = (c_ubyte * 256)(*[c_ubyte(x) for x \ - in allow_listeners[log_entry_counter]["traddr"].encode()]) + log_entry.traddr = (c_ubyte * 256)(*[c_ubyte(x) for x + in allow_listeners[log_entry_counter]["traddr"].encode()]) log_entry.traddr[len(allow_listeners[log_entry_counter]["traddr"]):] = \ [c_ubyte(0x20)] * (256 - len(allow_listeners[log_entry_counter]["traddr"])) - self_conn.log_page[1024*(log_entry_counter+1): \ - 1024*(log_entry_counter+2)] = log_entry + self_conn.log_page[1024 * (log_entry_counter + 1): + 1024 * (log_entry_counter + 2)] = log_entry log_entry_counter += 1 else: self.logger.debug("in the process of sending log pages...") @@ -828,7 +834,7 @@ def reply_get_log_page(self, conn, data, cmd_id): elif nvme_data_len % 1024 == 0: # reply log pages reply = pdu_reply + nvme_tcp_data_pdu + \ - self_conn.log_page[nvme_logpage_offset:nvme_logpage_offset+nvme_data_len] + self_conn.log_page[nvme_logpage_offset:nvme_logpage_offset + nvme_data_len] self_conn.unsent_log_page_len -= nvme_data_len if self_conn.unsent_log_page_len == 0: self_conn.log_page = b'' @@ -850,21 +856,21 @@ def reply_keep_alive(self, conn, data, cmd_id): self.logger.debug("handle keep alive request.") self_conn = self.conn_vals[conn.fileno()] nvme_sgl = struct.unpack_from('<16B', data, 32) - nvme_sgl_desc_type = nvme_sgl[15] & 0xF0 - nvme_sgl_desc_sub_type = nvme_sgl[15] & 0x0F - nvme_keep_alive_dword10 = struct.unpack_from('= \ - self.conn_vals[key].keep_alive_timeout / 1000: + time.time() - self.conn_vals[key].keep_alive_time >= \ + self.conn_vals[key].keep_alive_timeout / 1000: # Adding locks to prevent another thread from processing sudden requests. # Is there a better way? with self.lock: @@ -982,6 +987,7 @@ def reply_fabric_request(self, conn, data, cmd_id): NVME_TCP_FCTYPE.PROP_GET: self.reply_fc_cmd_prop_get, NVME_TCP_FCTYPE.PROP_SET: self.reply_fc_cmd_prop_set } + class UnknownFabricType(BaseException): def __init__(self, fabric_type): super().__init__(f"unsupported opcode: {fabric_type}") @@ -1015,9 +1021,9 @@ def nvmeof_tcp_connection(self, conn, mask): return pdu = struct.unpack_from(' None: self.set_group_id = set_group_id - def group_id(self, request: monitor_pb2.group_id_req, context = None) -> Empty: + def group_id(self, request: monitor_pb2.group_id_req, context=None) -> Empty: self.set_group_id(request.id) return Empty() + class SubsystemHostAuth: MAX_PSK_KEY_NAME_LENGTH = 200 # taken from SPDK SPDK_TLS_PSK_MAX_LEN @@ -162,6 +166,7 @@ def get_subsystem_dhchap_key(self, subsys) -> str: key = self.subsys_dhchap_key[subsys] return key + class NamespaceInfo: def __init__(self, nsid, bdev, uuid, anagrpid, auto_visible): self.nsid = nsid @@ -204,6 +209,7 @@ def host_count(self): def set_ana_group_id(self, anagrpid): self.anagrpid = anagrpid + class NamespacesLocalList: EMPTY_NAMESPACE = NamespaceInfo(None, None, None, 0, False) @@ -225,7 +231,7 @@ def add_namespace(self, nqn, nsid, bdev, uuid, anagrpid, auto_visible): bdev = GatewayService.find_unique_bdev_name(uuid) self.namespace_list[nqn][nsid] = NamespaceInfo(nsid, bdev, uuid, anagrpid, auto_visible) - def find_namespace(self, nqn, nsid, uuid = None) -> NamespaceInfo: + def find_namespace(self, nqn, nsid, uuid=None) -> NamespaceInfo: if nqn not in self.namespace_list: return NamespacesLocalList.EMPTY_NAMESPACE @@ -242,7 +248,7 @@ def find_namespace(self, nqn, nsid, uuid = None) -> NamespaceInfo: return NamespacesLocalList.EMPTY_NAMESPACE - def get_namespace_count(self, nqn, auto_visible = None, min_hosts = 0) -> int: + def get_namespace_count(self, nqn, auto_visible=None, min_hosts=0) -> int: if nqn and nqn not in self.namespace_list: return 0 @@ -282,7 +288,7 @@ def get_all_namespaces_by_ana_group_id(self, anagrpid): if ns.empty(): continue if ns.anagrpid == anagrpid: - ns_list.append((nsid, nqn))#list of tupples + ns_list.append((nsid, nqn)) # list of tupples return ns_list def get_ana_group_id_by_nsid_subsys(self, nqn, nsid): @@ -295,7 +301,6 @@ def get_ana_group_id_by_nsid_subsys(self, nqn, nsid): return 0 return ns.anagrpid - def get_subsys_namespaces_by_ana_group_id(self, nqn, anagrpid): ns_list = [] if nqn not in self.namespace_list: @@ -310,6 +315,7 @@ def get_subsys_namespaces_by_ana_group_id(self, nqn, anagrpid): return ns_list + class GatewayService(pb2_grpc.GatewayServicer): """Implements gateway service interface. @@ -384,7 +390,7 @@ def __init__(self, config: GatewayConfig, gateway_state: GatewayStateHandler, rp self.ana_grp_state[i] = pb2.ana_state.INACCESSIBLE self.cluster_nonce = {} self.bdev_cluster = {} - self.bdev_params = {} + self.bdev_params = {} self.subsystem_nsid_bdev_and_uuid = NamespacesLocalList() self.subsystem_listeners = defaultdict(set) self._init_cluster_context() @@ -393,7 +399,7 @@ def __init__(self, config: GatewayConfig, gateway_state: GatewayStateHandler, rp self.up_and_running = True self.rebalance = Rebalance(self) - def get_directories_for_key_file(self, key_type : str, subsysnqn : str, create_dir : bool = False) -> []: + def get_directories_for_key_file(self, key_type: str, subsysnqn: str, create_dir: bool = False) -> []: tmp_dirs = [] dir_prefix = f"{key_type}_{subsysnqn}_" @@ -419,13 +425,13 @@ def get_directories_for_key_file(self, key_type : str, subsysnqn : str, create_d return None return [tmp_dir_name] - def create_host_key_file(self, key_type : str, subsysnqn : str, hostnqn : str, key_value : str) -> str: + def create_host_key_file(self, key_type: str, subsysnqn: str, hostnqn: str, key_value: str) -> str: assert subsysnqn, "Subsystem NQN can't be empty" assert hostnqn, "Host NQN can't be empty" assert key_type, "Key type can't be empty" assert key_value, "Key value can't be empty" - tmp_dir_names = self.get_directories_for_key_file(key_type, subsysnqn, create_dir = True) + tmp_dir_names = self.get_directories_for_key_file(key_type, subsysnqn, create_dir=True) if not tmp_dir_names: return None @@ -443,7 +449,7 @@ def create_host_key_file(self, key_type : str, subsysnqn : str, hostnqn : str, k with open(file_fd, "wt") as f: f.write(key_value) except Exception: - self.logger.exception(f"Error creating file") + self.logger.exception("Error creating file") try: os.remove(filepath) except Exception: @@ -451,17 +457,17 @@ def create_host_key_file(self, key_type : str, subsysnqn : str, hostnqn : str, k return None return filepath - def create_host_psk_file(self, subsysnqn : str, hostnqn : str, key_value : str) -> str: + def create_host_psk_file(self, subsysnqn: str, hostnqn: str, key_value: str) -> str: return self.create_host_key_file(self.PSK_PREFIX, subsysnqn, hostnqn, key_value) - def create_host_dhchap_file(self, subsysnqn : str, hostnqn : str, key_value : str) -> str: + def create_host_dhchap_file(self, subsysnqn: str, hostnqn: str, key_value: str) -> str: return self.create_host_key_file(self.DHCHAP_PREFIX, subsysnqn, hostnqn, key_value) - def remove_host_key_file(self, key_type : str, subsysnqn : str, hostnqn : str) -> None: + def remove_host_key_file(self, key_type: str, subsysnqn: str, hostnqn: str) -> None: assert key_type, "Key type can't be empty" assert subsysnqn, "Subsystem NQN can't be empty" - tmp_dir_names = self.get_directories_for_key_file(key_type, subsysnqn, create_dir = False) + tmp_dir_names = self.get_directories_for_key_file(key_type, subsysnqn, create_dir=False) if not tmp_dir_names: return @@ -469,7 +475,7 @@ def remove_host_key_file(self, key_type : str, subsysnqn : str, hostnqn : str) - if not hostnqn: for one_tmp_dir in tmp_dir_names: try: - shutil.rmtree(one_tmp_dir, ignore_errors = True) + shutil.rmtree(one_tmp_dir, ignore_errors=True) except Exception: pass return @@ -484,27 +490,27 @@ def remove_host_key_file(self, key_type : str, subsysnqn : str, hostnqn : str) - self.logger.exception(f"Error deleting file {f.name}") pass - def remove_host_psk_file(self, subsysnqn : str, hostnqn : str) -> None: + def remove_host_psk_file(self, subsysnqn: str, hostnqn: str) -> None: self.remove_host_key_file(self.PSK_PREFIX, subsysnqn, hostnqn) - def remove_host_dhchap_file(self, subsysnqn : str, hostnqn : str) -> None: + def remove_host_dhchap_file(self, subsysnqn: str, hostnqn: str) -> None: self.remove_host_key_file(self.DHCHAP_PREFIX, subsysnqn, hostnqn) - def remove_all_host_key_files(self, subsysnqn : str, hostnqn : str) -> None: + def remove_all_host_key_files(self, subsysnqn: str, hostnqn: str) -> None: self.remove_host_psk_file(subsysnqn, hostnqn) self.remove_host_dhchap_file(subsysnqn, hostnqn) - def remove_all_subsystem_key_files(self, subsysnqn : str) -> None: + def remove_all_subsystem_key_files(self, subsysnqn: str) -> None: self.remove_all_host_key_files(subsysnqn, None) @staticmethod - def construct_key_name_for_keyring(subsysnqn : str, hostnqn : str, prefix : str = None) -> str: + def construct_key_name_for_keyring(subsysnqn: str, hostnqn: str, prefix: str = None) -> str: key_name = hashlib.sha256(subsysnqn.encode()).hexdigest() + "_" + hashlib.sha256(hostnqn.encode()).hexdigest() if prefix: key_name = prefix + "_" + key_name return key_name - def remove_key_from_keyring(self, key_type : str, subsysnqn : str, hostnqn : str) -> None: + def remove_key_from_keyring(self, key_type: str, subsysnqn: str, hostnqn: str) -> None: assert self.rpc_lock.locked(), "RPC is unlocked when calling remove_key_from_keyring()" key_name = GatewayService.construct_key_name_for_keyring(subsysnqn, hostnqn, key_type) try: @@ -512,21 +518,21 @@ def remove_key_from_keyring(self, key_type : str, subsysnqn : str, hostnqn : str except Exception: pass - def remove_psk_key_from_keyring(self, subsysnqn : str, hostnqn : str) -> None: + def remove_psk_key_from_keyring(self, subsysnqn: str, hostnqn: str) -> None: self.remove_key_from_keyring(self.PSK_PREFIX, subsysnqn, hostnqn) - def remove_dhchap_key_from_keyring(self, subsysnqn : str, hostnqn : str) -> None: + def remove_dhchap_key_from_keyring(self, subsysnqn: str, hostnqn: str) -> None: self.remove_key_from_keyring(self.DHCHAP_PREFIX, subsysnqn, hostnqn) - def remove_dhchap_controller_key_from_keyring(self, subsysnqn : str, hostnqn : str) -> None: + def remove_dhchap_controller_key_from_keyring(self, subsysnqn: str, hostnqn: str) -> None: self.remove_key_from_keyring(self.DHCHAP_CONTROLLER_PREFIX, subsysnqn, hostnqn) - def remove_all_host_keys_from_keyring(self, subsysnqn : str, hostnqn : str) -> None: + def remove_all_host_keys_from_keyring(self, subsysnqn: str, hostnqn: str) -> None: self.remove_psk_key_from_keyring(subsysnqn, hostnqn) self.remove_dhchap_key_from_keyring(subsysnqn, hostnqn) self.remove_dhchap_controller_key_from_keyring(subsysnqn, hostnqn) - def remove_all_subsystem_keys_from_keyring(self, subsysnqn : str) -> None: + def remove_all_subsystem_keys_from_keyring(self, subsysnqn: str) -> None: assert self.rpc_lock.locked(), "RPC is unlocked when calling remove_all_subsystem_keys_from_keyring()" try: key_list = rpc_keyring.keyring_get_keys(self.spdk_rpc_client) @@ -544,8 +550,14 @@ def remove_all_subsystem_keys_from_keyring(self, subsysnqn : str) -> None: continue if not key_name or not key_path: continue - if (key_path.startswith(f"{self.KEYS_DIR}/{self.PSK_PREFIX}_{subsysnqn}_") or - key_path.startswith(f"{self.KEYS_DIR}/{self.DHCHAP_PREFIX}_{subsysnqn}_")): + + should_remove = False + if key_path.startswith(f"{self.KEYS_DIR}/{self.PSK_PREFIX}_{subsysnqn}_"): + should_remove = True + elif key_path.startswith(f"{self.KEYS_DIR}/{self.DHCHAP_PREFIX}_{subsysnqn}_"): + should_remove = True + + if should_remove: try: rpc_keyring.keyring_file_remove_key(self.spdk_rpc_client, key_name) except Exception: @@ -567,19 +579,19 @@ def parse_json_exeption(self, ex): try: resp_index = ex.message.find(json_error_text) if resp_index >= 0: - resp_str = ex.message[resp_index + len(json_error_text) :] + resp_str = ex.message[resp_index + len(json_error_text):] resp_index = resp_str.find("response:") if resp_index >= 0: - resp_str = resp_str[resp_index + len("response:") :] + resp_str = resp_str[resp_index + len("response:"):] resp = json.loads(resp_str) except Exception: - self.logger.exception(f"Got exception parsing JSON exception") + self.logger.exception("Got exception parsing JSON exception") pass if resp: if resp["code"] < 0: resp["code"] = -resp["code"] else: - resp={} + resp = {} if "timeout" in ex.message.lower(): resp["code"] = errno.ETIMEDOUT else: @@ -625,13 +637,13 @@ def _put_cluster(self, name: str) -> None: if self.clusters[anagrp][name] == 0: ret = rpc_bdev.bdev_rbd_unregister_cluster( self.spdk_rpc_client, - name = name + name=name ) self.logger.info(f"Free cluster {name=} {ret=}") assert ret self.clusters[anagrp].pop(name) - else : - self.logger.info(f"put_cluster {name=} number bdevs: {self.clusters[anagrp][name]}") + else: + self.logger.info(f"put_cluster {name=} number bdevs: {self.clusters[anagrp][name]}") return assert False, f"Cluster {name} is not found" # we should find the cluster in our state @@ -650,9 +662,9 @@ def _alloc_cluster(self, anagrp: int) -> str: name = self._alloc_cluster_name(anagrp) nonce = rpc_bdev.bdev_rbd_register_cluster( self.spdk_rpc_client, - name = name, - user_id = self.rados_id, - core_mask = self.librbd_core_mask, + name=name, + user_id=self.rados_id, + core_mask=self.librbd_core_mask, ) with self.shared_state_lock: self.logger.info(f"Allocated cluster {name=} {nonce=} {anagrp=}") @@ -680,7 +692,8 @@ def execute_grpc_function(self, func, request, context): return self.omap_lock.execute_omap_locking_function(self._grpc_function_with_lock, func, request, context) - def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, block_size, create_image, rbd_image_size, context, peer_msg = ""): + def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, + block_size, create_image, rbd_image_size, context, peer_msg=""): """Creates a bdev from an RBD image.""" if create_image: @@ -694,7 +707,7 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl if block_size == 0: return BdevStatus(status=errno.EINVAL, - error_message=f"Failure creating bdev {name}: block size can't be zero") + error_message=f"Failure creating bdev {name}: block size can't be zero") if create_image: if rbd_image_size <= 0: @@ -706,7 +719,7 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl rc = self.ceph_utils.pool_exists(rbd_pool_name) if not rc: return BdevStatus(status=errno.ENODEV, - error_message=f"Failure creating bdev {name}: RBD pool {rbd_pool_name} doesn't exist") + error_message=f"Failure creating bdev {name}: RBD pool {rbd_pool_name} doesn't exist") try: rc = self.ceph_utils.create_image(rbd_pool_name, rbd_image_name, rbd_image_size) @@ -730,7 +743,7 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl return BdevStatus(status=errcode, error_message=f"Failure creating bdev {name}: {errmsg}") try: - cluster_name=self._get_cluster(anagrp) + cluster_name = self._get_cluster(anagrp) bdev_name = rpc_bdev.bdev_rbd_create( self.spdk_rpc_client, name=name, @@ -742,7 +755,8 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl ) with self.shared_state_lock: self.bdev_cluster[name] = cluster_name - self.bdev_params[name] = {'uuid':uuid, 'pool_name':rbd_pool_name, 'image_name':rbd_image_name, 'image_size':rbd_image_size, 'block_size': block_size} + self.bdev_params[name] = {'uuid': uuid, 'pool_name': rbd_pool_name, 'image_name': rbd_image_name, + 'image_size': rbd_image_size, 'block_size': block_size} self.logger.debug(f"bdev_rbd_create: {bdev_name}, cluster_name {cluster_name}") except Exception as ex: @@ -767,7 +781,7 @@ def create_bdev(self, anagrp: int, name, uuid, rbd_pool_name, rbd_image_name, bl return BdevStatus(status=0, error_message=os.strerror(0), bdev_name=name) - def resize_bdev(self, bdev_name, new_size, peer_msg = ""): + def resize_bdev(self, bdev_name, new_size, peer_msg=""): """Resizes a bdev.""" self.logger.info(f"Received request to resize bdev {bdev_name} to {new_size} MiB{peer_msg}") @@ -913,7 +927,7 @@ def get_peer_message(self, context) -> str: addr_fam = "" return f", client address: {addr_fam} {addr}" except Exception: - self.logger.exception(f"Got exception trying to get peer's address") + self.logger.exception("Got exception trying to get peer's address") return "" @@ -928,13 +942,13 @@ def create_subsystem_safe(self, request, context): if not request.enable_ha: errmsg = f"{create_subsystem_error_prefix}: HA must be enabled for subsystems" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = errno.EINVAL, error_message = errmsg, nqn = request.subsystem_nqn) + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.EINVAL, error_message=errmsg, nqn=request.subsystem_nqn) if not request.subsystem_nqn: - errmsg = f"Failure creating subsystem, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = errno.EINVAL, error_message = errmsg, nqn = request.subsystem_nqn) + errmsg = "Failure creating subsystem, missing subsystem NQN" + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.EINVAL, error_message=errmsg, nqn=request.subsystem_nqn) if not request.max_namespaces: request.max_namespaces = self.max_namespaces_per_subsystem @@ -947,29 +961,29 @@ def create_subsystem_safe(self, request, context): errmsg = "" if not GatewayState.is_key_element_valid(request.subsystem_nqn): errmsg = f"{create_subsystem_error_prefix}: Invalid NQN \"{request.subsystem_nqn}\", contains invalid characters" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = errno.EINVAL, error_message = errmsg, nqn = request.subsystem_nqn) + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.EINVAL, error_message=errmsg, nqn=request.subsystem_nqn) if self.verify_nqns: rc = GatewayUtils.is_valid_nqn(request.subsystem_nqn) if rc[0] != 0: errmsg = f"{create_subsystem_error_prefix}: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = rc[0], error_message = errmsg, nqn = request.subsystem_nqn) + self.logger.error(errmsg) + return pb2.subsys_status(status=rc[0], error_message=errmsg, nqn=request.subsystem_nqn) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): errmsg = f"{create_subsystem_error_prefix}: Can't create a discovery subsystem" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = errno.EINVAL, error_message = errmsg, nqn = request.subsystem_nqn) + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.EINVAL, error_message=errmsg, nqn=request.subsystem_nqn) if len(self.subsys_max_ns) >= self.max_subsystems: errmsg = f"{create_subsystem_error_prefix}: Maximal number of subsystems ({self.max_subsystems}) has already been reached" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = errno.E2BIG, error_message = errmsg, nqn = request.subsystem_nqn) + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.E2BIG, error_message=errmsg, nqn=request.subsystem_nqn) if context: if request.no_group_append or not self.gateway_group: - self.logger.info(f"Subsystem NQN will not be changed") + self.logger.info("Subsystem NQN will not be changed") else: group_name_to_use = self.gateway_group.replace(GatewayState.OMAP_KEY_DELIMITER, "-") request.subsystem_nqn += f".{group_name_to_use}" @@ -994,15 +1008,15 @@ def create_subsystem_safe(self, request, context): subsys_using_serial = None subsys_already_exists = self.subsystem_already_exists(context, request.subsystem_nqn) if subsys_already_exists: - errmsg = f"Subsystem already exists" + errmsg = "Subsystem already exists" else: subsys_using_serial = self.serial_number_already_used(context, request.serial_number) if subsys_using_serial: errmsg = f"Serial number {request.serial_number} is already used by subsystem {subsys_using_serial}" if subsys_already_exists or subsys_using_serial: errmsg = f"{create_subsystem_error_prefix}: {errmsg}" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status=errno.EEXIST, error_message=errmsg, nqn = request.subsystem_nqn) + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.EEXIST, error_message=errmsg, nqn=request.subsystem_nqn) ret = rpc_nvmf.nvmf_create_subsystem( self.spdk_rpc_client, nqn=request.subsystem_nqn, @@ -1011,7 +1025,7 @@ def create_subsystem_safe(self, request, context): max_namespaces=request.max_namespaces, min_cntlid=min_cntlid, max_cntlid=max_cntlid, - ana_reporting = True, + ana_reporting=True, ) self.subsys_max_ns[request.subsystem_nqn] = request.max_namespaces if request.dhchap_key: @@ -1025,26 +1039,31 @@ def create_subsystem_safe(self, request, context): if resp: status = resp["code"] errmsg = f"{create_subsystem_error_prefix}: {resp['message']}" - return pb2.subsys_status(status=status, error_message=errmsg, nqn = request.subsystem_nqn) + return pb2.subsys_status(status=status, + error_message=errmsg, nqn=request.subsystem_nqn) # Just in case SPDK failed with no exception if not ret: self.logger.error(create_subsystem_error_prefix) - return pb2.subsys_status(status=errno.EINVAL, error_message=create_subsystem_error_prefix, nqn = request.subsystem_nqn) + return pb2.subsys_status(status=errno.EINVAL, + error_message=create_subsystem_error_prefix, + nqn=request.subsystem_nqn) if context: # Update gateway state try: json_req = json_format.MessageToJson( - request, preserving_proto_field_name=True, including_default_value_fields=True) + request, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_subsystem(request.subsystem_nqn, json_req) except Exception as ex: errmsg = f"Error persisting subsystem {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" - return pb2.subsys_status(status=errno.EINVAL, error_message=errmsg, nqn = request.subsystem_nqn) + return pb2.subsys_status(status=errno.EINVAL, + error_message=errmsg, nqn=request.subsystem_nqn) - return pb2.subsys_status(status=0, error_message=os.strerror(0), nqn = request.subsystem_nqn) + return pb2.subsys_status(status=0, error_message=os.strerror(0), nqn=request.subsystem_nqn) def create_subsystem(self, request, context=None): return self.execute_grpc_function(self.create_subsystem_safe, request, context) @@ -1130,8 +1149,9 @@ def delete_subsystem_safe(self, request, context): # Just in case SPDK failed with no exception if not ret: self.logger.error(delete_subsystem_error_prefix) - self.remove_subsystem_from_state( request.subsystem_nqn, context) - return pb2.req_status(status=errno.EINVAL, error_message=delete_subsystem_error_prefix) + self.remove_subsystem_from_state(request.subsystem_nqn, context) + return pb2.req_status(status=errno.EINVAL, + error_message=delete_subsystem_error_prefix) return self.remove_subsystem_from_state(request.subsystem_nqn, context) @@ -1140,45 +1160,52 @@ def delete_subsystem(self, request, context=None): peer_msg = self.get_peer_message(context) delete_subsystem_error_prefix = f"Failure deleting subsystem {request.subsystem_nqn}" - self.logger.info(f"Received request to delete subsystem {request.subsystem_nqn}, context: {context}{peer_msg}") + self.logger.info(f"Received request to delete subsystem {request.subsystem_nqn}, " + f"context: {context}{peer_msg}") if not request.subsystem_nqn: - errmsg = f"Failure deleting subsystem, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = "Failure deleting subsystem, missing subsystem NQN" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if self.verify_nqns: rc = GatewayUtils.is_valid_nqn(request.subsystem_nqn) if rc[0] != 0: errmsg = f"{delete_subsystem_error_prefix}: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): errmsg = f"{delete_subsystem_error_prefix}: Can't delete a discovery subsystem" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) ns_list = [] if context: if self.subsystem_has_listeners(request.subsystem_nqn): - self.logger.warning(f"About to delete subsystem {request.subsystem_nqn} which has a listener defined") + self.logger.warning(f"About to delete subsystem {request.subsystem_nqn} " + f"which has a listener defined") ns_list = self.get_subsystem_namespaces(request.subsystem_nqn) # We found a namespace still using this subsystem and --force wasn't used fail with EBUSY if not request.force and len(ns_list) > 0: - errmsg = f"{delete_subsystem_error_prefix}: Namespace {ns_list[0]} is still using the subsystem. Either remove it or use the '--force' command line option" + errmsg = f"{delete_subsystem_error_prefix}: Namespace {ns_list[0]} is still using " \ + f"the subsystem. Either remove it or use the '--force' command line option" self.logger.error(errmsg) return pb2.req_status(status=errno.EBUSY, error_message=errmsg) for nsid in ns_list: - # We found a namespace still using this subsystem and --force was used so we will try to remove the namespace + # We found a namespace still using this subsystem and --force was used so + # we will try to remove the namespace self.logger.warning(f"Will remove namespace {nsid} from {request.subsystem_nqn}") - ret = self.namespace_delete(pb2.namespace_delete_req(subsystem_nqn=request.subsystem_nqn, nsid=nsid), context) + del_req = pb2.namespace_delete_req(subsystem_nqn=request.subsystem_nqn, nsid=nsid) + ret = self.namespace_delete(del_req, context) if ret.status == 0: - self.logger.info(f"Automatically removed namespace {nsid} from {request.subsystem_nqn}") + self.logger.info(f"Automatically removed namespace {nsid} from " + f"{request.subsystem_nqn}") else: - self.logger.error(f"Failure removing namespace {nsid} from {request.subsystem_nqn}:\n{ret.error_message}") + self.logger.error(f"Failure removing namespace {nsid} from " + f"{request.subsystem_nqn}:\n{ret.error_message}") self.logger.warning(f"Will continue deleting {request.subsystem_nqn} anyway") return self.execute_grpc_function(self.delete_subsystem_safe, request, context) @@ -1197,14 +1224,16 @@ def check_if_image_used(self, pool_name, image_name): ns_image = ns["rbd_image_name"] if pool_name and pool_name == ns_pool and image_name and image_name == ns_image: nqn = ns["subsystem_nqn"] - errmsg = f"RBD image {ns_pool}/{ns_image} is already used by a namespace in subsystem {nqn}" + errmsg = f"RBD image {ns_pool}/{ns_image} is already used by a namespace " \ + f"in subsystem {nqn}" break except Exception: self.logger.exception(f"Got exception while parsing {val}, will continue") continue return errmsg, nqn - def create_namespace(self, subsystem_nqn, bdev_name, nsid, anagrpid, uuid, auto_visible, context): + def create_namespace(self, subsystem_nqn, bdev_name, nsid, anagrpid, uuid, + auto_visible, context): """Adds a namespace to a subsystem.""" if context: @@ -1215,14 +1244,16 @@ def create_namespace(self, subsystem_nqn, bdev_name, nsid, anagrpid, uuid, auto_ nsid_msg = f" using NSID {nsid}" if not subsystem_nqn: - errmsg = f"Failure adding namespace, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.nsid_status(status=errno.EINVAL, error_message = errmsg) + errmsg = "Failure adding namespace, missing subsystem NQN" + self.logger.error(errmsg) + return pb2.nsid_status(status=errno.EINVAL, error_message=errmsg) add_namespace_error_prefix = f"Failure adding namespace{nsid_msg} to {subsystem_nqn}" peer_msg = self.get_peer_message(context) - self.logger.info(f"Received request to add {bdev_name} to {subsystem_nqn} with ANA group id {anagrpid}{nsid_msg}, auto_visible: {auto_visible}, context: {context}{peer_msg}") + self.logger.info(f"Received request to add {bdev_name} to {subsystem_nqn} with ANA group " + f"id {anagrpid}{nsid_msg}, auto_visible: {auto_visible}, " + f"context: {context}{peer_msg}") if subsystem_nqn not in self.subsys_max_ns: errmsg = f"{add_namespace_error_prefix}: No such subsystem" @@ -1230,7 +1261,8 @@ def create_namespace(self, subsystem_nqn, bdev_name, nsid, anagrpid, uuid, auto_ return pb2.nsid_status(status=errno.ENOENT, error_message=errmsg) if anagrpid > self.subsys_max_ns[subsystem_nqn]: - errmsg = f"{add_namespace_error_prefix}: Group ID {anagrpid} is bigger than configured maximum {self.subsys_max_ns[subsystem_nqn]}" + errmsg = f"{add_namespace_error_prefix}: Group ID {anagrpid} is bigger than " \ + f"configured maximum {self.subsys_max_ns[subsystem_nqn]}" self.logger.error(errmsg) return pb2.nsid_status(status=errno.EINVAL, error_message=errmsg) @@ -1239,31 +1271,44 @@ def create_namespace(self, subsystem_nqn, bdev_name, nsid, anagrpid, uuid, auto_ self.logger.error(errmsg) return pb2.nsid_status(status=errno.EINVAL, error_message=errmsg) - if not auto_visible and self.subsystem_nsid_bdev_and_uuid.get_namespace_count(subsystem_nqn, - False, 0) >= self.max_namespaces_with_netmask: - errmsg = f"{add_namespace_error_prefix}: Maximal number of namespaces which are only visible to selected hosts ({self.max_namespaces_with_netmask}) has already been reached" - self.logger.error(f"{errmsg}") - return pb2.req_status(status=errno.E2BIG, error_message=errmsg) + if not auto_visible: + ns_count = self.subsystem_nsid_bdev_and_uuid.get_namespace_count(subsystem_nqn, + False, 0) + if ns_count >= self.max_namespaces_with_netmask: + errmsg = f"{add_namespace_error_prefix}: Maximal number of namespaces which are " \ + f"only visible to selected hosts ({self.max_namespaces_with_netmask}) " \ + f"has already been reached" + self.logger.error(errmsg) + return pb2.req_status(status=errno.E2BIG, error_message=errmsg) if nsid and nsid > self.subsys_max_ns[subsystem_nqn]: - errmsg = f"{add_namespace_error_prefix}: Requested NSID {nsid} is bigger than the maximal one ({self.subsys_max_ns[subsystem_nqn]})" - self.logger.error(f"{errmsg}") + errmsg = f"{add_namespace_error_prefix}: Requested ID {nsid} is bigger than " \ + f"the maximal one ({self.subsys_max_ns[subsystem_nqn]})" + self.logger.error(errmsg) return pb2.req_status(status=errno.E2BIG, error_message=errmsg) - if not nsid and self.subsystem_nsid_bdev_and_uuid.get_namespace_count(subsystem_nqn, - None, 0) >= self.subsys_max_ns[subsystem_nqn]: - errmsg = f"{add_namespace_error_prefix}: Subsystem's maximal number of namespaces ({self.subsys_max_ns[subsystem_nqn]}) has already been reached" - self.logger.error(f"{errmsg}") - return pb2.req_status(status=errno.E2BIG, error_message=errmsg) + if not nsid: + ns_count = self.subsystem_nsid_bdev_and_uuid.get_namespace_count(subsystem_nqn, + None, 0) + if ns_count >= self.subsys_max_ns[subsystem_nqn]: + errmsg = f"{add_namespace_error_prefix}: Subsystem's maximal number of " \ + f"namespaces ({self.subsys_max_ns[subsystem_nqn]}) has " \ + f"already been reached" + self.logger.error(errmsg) + return pb2.req_status(status=errno.E2BIG, error_message=errmsg) - if self.subsystem_nsid_bdev_and_uuid.get_namespace_count(None, None, 0) >= self.max_namespaces: - errmsg = f"{add_namespace_error_prefix}: Maximal number of namespaces ({self.max_namespaces}) has already been reached" - self.logger.error(f"{errmsg}") + ns_count = self.subsystem_nsid_bdev_and_uuid.get_namespace_count(None, None, 0) + if ns_count >= self.max_namespaces: + errmsg = f"{add_namespace_error_prefix}: Maximal number of namespaces " \ + f"({self.max_namespaces}) has already been reached" + self.logger.error(errmsg) return pb2.req_status(status=errno.E2BIG, error_message=errmsg) - if self.subsystem_nsid_bdev_and_uuid.get_namespace_count(subsystem_nqn, None, 0) >= self.subsys_max_ns[subsystem_nqn]: - errmsg = f"{add_namespace_error_prefix}: Maximal number of namespaces per subsystem ({self.subsys_max_ns[subsystem_nqn]}) has already been reached" - self.logger.error(f"{errmsg}") + ns_count = self.subsystem_nsid_bdev_and_uuid.get_namespace_count(subsystem_nqn, None, 0) + if ns_count >= self.subsys_max_ns[subsystem_nqn]: + errmsg = f"{add_namespace_error_prefix}: Maximal number of namespaces per " \ + f"subsystem ({self.subsys_max_ns[subsystem_nqn]}) has already been reached" + self.logger.error(errmsg) return pb2.req_status(status=errno.E2BIG, error_message=errmsg) try: @@ -1276,11 +1321,16 @@ def create_namespace(self, subsystem_nqn, bdev_name, nsid, anagrpid, uuid, auto_ uuid=uuid, no_auto_visible=not auto_visible, ) - self.subsystem_nsid_bdev_and_uuid.add_namespace(subsystem_nqn, nsid, bdev_name, uuid, anagrpid, auto_visible) + self.subsystem_nsid_bdev_and_uuid.add_namespace(subsystem_nqn, nsid, + bdev_name, uuid, + anagrpid, auto_visible) self.logger.debug(f"subsystem_add_ns: {nsid}") self.ana_grp_ns_load[anagrpid] += 1 - if anagrpid in self.ana_grp_subs_load and subsystem_nqn in self.ana_grp_subs_load[anagrpid]: - self.ana_grp_subs_load[anagrpid][subsystem_nqn] += 1 + if anagrpid in self.ana_grp_subs_load: + if subsystem_nqn in self.ana_grp_subs_load[anagrpid]: + self.ana_grp_subs_load[anagrpid][subsystem_nqn] += 1 + else: + self.ana_grp_subs_load[anagrpid][subsystem_nqn] = 1 else: self.ana_grp_subs_load[anagrpid][subsystem_nqn] = 1 except Exception as ex: @@ -1321,8 +1371,8 @@ def set_ana_state_safe(self, ana_info: pb2.ana_info, context=None): # fill the static gateway dictionary per nqn and grp_id nqn = nas.nqn for gs in nas.states: - self.ana_map[nqn][gs.grp_id] = gs.state - self.ana_grp_state[gs.grp_id] = gs.state + self.ana_map[nqn][gs.grp_id] = gs.state + self.ana_grp_state[gs.grp_id] = gs.state # If this is not set the subsystem was not created yet if nqn not in self.subsys_max_ns: @@ -1337,30 +1387,41 @@ def set_ana_state_safe(self, ana_info: pb2.ana_info, context=None): # Access grp_id and state grp_id = gs.grp_id # The gateway's interface gRPC ana_state into SPDK JSON RPC values, - # see nvmf_subsystem_listener_set_ana_state method https://spdk.io/doc/jsonrpc.html - ana_state = "optimized" if gs.state == pb2.ana_state.OPTIMIZED else "inaccessible" + # see nvmf_subsystem_listener_set_ana_state + # method https://spdk.io/doc/jsonrpc.html + if gs.state == pb2.ana_state.OPTIMIZED: + ana_state = "optimized" + else: + ana_state = "inaccessible" try: # Need to wait for the latest OSD map, for each RADOS # cluster context before becoming optimized, # part of blocklist logic if gs.state == pb2.ana_state.OPTIMIZED: # Go over the namespaces belonging to the ana group - for ns_info in self.subsystem_nsid_bdev_and_uuid.get_namespace_infos_for_anagrpid(nqn, grp_id): + ns = self.subsystem_nsid_bdev_and_uuid.get_namespace_infos_for_anagrpid( + nqn, grp_id) + for ns_info in ns: # get the cluster name for this namespace with self.shared_state_lock: cluster = self.bdev_cluster[ns_info.bdev] if not cluster: - raise Exception(f"can not find cluster context name for bdev {ns_info.bdev}") + raise Exception(f"can not find cluster context name for " + f"bdev {ns_info.bdev}") if cluster in awaited_cluster_contexts: # this cluster context was already awaited continue - if not rpc_bdev.bdev_rbd_wait_for_latest_osdmap(self.spdk_rpc_client, name=cluster): - raise Exception(f"bdev_rbd_wait_for_latest_osdmap({cluster=}) error") - self.logger.debug(f"set_ana_state bdev_rbd_wait_for_latest_osdmap {cluster=}") + if not rpc_bdev.bdev_rbd_wait_for_latest_osdmap( + self.spdk_rpc_client, name=cluster): + raise Exception(f"bdev_rbd_wait_for_latest_osdmap({cluster=})" + f" error") + self.logger.debug(f"set_ana_state " + f"bdev_rbd_wait_for_latest_osdmap {cluster=}") awaited_cluster_contexts.add(cluster) - self.logger.debug(f"set_ana_state nvmf_subsystem_listener_set_ana_state {nqn=} {listener=} {ana_state=} {grp_id=}") + self.logger.debug(f"set_ana_state nvmf_subsystem_listener_set_ana_state " + f"{nqn=} {listener=} {ana_state=} {grp_id=}") (adrfam, traddr, trsvcid, secure) = listener ret = rpc_nvmf.nvmf_subsystem_listener_set_ana_state( self.spdk_rpc_client, @@ -1371,11 +1432,13 @@ def set_ana_state_safe(self, ana_info: pb2.ana_info, context=None): adrfam=adrfam, ana_state=ana_state, anagrpid=grp_id) - if ana_state == "inaccessible" : + if ana_state == "inaccessible": inaccessible_ana_groups[grp_id] = True - self.logger.debug(f"set_ana_state nvmf_subsystem_listener_set_ana_state response {ret=}") + self.logger.debug(f"set_ana_state nvmf_subsystem_listener_set_ana_state " + f"response {ret=}") if not ret: - raise Exception(f"nvmf_subsystem_listener_set_ana_state({nqn=}, {listener=}, {ana_state=}, {grp_id=}) error") + raise Exception(f"nvmf_subsystem_listener_set_ana_state({nqn=}, " + f"{listener=}, {ana_state=}, {grp_id=}) error") except Exception as ex: self.logger.exception("nvmf_subsystem_listener_set_ana_state()") if context: @@ -1384,31 +1447,35 @@ def set_ana_state_safe(self, ana_info: pb2.ana_info, context=None): return pb2.req_status() return pb2.req_status(status=True) - def choose_anagrpid_for_namespace(self, nsid) ->int: - grps_list = self.ceph_utils.get_number_created_gateways(self.gateway_pool, self.gateway_group) + def choose_anagrpid_for_namespace(self, nsid) -> int: + grps_list = self.ceph_utils.get_number_created_gateways(self.gateway_pool, + self.gateway_group) for ana_grp in grps_list: - if self.ana_grp_ns_load[ana_grp] == 0: # still no namespaces in this ana-group - probably the new GW added + if self.ana_grp_ns_load[ana_grp] == 0: + # still no namespaces in this ana-group - probably the new GW added self.logger.info(f"New GW created: chosen ana group {ana_grp} for ns {nsid} ") return ana_grp min_load = 2000 chosen_ana_group = 0 for ana_grp in self.ana_grp_ns_load: if ana_grp in grps_list: - self.logger.info(f" ana group {ana_grp} load = {self.ana_grp_ns_load[ana_grp]} ") - if self.ana_grp_ns_load[ana_grp] <= min_load: + self.logger.info(f" ana group {ana_grp} load = {self.ana_grp_ns_load[ana_grp]} ") + if self.ana_grp_ns_load[ana_grp] <= min_load: min_load = self.ana_grp_ns_load[ana_grp] chosen_ana_group = ana_grp - self.logger.info(f" ana group {ana_grp} load = {self.ana_grp_ns_load[ana_grp]} set as min {min_load} ") - self.logger.info(f"Found min loaded cluster: chosen ana group {chosen_ana_group} for ns {nsid} ") + self.logger.info(f" ana group {ana_grp} load = {self.ana_grp_ns_load[ana_grp]}" + f" set as min {min_load} ") + self.logger.info(f"Found min loaded cluster: chosen ana group {chosen_ana_group} " + f"for ID {nsid}") return chosen_ana_group def namespace_add_safe(self, request, context): """Adds a namespace to a subsystem.""" if not request.subsystem_nqn: - errmsg = f"Failure adding namespace, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.nsid_status(status=errno.EINVAL, error_message = errmsg) + errmsg = "Failure adding namespace, missing subsystem NQN" + self.logger.error(errmsg) + return pb2.nsid_status(status=errno.EINVAL, error_message=errmsg) grps_list = [] anagrp = 0 @@ -1416,40 +1483,50 @@ def namespace_add_safe(self, request, context): nsid_msg = "" if request.nsid: nsid_msg = f"{request.nsid} " - self.logger.info(f"Received request to add namespace {nsid_msg}to {request.subsystem_nqn}, ana group {request.anagrpid}, no_auto_visible: {request.no_auto_visible}, context: {context}{peer_msg}") + self.logger.info(f"Received request to add namespace {nsid_msg}to " + f"{request.subsystem_nqn}, ana group {request.anagrpid}, " + f"no_auto_visible: {request.no_auto_visible}, " + f"context: {context}{peer_msg}") if not request.uuid: request.uuid = str(uuid.uuid4()) if context: if request.anagrpid != 0: - grps_list = self.ceph_utils.get_number_created_gateways(self.gateway_pool, self.gateway_group) + grps_list = self.ceph_utils.get_number_created_gateways(self.gateway_pool, + self.gateway_group) else: anagrp = self.choose_anagrpid_for_namespace(request.nsid) assert anagrp != 0, "Chosen ANA group is 0" if request.nsid: - ns = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + ns = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, + request.nsid) if not ns.empty(): errmsg = f"Failure adding namespace, NSID {request.nsid} is already in use" - self.logger.error(f"{errmsg}") - return pb2.nsid_status(status=errno.EEXIST, error_message = errmsg) + self.logger.error(errmsg) + return pb2.nsid_status(status=errno.EEXIST, error_message=errmsg) - ns = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, None, request.uuid) + ns = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, + None, request.uuid) if not ns.empty(): errmsg = f"Failure adding namespace, UUID {request.uuid} is already in use" - self.logger.error(f"{errmsg}") - return pb2.nsid_status(status=errno.EEXIST, error_message = errmsg) + self.logger.error(errmsg) + return pb2.nsid_status(status=errno.EEXIST, error_message=errmsg) omap_lock = self.omap_lock.get_omap_lock_to_use(context) with omap_lock: if context: - errmsg, ns_nqn = self.check_if_image_used(request.rbd_pool_name, request.rbd_image_name) + errmsg, ns_nqn = self.check_if_image_used(request.rbd_pool_name, + request.rbd_image_name) if errmsg and ns_nqn: if request.force: - self.logger.warning(f"{errmsg}, will continue as the \"force\" argument was used") + self.logger.warning(f"{errmsg}, will continue as the \"force\" " + f"argument was used") else: - errmsg = f"{errmsg}, either delete the namespace or use the \"force\" argument,\nyou can find the offending namespace by using the \"namespace list --subsystem {ns_nqn}\" CLI command" + errmsg = f"{errmsg}, either delete the namespace or use the \"force\" " \ + f"argument,\nyou can find the offending namespace by using " \ + f"the \"namespace list --subsystem {ns_nqn}\" CLI command" self.logger.error(errmsg) return pb2.nsid_status(status=errno.EEXIST, error_message=errmsg) @@ -1458,40 +1535,48 @@ def namespace_add_safe(self, request, context): create_image = request.create_image if not context: create_image = False - else: # new namespace + else: # new namespace # If an explicit load balancing group was passed, make sure it exists if request.anagrpid != 0: if request.anagrpid not in grps_list: self.logger.debug(f"ANA groups: {grps_list}") - errmsg = f"Failure adding namespace {nsid_msg}to {request.subsystem_nqn}: Load balancing group {request.anagrpid} doesn't exist" + errmsg = f"Failure adding namespace {nsid_msg}to " \ + f"{request.subsystem_nqn}: Load balancing group " \ + f"{request.anagrpid} doesn't exist" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) else: - request.anagrpid = anagrp + request.anagrpid = anagrp anagrp = request.anagrpid ret_bdev = self.create_bdev(anagrp, bdev_name, request.uuid, request.rbd_pool_name, - request.rbd_image_name, request.block_size, create_image, request.size, context, peer_msg) + request.rbd_image_name, request.block_size, create_image, + request.size, context, peer_msg) if ret_bdev.status != 0: - errmsg = f"Failure adding namespace {nsid_msg}to {request.subsystem_nqn}: {ret_bdev.error_message}" + errmsg = f"Failure adding namespace {nsid_msg}to {request.subsystem_nqn}: " \ + f"{ret_bdev.error_message}" self.logger.error(errmsg) # Delete the bdev unless there was one already there, just to be on the safe side if ret_bdev.status != errno.EEXIST: ns_bdev = self.get_bdev_info(bdev_name) - if ns_bdev != None: + if ns_bdev is not None: try: - ret_del = self.delete_bdev(bdev_name, peer_msg = peer_msg) + ret_del = self.delete_bdev(bdev_name, peer_msg=peer_msg) self.logger.debug(f"delete_bdev({bdev_name}): {ret_del.status}") except AssertionError: - self.logger.exception(f"Got an assert while trying to delete bdev {bdev_name}") + self.logger.exception( + f"Got an assert while trying to delete bdev {bdev_name}") raise except Exception: - self.logger.exception(f"Got exception while trying to delete bdev {bdev_name}") + self.logger.exception( + f"Got exception while trying to delete bdev {bdev_name}") return pb2.nsid_status(status=ret_bdev.status, error_message=errmsg) # If we got here we asserted that ret_bdev.bdev_name == bdev_name - ret_ns = self.create_namespace(request.subsystem_nqn, bdev_name, request.nsid, anagrp, request.uuid, not request.no_auto_visible, context) + ret_ns = self.create_namespace(request.subsystem_nqn, bdev_name, + request.nsid, anagrp, request.uuid, + not request.no_auto_visible, context) if ret_ns.status == 0 and request.nsid and ret_ns.nsid != request.nsid: errmsg = f"Returned NSID {ret_ns.nsid} differs from requested one {request.nsid}" self.logger.error(errmsg) @@ -1500,15 +1585,17 @@ def namespace_add_safe(self, request, context): if ret_ns.status != 0: try: - ret_del = self.delete_bdev(bdev_name, peer_msg = peer_msg) + ret_del = self.delete_bdev(bdev_name, peer_msg=peer_msg) if ret_del.status != 0: - self.logger.warning(f"Failure {ret_del.status} deleting bdev {bdev_name}: {ret_del.error_message}") + self.logger.warning(f"Failure {ret_del.status} deleting bdev " + f"{bdev_name}: {ret_del.error_message}") except AssertionError: self.logger.exception(f"Got an assert while trying to delete bdev {bdev_name}") raise except Exception: self.logger.exception(f"Got exception while trying to delete bdev {bdev_name}") - errmsg = f"Failure adding namespace {nsid_msg}to {request.subsystem_nqn}: {ret_ns.error_message}" + errmsg = f"Failure adding namespace {nsid_msg}to {request.subsystem_nqn}: " \ + f"{ret_ns.error_message}" self.logger.error(errmsg) return pb2.nsid_status(status=ret_ns.status, error_message=errmsg) @@ -1517,7 +1604,8 @@ def namespace_add_safe(self, request, context): request.nsid = ret_ns.nsid try: json_req = json_format.MessageToJson( - request, preserving_proto_field_name=True, including_default_value_fields=True) + request, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_namespace(request.subsystem_nqn, ret_ns.nsid, json_req) except Exception as ex: errmsg = f"Error persisting namespace {nsid_msg}on {request.subsystem_nqn}" @@ -1536,63 +1624,77 @@ def namespace_change_load_balancing_group_safe(self, request, context): grps_list = [] peer_msg = self.get_peer_message(context) - change_lb_group_failure_prefix = f"Failure changing load balancing group for namespace with NSID {request.nsid} in {request.subsystem_nqn}" + change_lb_group_failure_prefix = f"Failure changing load balancing group for namespace " \ + f"with ID {request.nsid} in {request.subsystem_nqn}" auto_lb_msg = "auto" if request.auto_lb_logic else "manual" - self.logger.info(f"Received {auto_lb_msg} request to change load balancing group for namespace with NSID {request.nsid} in {request.subsystem_nqn} to {request.anagrpid}, context: {context}{peer_msg}") + self.logger.info(f"Received {auto_lb_msg} request to change load balancing group for " + f"namespace with ID {request.nsid} in {request.subsystem_nqn} to " + f"{request.anagrpid}, context: {context}{peer_msg}") if not request.subsystem_nqn: - errmsg = f"Failure changing load balancing group for namespace, missing subsystem NQN" - self.logger.error(f"{errmsg}") + errmsg = "Failure changing load balancing group for namespace, missing subsystem NQN" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.nsid: - errmsg = f"Failure changing load balancing group for namespace in {request.subsystem_nqn}: No NSID was given" + errmsg = f"Failure changing load balancing group for namespace in " \ + f"{request.subsystem_nqn}: No namespace ID was given" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) - #below checks are legal only if command is initiated by local cli or is sent from the local rebalance logic. + # below checks are legal only if command is initiated by local cli or is sent from + # the local rebalance logic. if context: - grps_list = self.ceph_utils.get_number_created_gateways(self.gateway_pool, self.gateway_group) + grps_list = self.ceph_utils.get_number_created_gateways( + self.gateway_pool, self.gateway_group) if request.anagrpid not in grps_list: self.logger.debug(f"ANA groups: {grps_list}") - errmsg = f"{change_lb_group_failure_prefix}: Load balancing group {request.anagrpid} doesn't exist" + errmsg = f"{change_lb_group_failure_prefix}: Load balancing group " \ + f"{request.anagrpid} doesn't exist" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace( + request.subsystem_nqn, request.nsid) omap_lock = self.omap_lock.get_omap_lock_to_use(context) with omap_lock: ns_entry = None if context: - # notice that the local state might not be up to date in case we're in the middle of update() but as the - # context is not None, we are not in an update(), the omap lock made sure that we got here with an updated local state + # notice that the local state might not be up to date in case we're in the + # middle of update() but as the context is not None, we are not in an update(), + # the omap lock made sure that we got here with an updated local state state = self.gateway_state.local.get_state() ns_key = GatewayState.build_namespace_key(request.subsystem_nqn, request.nsid) try: state_ns = state[ns_key] ns_entry = json.loads(state_ns) except Exception: - errmsg = f"{change_lb_group_failure_prefix}: Can't find entry for namespace {request.nsid} in {request.subsystem_nqn}" + errmsg = f"{change_lb_group_failure_prefix}: Can't find entry for " \ + f"namespace {request.nsid} in {request.subsystem_nqn}" self.logger.error(errmsg) return pb2.req_status(status=errno.ENOENT, error_message=errmsg) if not request.auto_lb_logic: anagrp = ns_entry["anagrpid"] - gw_id = self.ceph_utils.get_gw_id_owner_ana_group(self.gateway_pool, self.gateway_group, anagrp) - self.logger.debug(f"ANA group of ns#{request.nsid} - {anagrp} is owned by gateway {gw_id}, self.name is {self.gateway_name}") + gw_id = self.ceph_utils.get_gw_id_owner_ana_group( + self.gateway_pool, self.gateway_group, anagrp) + self.logger.debug(f"ANA group of ns#{request.nsid} - {anagrp} is owned by " + f"gateway {gw_id}, self.name is {self.gateway_name}") if self.gateway_name != gw_id: - errmsg = f"ANA group of ns#{request.nsid} - {anagrp} is owned by gateway {gw_id} so try this command from it, this gateway name is {self.gateway_name}" + errmsg = f"ANA group of ns#{request.nsid} - {anagrp} is owned by " \ + f"gateway {gw_id} so try this command from it, this gateway " \ + f"name is {self.gateway_name}" self.logger.error(errmsg) return pb2.req_status(status=errno.EEXIST, error_message=errmsg) try: - anagrpid = self.subsystem_nsid_bdev_and_uuid.get_ana_group_id_by_nsid_subsys(request.subsystem_nqn, request.nsid) + anagrpid = self.subsystem_nsid_bdev_and_uuid.get_ana_group_id_by_nsid_subsys( + request.subsystem_nqn, request.nsid) ret = rpc_nvmf.nvmf_subsystem_set_ns_ana_group( self.spdk_rpc_client, nqn=request.subsystem_nqn, nsid=request.nsid, anagrpid=request.anagrpid, - #transit_anagrpid=0, #temporary for spdk 24.05 ) self.logger.debug(f"nvmf_subsystem_set_ns_ana_group: {ret}") except Exception as ex: @@ -1607,18 +1709,23 @@ def namespace_change_load_balancing_group_safe(self, request, context): # Just in case SPDK failed with no exception if not ret: self.logger.error(change_lb_group_failure_prefix) - return pb2.req_status(status=errno.EINVAL, error_message=change_lb_group_failure_prefix) + return pb2.req_status(status=errno.EINVAL, + error_message=change_lb_group_failure_prefix) # change LB success - need to update the data structures self.ana_grp_ns_load[anagrpid] -= 1 # decrease loading of previous "old" ana group self.ana_grp_subs_load[anagrpid][request.subsystem_nqn] -= 1 self.logger.debug(f"updated load in grp {anagrpid} = {self.ana_grp_ns_load[anagrpid]} ") self.ana_grp_ns_load[request.anagrpid] += 1 - if request.anagrpid in self.ana_grp_subs_load and request.subsystem_nqn in self.ana_grp_subs_load[request.anagrpid]: - self.ana_grp_subs_load[request.anagrpid][request.subsystem_nqn] += 1 + if request.anagrpid in self.ana_grp_subs_load: + if request.subsystem_nqn in self.ana_grp_subs_load[request.anagrpid]: + self.ana_grp_subs_load[request.anagrpid][request.subsystem_nqn] += 1 + else: + self.ana_grp_subs_load[request.anagrpid][request.subsystem_nqn] = 1 else: self.ana_grp_subs_load[request.anagrpid][request.subsystem_nqn] = 1 - self.logger.debug(f"updated load in grp {request.anagrpid} = {self.ana_grp_ns_load[request.anagrpid]} ") - #here update find_ret.set_ana_group_id(request.anagrpid) + self.logger.debug(f"updated load in grp {request.anagrpid} = " + f"{self.ana_grp_ns_load[request.anagrpid]} ") + # here update find_ret.set_ana_group_id(request.anagrpid) if not find_ret.empty(): find_ret.set_ana_group_id(request.anagrpid) @@ -1638,10 +1745,13 @@ def namespace_change_load_balancing_group_safe(self, request, context): force=ns_entry["force"], no_auto_visible=ns_entry["no_auto_visible"]) json_req = json_format.MessageToJson( - add_req, preserving_proto_field_name=True, including_default_value_fields=True) - self.gateway_state.add_namespace(request.subsystem_nqn, request.nsid, json_req) + add_req, preserving_proto_field_name=True, + including_default_value_fields=True) + self.gateway_state.add_namespace(request.subsystem_nqn, + request.nsid, json_req) except Exception as ex: - errmsg = f"Error persisting namespace load balancing group for namespace with NSID {request.nsid} in {request.subsystem_nqn}" + errmsg = f"Error persisting namespace load balancing group for namespace " \ + f"with ID {request.nsid} in {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -1650,7 +1760,8 @@ def namespace_change_load_balancing_group_safe(self, request, context): def namespace_change_load_balancing_group(self, request, context=None): """Changes a namespace load balancing group.""" - return self.execute_grpc_function(self.namespace_change_load_balancing_group_safe, request, context) + return self.execute_grpc_function(self.namespace_change_load_balancing_group_safe, + request, context) def subsystem_has_connections(self, subsys: str) -> bool: assert subsys, "Subsystem NQN is empty" @@ -1666,17 +1777,22 @@ def namespace_change_visibility_safe(self, request, context): """Changes a namespace visibility.""" peer_msg = self.get_peer_message(context) - failure_prefix = f"Failure changing visibility for namespace {request.nsid} in {request.subsystem_nqn}" - vis_txt = "\"visible to all hosts\"" if request.auto_visible else "\"visible to selected hosts\"" - self.logger.info(f"Received request to change the visibility of namespace {request.nsid} in {request.subsystem_nqn} to {vis_txt}, force: {request.force}, context: {context}{peer_msg}") + failure_prefix = f"Failure changing visibility for namespace {request.nsid} " \ + f"in {request.subsystem_nqn}" + vis_txt = "\"visible to all hosts\"" if request.auto_visible else "\"visible " \ + "to selected hosts\"" + self.logger.info(f"Received request to change the visibility of namespace {request.nsid} " + f"in {request.subsystem_nqn} to {vis_txt}, force: {request.force}, " + f"context: {context}{peer_msg}") if not request.subsystem_nqn: - errmsg = f"Failure changing visibility for namespace, missing subsystem NQN" - self.logger.error(f"{errmsg}") + errmsg = "Failure changing visibility for namespace, missing subsystem NQN" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.nsid: - errmsg = f"Failure changing visibility for namespace in {request.subsystem_nqn}: No NSID was given" + errmsg = f"Failure changing visibility for namespace in {request.subsystem_nqn}: " \ + f"No namespace ID was given" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) @@ -1686,7 +1802,8 @@ def namespace_change_visibility_safe(self, request, context): self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace( + request.subsystem_nqn, request.nsid) if find_ret.empty(): errmsg = f"{failure_prefix}: Can't find namespace" self.logger.error(errmsg) @@ -1694,17 +1811,28 @@ def namespace_change_visibility_safe(self, request, context): if find_ret.host_count() > 0 and request.auto_visible: if request.force: - self.logger.warning(f"Asking to change visibility of namespace {request.nsid} in {request.subsystem_nqn} to be visible to all hosts while there are already hosts added to it. Will continue as the \"--force\" parameter was used but these hosts will be removed from the namespace.") + self.logger.warning(f"Asking to change visibility of namespace {request.nsid} " + f"in {request.subsystem_nqn} to be visible to all hosts " + f"while there are already hosts added to it. Will continue " + f"as the \"--force\" parameter was used but these hosts " + f"will be removed from the namespace.") else: - errmsg = f"{failure_prefix}: Asking to change visibility of namespace to be visible to all hosts while there are already hosts added to it. Either remove these hosts or use the \"--force\" parameter" + errmsg = f"{failure_prefix}: Asking to change visibility of namespace to be " \ + f"visible to all hosts while there are already hosts added to it. " \ + f"Either remove these hosts or use the \"--force\" parameter" self.logger.error(errmsg) return pb2.req_status(status=errno.EBUSY, error_message=errmsg) if self.subsystem_has_connections(request.subsystem_nqn): if request.force: - self.logger.warning(f"Asking to change visibility of namespace {request.nsid} in {request.subsystem_nqn} while there are active connections on the subsystem, will continue as the \"--force\" parameter was used.") + self.logger.warning(f"Asking to change visibility of namespace {request.nsid} " + f"in {request.subsystem_nqn} while there are active " + f"connections on the subsystem, will continue as the " + f"\"--force\" parameter was used.") else: - errmsg = f"{failure_prefix}: Asking to change visibility of namespace while there are active connections on the subsystem, please disconnect them or use the \"--force\" parameter." + errmsg = f"{failure_prefix}: Asking to change visibility of namespace while " \ + f"there are active connections on the subsystem, please disconnect " \ + f"them or use the \"--force\" parameter." self.logger.error(errmsg) return pb2.req_status(status=errno.EBUSY, error_message=errmsg) @@ -1712,18 +1840,21 @@ def namespace_change_visibility_safe(self, request, context): with omap_lock: ns_entry = None if context: - # notice that the local state might not be up to date in case we're in the middle of update() but as the - # context is not None, we are not in an update(), the omap lock made sure that we got here with an updated local state + # notice that the local state might not be up to date in case we're in the middle + # of update() but as the context is not None, we are not in an update(), the OMAP + # lock made sure that we got here with an updated local state state = self.gateway_state.local.get_state() ns_key = GatewayState.build_namespace_key(request.subsystem_nqn, request.nsid) try: state_ns = state[ns_key] ns_entry = json.loads(state_ns) if ns_entry["no_auto_visible"] == (not request.auto_visible): - self.logger.warning(f"No change to namespace {request.nsid} in {request.subsystem_nqn} visibility, nothing to do") + self.logger.warning(f"No change to namespace {request.nsid} in " + f"{request.subsystem_nqn} visibility, nothing to do") return pb2.req_status(status=0, error_message=os.strerror(0)) except Exception: - errmsg = f"{failure_prefix}: Can't find entry for namespace {request.nsid} in {request.subsystem_nqn}" + errmsg = f"{failure_prefix}: Can't find entry for namespace {request.nsid} " \ + f"in {request.subsystem_nqn}" self.logger.error(errmsg) return pb2.req_status(status=errno.ENOENT, error_message=errmsg) try: @@ -1735,7 +1866,9 @@ def namespace_change_visibility_safe(self, request, context): ) self.logger.debug(f"nvmf_subsystem_set_ns_visible: {ret}") if request.force and find_ret.host_count() > 0 and request.auto_visible: - self.logger.warning(f"Removing all hosts added to namespace {request.nsid} in {request.subsystem_nqn} as it was set to be visible to all hosts") + self.logger.warning(f"Removing all hosts added to namespace {request.nsid} in " + f"{request.subsystem_nqn} as it was set to be " + f"visible to all hosts") find_ret.remove_all_hosts() find_ret.set_visibility(request.auto_visible) except Exception as ex: @@ -1768,10 +1901,12 @@ def namespace_change_visibility_safe(self, request, context): force=ns_entry["force"], no_auto_visible=not request.auto_visible) json_req = json_format.MessageToJson( - add_req, preserving_proto_field_name=True, including_default_value_fields=True) + add_req, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_namespace(request.subsystem_nqn, request.nsid, json_req) except Exception as ex: - errmsg = f"Error persisting visibility change for namespace {request.nsid} in {request.subsystem_nqn}" + errmsg = f"Error persisting visibility change for namespace " \ + f"{request.nsid} in {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -1787,7 +1922,8 @@ def remove_namespace_from_state(self, nqn, nsid, context): return pb2.req_status(status=0, error_message=os.strerror(0)) # If we got here context is not None, so we must hold the OMAP lock - assert self.omap_lock.locked(), "OMAP is unlocked when calling remove_namespace_from_state()" + assert self.omap_lock.locked(), "OMAP is unlocked when calling " \ + "remove_namespace_from_state()" # Update gateway state try: @@ -1820,11 +1956,13 @@ def remove_namespace(self, subsystem_nqn, nsid, context): assert self.omap_lock.locked(), "OMAP is unlocked when calling remove_namespace()" peer_msg = self.get_peer_message(context) namespace_failure_prefix = f"Failure removing namespace {nsid} from {subsystem_nqn}" - self.logger.info(f"Received request to remove namespace {nsid} from {subsystem_nqn}{peer_msg}") + self.logger.info(f"Received request to remove namespace {nsid} from " + f"{subsystem_nqn}{peer_msg}") if GatewayUtils.is_discovery_nqn(subsystem_nqn): - errmsg=f"{namespace_failure_prefix}: Can't remove a namespace from a discovery subsystem" - self.logger.error(f"{errmsg}") + errmsg = f"{namespace_failure_prefix}: Can't remove a namespace from " \ + f"a discovery subsystem" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) try: @@ -1834,7 +1972,8 @@ def remove_namespace(self, subsystem_nqn, nsid, context): nsid=nsid, ) self.logger.debug(f"remove_namespace {nsid}: {ret}") - anagrpid = self.subsystem_nsid_bdev_and_uuid.get_ana_group_id_by_nsid_subsys(subsystem_nqn, nsid) + anagrpid = self.subsystem_nsid_bdev_and_uuid.get_ana_group_id_by_nsid_subsys( + subsystem_nqn, nsid) self.ana_grp_ns_load[anagrpid] -= 1 self.ana_grp_subs_load[anagrpid][subsystem_nqn] -= 1 @@ -1863,7 +2002,8 @@ def get_bdev_info(self, bdev_name): try: bdevs = rpc_bdev.bdev_get_bdevs(self.spdk_rpc_client, name=bdev_name) if (len(bdevs) > 1): - self.logger.warning(f"Got {len(bdevs)} bdevs for bdev name {bdev_name}, will use the first one") + self.logger.warning(f"Got {len(bdevs)} bdevs for bdev name {bdev_name}, " + f"will use the first one") ret_bdev = bdevs[0] except Exception: self.logger.exception(f"Got exception while getting bdev {bdev_name} info") @@ -1874,29 +2014,31 @@ def list_namespaces(self, request, context=None): """List namespaces.""" peer_msg = self.get_peer_message(context) - if request.nsid == None or request.nsid == 0: + if request.nsid is None or request.nsid == 0: if request.uuid: nsid_msg = f"namespace with UUID {request.uuid}" else: nsid_msg = "all namespaces" else: if request.uuid: - nsid_msg = f"namespace with NSID {request.nsid} and UUID {request.uuid}" + nsid_msg = f"namespace with ID {request.nsid} and UUID {request.uuid}" else: - nsid_msg = f"namespace with NSID {request.nsid}" - self.logger.info(f"Received request to list {nsid_msg} for {request.subsystem}, context: {context}{peer_msg}") + nsid_msg = f"namespace with ID {request.nsid}" + self.logger.info(f"Received request to list {nsid_msg} for {request.subsystem}, " + f"context: {context}{peer_msg}") if not request.subsystem: - errmsg = f"Failure listing namespaces, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.namespaces_info(status=errno.EINVAL, error_message=errmsg, subsystem_nqn=request.subsystem, namespaces=[]) + errmsg = "Failure listing namespaces, missing subsystem NQN" + self.logger.error(errmsg) + return pb2.namespaces_info(status=errno.EINVAL, error_message=errmsg, + subsystem_nqn=request.subsystem, namespaces=[]) with self.rpc_lock: try: ret = rpc_nvmf.nvmf_get_subsystems(self.spdk_rpc_client, nqn=request.subsystem) self.logger.debug(f"list_namespaces: {ret}") except Exception as ex: - errmsg = f"Failure listing namespaces" + errmsg = "Failure listing namespaces" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -1904,13 +2046,15 @@ def list_namespaces(self, request, context=None): if resp: status = resp["code"] errmsg = f"Failure listing namespaces: {resp['message']}" - return pb2.namespaces_info(status=status, error_message=errmsg, subsystem_nqn=request.subsystem, namespaces=[]) + return pb2.namespaces_info(status=status, error_message=errmsg, + subsystem_nqn=request.subsystem, namespaces=[]) namespaces = [] for s in ret: try: if s["nqn"] != request.subsystem: - self.logger.warning(f'Got subsystem {s["nqn"]} instead of {request.subsystem}, ignore') + self.logger.warning(f'Got subsystem {s["nqn"]} instead of ' + f'{request.subsystem}, ignore') continue try: ns_list = s["namespaces"] @@ -1923,29 +2067,35 @@ def list_namespaces(self, request, context=None): nsid = n["nsid"] bdev_name = n["bdev_name"] if request.nsid and request.nsid != n["nsid"]: - self.logger.debug(f'Filter out namespace {n["nsid"]} which is different than requested nsid {request.nsid}') + self.logger.debug(f'Filter out namespace {n["nsid"]} which is ' + f'different than requested nsid {request.nsid}') continue if request.uuid and request.uuid != n["uuid"]: - self.logger.debug(f'Filter out namespace with UUID {n["uuid"]} which is different than requested UUID {request.uuid}') + self.logger.debug(f'Filter out namespace with UUID {n["uuid"]} which is ' + f'different than requested UUID {request.uuid}') continue lb_group = 0 try: lb_group = n["anagrpid"] except KeyError: pass - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem, nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem, + nsid) if find_ret.empty(): - self.logger.warning(f"Can't find info of namesapce {nsid} in {request.subsystem}. Visibility status will be inaccurate") - one_ns = pb2.namespace_cli(nsid = nsid, - bdev_name = bdev_name, - uuid = n["uuid"], - load_balancing_group = lb_group, - auto_visible = find_ret.auto_visible, - hosts = find_ret.host_list) + self.logger.warning(f"Can't find info of namesapce {nsid} in " + f"{request.subsystem}. Visibility status " + f"will be inaccurate") + one_ns = pb2.namespace_cli(nsid=nsid, + bdev_name=bdev_name, + uuid=n["uuid"], + load_balancing_group=lb_group, + auto_visible=find_ret.auto_visible, + hosts=find_ret.host_list) with self.rpc_lock: ns_bdev = self.get_bdev_info(bdev_name) - if ns_bdev == None: - self.logger.warning(f"Can't find namespace's bdev {bdev_name}, will not list bdev's information") + if ns_bdev is None: + self.logger.warning(f"Can't find namespace's bdev {bdev_name}, " + f"will not list bdev's information") else: try: drv_specific_info = ns_bdev["driver_specific"] @@ -1955,15 +2105,16 @@ def list_namespaces(self, request, context=None): one_ns.block_size = ns_bdev["block_size"] one_ns.rbd_image_size = ns_bdev["block_size"] * ns_bdev["num_blocks"] assigned_limits = ns_bdev["assigned_rate_limits"] - one_ns.rw_ios_per_second=assigned_limits["rw_ios_per_sec"] - one_ns.rw_mbytes_per_second=assigned_limits["rw_mbytes_per_sec"] - one_ns.r_mbytes_per_second=assigned_limits["r_mbytes_per_sec"] - one_ns.w_mbytes_per_second=assigned_limits["w_mbytes_per_sec"] + one_ns.rw_ios_per_second = assigned_limits["rw_ios_per_sec"] + one_ns.rw_mbytes_per_second = assigned_limits["rw_mbytes_per_sec"] + one_ns.r_mbytes_per_second = assigned_limits["r_mbytes_per_sec"] + one_ns.w_mbytes_per_second = assigned_limits["w_mbytes_per_sec"] except KeyError as err: - self.logger.warning(f"Key {err} is not found, will not list bdev's information") + self.logger.warning(f"Key {err} is not found, will not list " + f"bdev's information") pass except Exception: - self.logger.exception(f"{ns_bdev=} parse error") + self.logger.exception(f"{ns_bdev=} parse error") pass namespaces.append(one_ns) break @@ -1971,33 +2122,40 @@ def list_namespaces(self, request, context=None): self.logger.exception(f"{s=} parse error") pass - return pb2.namespaces_info(status = 0, error_message = os.strerror(0), subsystem_nqn=request.subsystem, namespaces=namespaces) + return pb2.namespaces_info(status=0, + error_message=os.strerror(0), + subsystem_nqn=request.subsystem, + namespaces=namespaces) def namespace_get_io_stats(self, request, context=None): """Get namespace's IO stats.""" + failure_prefix = f"Failure getting IO stats for namespace {request.nsid} " \ + f"on {request.subsystem_nqn}" peer_msg = self.get_peer_message(context) - self.logger.info(f"Received request to get IO stats for namespace {request.nsid} on {request.subsystem_nqn}, context: {context}{peer_msg}") + self.logger.info(f"Received request to get IO stats for namespace {request.nsid} on " + f"{request.subsystem_nqn}, context: {context}{peer_msg}") if not request.nsid: - errmsg = f"Failure getting IO stats for namespace, missing NSID" - self.logger.error(f"{errmsg}") + errmsg = "Failure getting IO stats for namespace, missing NSID" + self.logger.error(errmsg) return pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=errmsg) if not request.subsystem_nqn: errmsg = f"Failure getting IO stats for namespace {request.nsid}, missing subsystem NQN" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=errmsg) with self.rpc_lock: - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace( + request.subsystem_nqn, request.nsid) if find_ret.empty(): - errmsg = f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}: Can't find namespace" + errmsg = f"{failure_prefix}: Can't find namespace" self.logger.error(errmsg) return pb2.namespace_io_stats_info(status=errno.ENODEV, error_message=errmsg) uuid = find_ret.uuid bdev_name = find_ret.bdev if not bdev_name: - errmsg = f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}: Can't find associated block device" + errmsg = f"{failure_prefix}: Can't find associated block device" self.logger.error(errmsg) return pb2.namespace_io_stats_info(status=errno.ENODEV, error_message=errmsg) @@ -2008,74 +2166,76 @@ def namespace_get_io_stats(self, request, context=None): ) self.logger.debug(f"get_bdev_iostat {bdev_name}: {ret}") except Exception as ex: - errmsg = f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}" - self.logger.exception(errmsg) - errmsg = f"{errmsg}:\n{ex}" + self.logger.exception(failure_prefix) + errmsg = f"{failure_prefix}:\n{ex}" resp = self.parse_json_exeption(ex) status = errno.EINVAL if resp: status = resp["code"] - errmsg = f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}: {resp['message']}" + errmsg = f"{failure_prefix}: {resp['message']}" return pb2.namespace_io_stats_info(status=status, error_message=errmsg) # Just in case SPDK failed with no exception if not ret: - errmsg = f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}" - self.logger.error(errmsg) - return pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=errmsg) + self.logger.error(failure_prefix) + return pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=failure_prefix) exmsg = "" try: bdevs = ret["bdevs"] if not bdevs: - return pb2.namespace_io_stats_info(status=errno.ENODEV, - error_message=f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}: No associated block device found") + return pb2.namespace_io_stats_info( + status=errno.ENODEV, + error_message=f"{failure_prefix}: No associated block device found") if len(bdevs) > 1: - self.logger.warning(f"More than one associated block device found for namespace, will use the first one") + self.logger.warning("More than one associated block device found for namespace, " + "will use the first one") bdev = bdevs[0] io_errs = [] try: - io_error=bdev["io_error"] + io_error = bdev["io_error"] for err_name in io_error.keys(): one_error = pb2.namespace_io_error(name=err_name, value=io_error[err_name]) io_errs.append(one_error) except Exception: - self.logger.exception(f"failure getting io errors") - io_stats = pb2.namespace_io_stats_info(status=0, - error_message=os.strerror(0), - subsystem_nqn=request.subsystem_nqn, - nsid=request.nsid, - uuid=uuid, - bdev_name=bdev_name, - tick_rate=ret["tick_rate"], - ticks=ret["ticks"], - bytes_read=bdev["bytes_read"], - num_read_ops=bdev["num_read_ops"], - bytes_written=bdev["bytes_written"], - num_write_ops=bdev["num_write_ops"], - bytes_unmapped=bdev["bytes_unmapped"], - num_unmap_ops=bdev["num_unmap_ops"], - read_latency_ticks=bdev["read_latency_ticks"], - max_read_latency_ticks=bdev["max_read_latency_ticks"], - min_read_latency_ticks=bdev["min_read_latency_ticks"], - write_latency_ticks=bdev["write_latency_ticks"], - max_write_latency_ticks=bdev["max_write_latency_ticks"], - min_write_latency_ticks=bdev["min_write_latency_ticks"], - unmap_latency_ticks=bdev["unmap_latency_ticks"], - max_unmap_latency_ticks=bdev["max_unmap_latency_ticks"], - min_unmap_latency_ticks=bdev["min_unmap_latency_ticks"], - copy_latency_ticks=bdev["copy_latency_ticks"], - max_copy_latency_ticks=bdev["max_copy_latency_ticks"], - min_copy_latency_ticks=bdev["min_copy_latency_ticks"], - io_error=io_errs) + self.logger.exception("failure getting io errors") + io_stats = pb2.namespace_io_stats_info( + status=0, + error_message=os.strerror(0), + subsystem_nqn=request.subsystem_nqn, + nsid=request.nsid, + uuid=uuid, + bdev_name=bdev_name, + tick_rate=ret["tick_rate"], + ticks=ret["ticks"], + bytes_read=bdev["bytes_read"], + num_read_ops=bdev["num_read_ops"], + bytes_written=bdev["bytes_written"], + num_write_ops=bdev["num_write_ops"], + bytes_unmapped=bdev["bytes_unmapped"], + num_unmap_ops=bdev["num_unmap_ops"], + read_latency_ticks=bdev["read_latency_ticks"], + max_read_latency_ticks=bdev["max_read_latency_ticks"], + min_read_latency_ticks=bdev["min_read_latency_ticks"], + write_latency_ticks=bdev["write_latency_ticks"], + max_write_latency_ticks=bdev["max_write_latency_ticks"], + min_write_latency_ticks=bdev["min_write_latency_ticks"], + unmap_latency_ticks=bdev["unmap_latency_ticks"], + max_unmap_latency_ticks=bdev["max_unmap_latency_ticks"], + min_unmap_latency_ticks=bdev["min_unmap_latency_ticks"], + copy_latency_ticks=bdev["copy_latency_ticks"], + max_copy_latency_ticks=bdev["max_copy_latency_ticks"], + min_copy_latency_ticks=bdev["min_copy_latency_ticks"], + io_error=io_errs) return io_stats except Exception as ex: - self.logger.exception(f"parse error") + self.logger.exception("parse error") exmsg = str(ex) pass return pb2.namespace_io_stats_info(status=errno.EINVAL, - error_message=f"Failure getting IO stats for namespace {request.nsid} on {request.subsystem_nqn}: Error parsing returned stats:\n{exmsg}") + error_message=f"{failure_prefix}: Error " + f"parsing returned stats:\n{exmsg}") def get_qos_limits_string(self, request): limits_to_set = "" @@ -2093,28 +2253,34 @@ def get_qos_limits_string(self, request): def namespace_set_qos_limits_safe(self, request, context): """Set namespace's qos limits.""" + failure_prefix = f"Failure setting QOS limits for namespace {request.nsid} " \ + f"on {request.subsystem_nqn}" peer_msg = self.get_peer_message(context) limits_to_set = self.get_qos_limits_string(request) - self.logger.info(f"Received request to set QOS limits for namespace {request.nsid} on {request.subsystem_nqn},{limits_to_set}, context: {context}{peer_msg}") + self.logger.info(f"Received request to set QOS limits for namespace {request.nsid} " + f"on {request.subsystem_nqn},{limits_to_set}, " + f"context: {context}{peer_msg}") if not request.nsid: - errmsg = f"Failure setting QOS limits for namespace, missing NSID" - self.logger.error(f"{errmsg}") + errmsg = "Failure setting QOS limits for namespace, missing NSID" + self.logger.error(errmsg) return pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=errmsg) if not request.subsystem_nqn: - errmsg = f"Failure setting QOS limits for namespace {request.nsid}, missing subsystem NQN" - self.logger.error(f"{errmsg}") + errmsg = f"Failure setting QOS limits for namespace {request.nsid}, " \ + f"missing subsystem NQN" + self.logger.error(errmsg) return pb2.namespace_io_stats_info(status=errno.EINVAL, error_message=errmsg) - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace( + request.subsystem_nqn, request.nsid) if find_ret.empty(): - errmsg = f"Failure setting QOS limits for namespace {request.nsid} on {request.subsystem_nqn}: Can't find namespace" + errmsg = f"{failure_prefix}: Can't find namespace" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) bdev_name = find_ret.bdev if not bdev_name: - errmsg = f"Failure setting QOS limits for namespace {request.nsid} on {request.subsystem_nqn}: Can't find associated block device" + errmsg = f"{failure_prefix}: Can't find associated block device" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) @@ -2137,21 +2303,29 @@ def namespace_set_qos_limits_safe(self, request, context): state_ns_qos = state[ns_qos_key] ns_qos_entry = json.loads(state_ns_qos) except Exception: - self.logger.info(f"No previous QOS limits found, this is the first time the limits are set for namespace {request.nsid} on {request.subsystem_nqn}") + self.logger.info(f"No previous QOS limits found, this is the first time the " + f"limits are set for namespace {request.nsid} on " + f"{request.subsystem_nqn}") # Merge current limits with previous ones, if exist if ns_qos_entry: - if not request.HasField("rw_ios_per_second") and ns_qos_entry.get("rw_ios_per_second") != None: + if not request.HasField("rw_ios_per_second") and ns_qos_entry.get( + "rw_ios_per_second") is not None: request.rw_ios_per_second = int(ns_qos_entry["rw_ios_per_second"]) - if not request.HasField("rw_mbytes_per_second") and ns_qos_entry.get("rw_mbytes_per_second") != None: + if not request.HasField("rw_mbytes_per_second") and ns_qos_entry.get( + "rw_mbytes_per_second") is not None: request.rw_mbytes_per_second = int(ns_qos_entry["rw_mbytes_per_second"]) - if not request.HasField("r_mbytes_per_second") and ns_qos_entry.get("r_mbytes_per_second") != None: + if not request.HasField("r_mbytes_per_second") and ns_qos_entry.get( + "r_mbytes_per_second") is not None: request.r_mbytes_per_second = int(ns_qos_entry["r_mbytes_per_second"]) - if not request.HasField("w_mbytes_per_second") and ns_qos_entry.get("w_mbytes_per_second") != None: + if not request.HasField("w_mbytes_per_second") and ns_qos_entry.get( + "w_mbytes_per_second") is not None: request.w_mbytes_per_second = int(ns_qos_entry["w_mbytes_per_second"]) limits_to_set = self.get_qos_limits_string(request) - self.logger.debug(f"After merging current QOS limits with previous ones for namespace {request.nsid} on {request.subsystem_nqn},{limits_to_set}") + self.logger.debug(f"After merging current QOS limits with previous ones for " + f"namespace {request.nsid} on {request.subsystem_nqn}," + f"{limits_to_set}") omap_lock = self.omap_lock.get_omap_lock_to_use(context) with omap_lock: @@ -2161,7 +2335,8 @@ def namespace_set_qos_limits_safe(self, request, context): **set_qos_limits_args) self.logger.debug(f"bdev_set_qos_limit {bdev_name}: {ret}") except Exception as ex: - errmsg = f"Failure setting QOS limits for namespace {request.nsid} on {request.subsystem_nqn}" + errmsg = f"Failure setting QOS limits for namespace {request.nsid} " \ + f"on {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -2173,7 +2348,8 @@ def namespace_set_qos_limits_safe(self, request, context): # Just in case SPDK failed with no exception if not ret: - errmsg = f"Failure setting QOS limits for namespace {request.nsid} on {request.subsystem_nqn}" + errmsg = f"Failure setting QOS limits for namespace {request.nsid} " \ + f"on {request.subsystem_nqn}" self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -2181,10 +2357,13 @@ def namespace_set_qos_limits_safe(self, request, context): # Update gateway state try: json_req = json_format.MessageToJson( - request, preserving_proto_field_name=True, including_default_value_fields=True) - self.gateway_state.add_namespace_qos(request.subsystem_nqn, request.nsid, json_req) + request, preserving_proto_field_name=True, + including_default_value_fields=True) + self.gateway_state.add_namespace_qos(request.subsystem_nqn, + request.nsid, json_req) except Exception as ex: - errmsg = f"Error persisting namespace QOS settings {request.nsid} on {request.subsystem_nqn}" + errmsg = f"Error persisting namespace QOS settings {request.nsid} " \ + f"on {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -2198,32 +2377,36 @@ def namespace_set_qos_limits(self, request, context=None): def namespace_resize_safe(self, request, context=None): """Resize a namespace.""" + failure_prefix = f"Failure resizing namespace {request.nsid} on {request.subsystem_nqn}" peer_msg = self.get_peer_message(context) - self.logger.info(f"Received request to resize namespace {request.nsid} on {request.subsystem_nqn} to {request.new_size} MiB, context: {context}{peer_msg}") + self.logger.info(f"Received request to resize namespace {request.nsid} on " + f"{request.subsystem_nqn} to {request.new_size} MiB, context: " + f"{context}{peer_msg}") if not request.nsid: - errmsg = f"Failure resizing namespace, missing NSID" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = "Failure resizing namespace, missing NSID" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.subsystem_nqn: errmsg = f"Failure resizing namespace {request.nsid}, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if request.new_size <= 0: - errmsg = f"Failure resizing namespace {request.nsid}: New size must be positive" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: New size must be positive" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, + request.nsid) if find_ret.empty(): - errmsg = f"Failure resizing namespace {request.nsid} on {request.subsystem_nqn}: Can't find namespace" + errmsg = f"{failure_prefix}: Can't find namespace" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) bdev_name = find_ret.bdev if not bdev_name: - errmsg = f"Failure resizing namespace {request.nsid} on {request.subsystem_nqn}: Can't find associated block device" + errmsg = f"{failure_prefix}: Can't find associated block device" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) @@ -2232,7 +2415,8 @@ def namespace_resize_safe(self, request, context=None): if ret.status == 0: errmsg = os.strerror(0) else: - errmsg = f"Failure resizing namespace {request.nsid} on {request.subsystem_nqn}: {ret.error_message}" + errmsg = f"Failure resizing namespace {request.nsid} on " \ + f"{request.subsystem_nqn}: {ret.error_message}" self.logger.error(errmsg) return pb2.req_status(status=ret.status, error_message=errmsg) @@ -2245,26 +2429,29 @@ def namespace_delete_safe(self, request, context): """Delete a namespace.""" if not request.nsid: - errmsg = f"Failure deleting namespace, missing NSID" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = "Failure deleting namespace, missing NSID" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.subsystem_nqn: errmsg = f"Failure deleting namespace {request.nsid}, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) peer_msg = self.get_peer_message(context) - self.logger.info(f"Received request to delete namespace {request.nsid} from {request.subsystem_nqn}, context: {context}{peer_msg}") + self.logger.info(f"Received request to delete namespace {request.nsid} from " + f"{request.subsystem_nqn}, context: {context}{peer_msg}") - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, + request.nsid) if find_ret.empty(): errmsg = f"Failure deleting namespace {request.nsid}: Can't find namespace" self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) bdev_name = find_ret.bdev if not bdev_name: - self.logger.warning(f"Can't find namespace's bdev name, will try to delete namespace anyway") + self.logger.warning("Can't find namespace's bdev name, will try to " + "delete namespace anyway") omap_lock = self.omap_lock.get_omap_lock_to_use(context) with omap_lock: @@ -2275,9 +2462,10 @@ def namespace_delete_safe(self, request, context): self.remove_namespace_from_state(request.subsystem_nqn, request.nsid, context) self.subsystem_nsid_bdev_and_uuid.remove_namespace(request.subsystem_nqn, request.nsid) if bdev_name: - ret_del = self.delete_bdev(bdev_name, peer_msg = peer_msg) + ret_del = self.delete_bdev(bdev_name, peer_msg=peer_msg) if ret_del.status != 0: - errmsg = f"Failure deleting namespace {request.nsid} from {request.subsystem_nqn}: {ret_del.error_message}" + errmsg = f"Failure deleting namespace {request.nsid} from " \ + f"{request.subsystem_nqn}: {ret_del.error_message}" self.logger.error(errmsg) return pb2.nsid_status(status=ret_del.status, error_message=errmsg) @@ -2291,23 +2479,28 @@ def namespace_add_host_safe(self, request, context): """Add a host to a namespace.""" peer_msg = self.get_peer_message(context) - failure_prefix = f"Failure adding host {request.host_nqn} to namespace {request.nsid} on {request.subsystem_nqn}" - self.logger.info(f"Received request to add host {request.host_nqn} to namespace {request.nsid} on {request.subsystem_nqn}, context: {context}{peer_msg}") + failure_prefix = f"Failure adding host {request.host_nqn} to namespace " \ + f"{request.nsid} on {request.subsystem_nqn}" + self.logger.info(f"Received request to add host {request.host_nqn} to namespace " + f"{request.nsid} on {request.subsystem_nqn}, " + f"context: {context}{peer_msg}") if not request.nsid: - errmsg = f"Failure adding host {request.host_nqn} to namespace on {request.subsystem_nqn}: Missing NSID" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"Failure adding host {request.host_nqn} to namespace on " \ + f"{request.subsystem_nqn}: Missing NSID" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.subsystem_nqn: errmsg = f"Failure adding host to namespace {request.nsid}: Missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.host_nqn: - errmsg = f"Failure adding host to namespace {request.nsid} on {request.subsystem_nqn}: Missing host NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"Failure adding host to namespace {request.nsid} on " \ + f"{request.subsystem_nqn}: Missing host NQN" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) # If this is not set the subsystem was not created yet if request.subsystem_nqn not in self.subsys_max_ns: @@ -2317,32 +2510,33 @@ def namespace_add_host_safe(self, request, context): if request.host_nqn == "*": errmsg = f"{failure_prefix}: Host NQN can't be \"*\"" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if self.verify_nqns: rc = GatewayUtils.is_valid_nqn(request.subsystem_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: Invalid subsystem NQN: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) rc = GatewayUtils.is_valid_nqn(request.host_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: Invalid host NQN: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): errmsg = f"{failure_prefix}: Subsystem NQN can't be a discovery NQN" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.host_nqn): errmsg = f"{failure_prefix}: Host NQN can't be a discovery NQN" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, + request.nsid) if find_ret.empty(): errmsg = f"{failure_prefix}: Can't find namespace" self.logger.error(errmsg) @@ -2350,12 +2544,13 @@ def namespace_add_host_safe(self, request, context): if find_ret.auto_visible: errmsg = f"{failure_prefix}: Namespace is visible to all hosts" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if find_ret.host_count() >= self.max_hosts_per_namespace: - errmsg = f"{failure_prefix}: Maximal host count for namespace ({self.max_hosts_per_namespace}) was already reached" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: Maximal host count for namespace " \ + f"({self.max_hosts_per_namespace}) was already reached" + self.logger.error(errmsg) return pb2.req_status(status=errno.E2BIG, error_message=errmsg) omap_lock = self.omap_lock.get_omap_lock_to_use(context) @@ -2380,10 +2575,13 @@ def namespace_add_host_safe(self, request, context): # Update gateway state try: json_req = json_format.MessageToJson( - request, preserving_proto_field_name=True, including_default_value_fields=True) - self.gateway_state.add_namespace_host(request.subsystem_nqn, request.nsid, request.host_nqn, json_req) + request, preserving_proto_field_name=True, + including_default_value_fields=True) + self.gateway_state.add_namespace_host(request.subsystem_nqn, + request.nsid, request.host_nqn, json_req) except Exception as ex: - errmsg = f"Error persisting host {request.host_nqn} for namespace {request.nsid} on {request.subsystem_nqn}" + errmsg = f"Error persisting host {request.host_nqn} for namespace " \ + f"{request.nsid} on {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -2398,23 +2596,28 @@ def namespace_delete_host_safe(self, request, context): """Delete a host from a namespace.""" peer_msg = self.get_peer_message(context) - failure_prefix = f"Failure deleting host {request.host_nqn} from namespace {request.nsid} on {request.subsystem_nqn}" - self.logger.info(f"Received request to delete host {request.host_nqn} from namespace {request.nsid} on {request.subsystem_nqn}, context: {context}{peer_msg}") + failure_prefix = f"Failure deleting host {request.host_nqn} from namespace " \ + f"{request.nsid} on {request.subsystem_nqn}" + self.logger.info(f"Received request to delete host {request.host_nqn} from namespace " + f"{request.nsid} on {request.subsystem_nqn}, " + f"context: {context}{peer_msg}") if not request.nsid: errmsg = f"Failure deleting host {request.host_nqn} from namespace: Missing NSID" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.subsystem_nqn: - errmsg = f"Failure deleting host {request.host_nqn} from namespace {request.nsid}: Missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"Failure deleting host {request.host_nqn} from namespace " \ + f"{request.nsid}: Missing subsystem NQN" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not request.host_nqn: - errmsg = f"Failure deleting host from namespace {request.nsid} on {request.subsystem_nqn}: Missing host NQN" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"Failure deleting host from namespace {request.nsid} on " \ + f"{request.subsystem_nqn}: Missing host NQN" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) # If this is not set the subsystem was not created yet if request.subsystem_nqn not in self.subsys_max_ns: @@ -2424,32 +2627,33 @@ def namespace_delete_host_safe(self, request, context): if request.host_nqn == "*": errmsg = f"{failure_prefix}: Host NQN can't be \"*\"" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if self.verify_nqns: rc = GatewayUtils.is_valid_nqn(request.subsystem_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: Invalid subsystem NQN: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) rc = GatewayUtils.is_valid_nqn(request.host_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: Invalid host NQN: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): errmsg = f"{failure_prefix}: Subsystem NQN can't be a discovery NQN" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.host_nqn): errmsg = f"{failure_prefix}: Host NQN can't be a discovery NQN" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, request.nsid) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(request.subsystem_nqn, + request.nsid) if find_ret.empty(): errmsg = f"{failure_prefix}: Can't find namespace" self.logger.error(errmsg) @@ -2457,12 +2661,12 @@ def namespace_delete_host_safe(self, request, context): if find_ret.auto_visible: errmsg = f"{failure_prefix}: Namespace is visible to all hosts" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not find_ret.is_host_in_namespace(request.host_nqn): errmsg = f"{failure_prefix}: Host is not found in namespace's host list" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) omap_lock = self.omap_lock.get_omap_lock_to_use(context) @@ -2487,11 +2691,13 @@ def namespace_delete_host_safe(self, request, context): if context: # Update gateway state try: - self.gateway_state.remove_namespace_host(request.subsystem_nqn, request.nsid, request.host_nqn) + self.gateway_state.remove_namespace_host(request.subsystem_nqn, + request.nsid, request.host_nqn) except KeyError: pass except Exception as ex: - errmsg = f"Error persisting deletion of host {request.host_nqn} for namespace {request.nsid} on {request.subsystem_nqn}" + errmsg = f"Error persisting deletion of host {request.host_nqn} for " \ + f"namespace {request.nsid} on {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -2526,30 +2732,35 @@ def get_subsystem_hosts(self, subsys_nqn): pass return hosts - def _create_dhchap_key_files(self, subsystem_nqn, host_nqn, dhchap_key, dhchap_ctrlr_key, err_prefix): + def _create_dhchap_key_files(self, subsystem_nqn, host_nqn, dhchap_key, + dhchap_ctrlr_key, err_prefix): assert dhchap_key, "DH-HMAC-CHAP key value can't be empty" dhchap_file = None dhchap_key_name = None if dhchap_key: dhchap_file = self.create_host_dhchap_file(subsystem_nqn, host_nqn, dhchap_key) if not dhchap_file: - errmsg=f"{err_prefix}: Can't write DH-HMAC-CHAP file" - self.logger.error(f"{errmsg}") + errmsg = f"{err_prefix}: Can't write DH-HMAC-CHAP file" + self.logger.error(errmsg) return (errno.ENOENT, errmsg, None, None, None, None) dhchap_key_name = GatewayService.construct_key_name_for_keyring( - subsystem_nqn, host_nqn, GatewayService.DHCHAP_PREFIX) + subsystem_nqn, + host_nqn, GatewayService.DHCHAP_PREFIX) dhchap_ctrlr_file = None dhchap_ctrlr_key_name = None if dhchap_ctrlr_key: - dhchap_ctrlr_file = self.create_host_dhchap_file(subsystem_nqn, host_nqn, dhchap_ctrlr_key) + dhchap_ctrlr_file = self.create_host_dhchap_file(subsystem_nqn, + host_nqn, dhchap_ctrlr_key) if not dhchap_ctrlr_file: - errmsg=f"{err_prefix}: Can't write DH-HMAC-CHAP controller file" - self.logger.error(f"{errmsg}") + errmsg = f"{err_prefix}: Can't write DH-HMAC-CHAP controller file" + self.logger.error(errmsg) if dhchap_file: self.remove_host_dhchap_file(subsystem_nqn, host_nqn) return (errno.ENOENT, errmsg, None, None, None, None) dhchap_ctrlr_key_name = GatewayService.construct_key_name_for_keyring( - subsystem_nqn, host_nqn, GatewayService.DHCHAP_CONTROLLER_PREFIX) + subsystem_nqn, + host_nqn, + GatewayService.DHCHAP_CONTROLLER_PREFIX) return (0, "", dhchap_file, dhchap_key_name, dhchap_ctrlr_file, dhchap_ctrlr_key_name) @@ -2589,64 +2800,76 @@ def add_host_safe(self, request, context): peer_msg = self.get_peer_message(context) if request.host_nqn == "*": - self.logger.info(f"Received request to allow any host access for {request.subsystem_nqn}, context: {context}{peer_msg}") + self.logger.info(f"Received request to allow any host access for " + f"{request.subsystem_nqn}, context: {context}{peer_msg}") else: self.logger.info( - f"Received request to add host {request.host_nqn} to {request.subsystem_nqn}, psk: {request.psk}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}") + f"Received request to add host {request.host_nqn} to {request.subsystem_nqn}, " + f"psk: {request.psk}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}") - all_host_failure_prefix=f"Failure allowing open host access to {request.subsystem_nqn}" - host_failure_prefix=f"Failure adding host {request.host_nqn} to {request.subsystem_nqn}" + all_host_failure_prefix = f"Failure allowing open host access to {request.subsystem_nqn}" + host_failure_prefix = f"Failure adding host {request.host_nqn} to {request.subsystem_nqn}" if not GatewayState.is_key_element_valid(request.host_nqn): - errmsg = f"{host_failure_prefix}: Invalid host NQN \"{request.host_nqn}\", contains invalid characters" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"{host_failure_prefix}: Invalid host NQN \"{request.host_nqn}\", " \ + f"contains invalid characters" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not GatewayState.is_key_element_valid(request.subsystem_nqn): - errmsg = f"{host_failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\", contains invalid characters" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"{host_failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\"," \ + f" contains invalid characters" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) - if request.host_nqn == "*" and self.host_info.does_subsystem_have_dhchap_key(request.subsystem_nqn): - errmsg=f"{all_host_failure_prefix}: Can't allow any host access on a subsystem having a DH-HMAC-CHAP key" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + if request.host_nqn == "*": + if self.host_info.does_subsystem_have_dhchap_key(request.subsystem_nqn): + errmsg = f"{all_host_failure_prefix}: Can't allow any host access " \ + f"on a subsystem having a DH-HMAC-CHAP key" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if request.host_nqn != "*" and self.host_info.is_any_host_allowed(request.subsystem_nqn): - self.logger.warning(f"A specific host {request.host_nqn} was added to subsystem {request.subsystem_nqn} in which all hosts are allowed") + self.logger.warning(f"A specific host {request.host_nqn} was added to subsystem " + f"{request.subsystem_nqn} in which all hosts are allowed") if self.verify_nqns: rc = GatewayService.is_valid_host_nqn(request.host_nqn) if rc.status != 0: errmsg = f"{host_failure_prefix}: {rc.error_message}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc.status, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc.status, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): if request.host_nqn == "*": - errmsg=f"{all_host_failure_prefix}: Can't allow host access to a discovery subsystem" + errmsg = f"{all_host_failure_prefix}: Can't allow host access " \ + f"to a discovery subsystem" else: - errmsg=f"{host_failure_prefix}: Can't add host to a discovery subsystem" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: Can't add host to a discovery subsystem" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.host_nqn): - errmsg=f"{host_failure_prefix}: Can't use a discovery NQN as host's" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: Can't use a discovery NQN as host's" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if request.psk and request.host_nqn == "*": - errmsg=f"{all_host_failure_prefix}: PSK is only allowed for specific hosts" - self.logger.error(f"{errmsg}") + errmsg = f"{all_host_failure_prefix}: PSK is only allowed for specific hosts" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if request.dhchap_key and request.host_nqn == "*": - errmsg=f"{all_host_failure_prefix}: DH-HMAC-CHAP key is only allowed for specific hosts" - self.logger.error(f"{errmsg}") + errmsg = f"{all_host_failure_prefix}: DH-HMAC-CHAP key is " \ + f"only allowed for specific hosts" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) - if request.dhchap_key and not self.host_info.does_subsystem_have_dhchap_key(request.subsystem_nqn): - self.logger.warning(f"Host {request.host_nqn} has a DH-HMAC-CHAP key but subsystem {request.subsystem_nqn} has no key, a unidirectional authentication will be used") + if request.dhchap_key: + if not self.host_info.does_subsystem_have_dhchap_key(request.subsystem_nqn): + self.logger.warning(f"Host {request.host_nqn} has a DH-HMAC-CHAP key but subsystem " + f"{request.subsystem_nqn} has no key, a unidirectional " + f"authentication will be used") if request.host_nqn == "*": secure = False @@ -2654,50 +2877,60 @@ def add_host_safe(self, request, context): for listener in self.subsystem_listeners[request.subsystem_nqn]: (_, _, _, secure) = listener if secure: - errmsg=f"{all_host_failure_prefix}: Can't allow any host on a subsystem with secure listeners" - self.logger.error(f"{errmsg}") + errmsg = f"{all_host_failure_prefix}: Can't allow open host access " \ + f"on a subsystem with secure listeners" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) except Exception: pass - host_already_exist = self.matching_host_exists(context, request.subsystem_nqn, request.host_nqn) + host_already_exist = self.matching_host_exists(context, + request.subsystem_nqn, request.host_nqn) if host_already_exist: if request.host_nqn == "*": errmsg = f"{all_host_failure_prefix}: Open host access is already allowed" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EEXIST, error_message=errmsg) else: errmsg = f"{host_failure_prefix}: Host is already added" - self.logger.error(f"{errmsg}") + self.logger.error(errmsg) return pb2.req_status(status=errno.EEXIST, error_message=errmsg) - if request.host_nqn != "*" and self.host_info.get_host_count(request.subsystem_nqn) >= self.max_hosts_per_subsystem: - errmsg = f"{host_failure_prefix}: Maximal number of hosts for subsystem ({self.max_hosts_per_subsystem}) has already been reached" - self.logger.error(f"{errmsg}") - return pb2.subsys_status(status = errno.E2BIG, error_message = errmsg, nqn = request.subsystem_nqn) + if request.host_nqn != "*": + if self.host_info.get_host_count(request.subsystem_nqn) >= self.max_hosts_per_subsystem: + errmsg = f"{host_failure_prefix}: Maximal number of hosts for subsystem " \ + f"({self.max_hosts_per_subsystem}) has already been reached" + self.logger.error(errmsg) + return pb2.subsys_status(status=errno.E2BIG, error_message=errmsg, + nqn=request.subsystem_nqn) dhchap_ctrlr_key = self.host_info.get_subsystem_dhchap_key(request.subsystem_nqn) if dhchap_ctrlr_key: - self.logger.info(f"Got DHCHAP key {dhchap_ctrlr_key} for subsystem {request.subsystem_nqn}") + self.logger.info(f"Got DHCHAP key {dhchap_ctrlr_key} for " + f"subsystem {request.subsystem_nqn}") if dhchap_ctrlr_key and not request.dhchap_key: - errmsg=f"{host_failure_prefix}: Host must have a DH-HMAC-CHAP key if the subsystem has one" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: Host must have a DH-HMAC-CHAP " \ + f"key if the subsystem has one" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) psk_file = None psk_key_name = None if request.psk: - psk_file = self.create_host_psk_file(request.subsystem_nqn, request.host_nqn, request.psk) + psk_file = self.create_host_psk_file(request.subsystem_nqn, + request.host_nqn, request.psk) if not psk_file: - errmsg=f"{host_failure_prefix}: Can't write PSK file" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: Can't write PSK file" + self.logger.error(errmsg) return pb2.req_status(status=errno.ENOENT, error_message=errmsg) - psk_key_name = GatewayService.construct_key_name_for_keyring( - request.subsystem_nqn, request.host_nqn, GatewayService.PSK_PREFIX) + psk_key_name = GatewayService.construct_key_name_for_keyring(request.subsystem_nqn, + request.host_nqn, + GatewayService.PSK_PREFIX) if len(psk_key_name) >= SubsystemHostAuth.MAX_PSK_KEY_NAME_LENGTH: - errmsg=f"{host_failure_prefix}: PSK key name {psk_key_name} is too long, max length is {SubsystemHostAuth.MAX_PSK_KEY_NAME_LENGTH}" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: PSK key name {psk_key_name} is too long, " \ + f"max length is {SubsystemHostAuth.MAX_PSK_KEY_NAME_LENGTH}" + self.logger.error(errmsg) return pb2.req_status(status=errno.E2BIG, error_message=errmsg) dhchap_file = None @@ -2711,8 +2944,8 @@ def add_host_safe(self, request, context): dhchap_key_name, dhchap_ctrlr_file, dhchap_ctrlr_key_name) = self._create_dhchap_key_files( - request.subsystem_nqn, request.host_nqn, - request.dhchap_key, dhchap_ctrlr_key, host_failure_prefix) + request.subsystem_nqn, request.host_nqn, + request.dhchap_key, dhchap_ctrlr_key, host_failure_prefix) if key_files_status != 0: if psk_file: self.remove_host_psk_file(request.subsystem_nqn, request.host_nqn) @@ -2732,7 +2965,8 @@ def add_host_safe(self, request, context): else: # Allow single host access to subsystem self._add_key_to_keyring("PSK", psk_file, psk_key_name) self._add_key_to_keyring("DH-HMAC-CHAP", dhchap_file, dhchap_key_name) - self._add_key_to_keyring("DH-HMAC-CHAP controller", dhchap_ctrlr_file, dhchap_ctrlr_key_name) + self._add_key_to_keyring("DH-HMAC-CHAP controller", + dhchap_ctrlr_file, dhchap_ctrlr_key_name) ret = rpc_nvmf.nvmf_subsystem_add_host( self.spdk_rpc_client, nqn=request.subsystem_nqn, @@ -2743,14 +2977,17 @@ def add_host_safe(self, request, context): ) self.logger.debug(f"add_host {request.host_nqn}: {ret}") if psk_file: - self.host_info.add_psk_host(request.subsystem_nqn, request.host_nqn, request.psk) + self.host_info.add_psk_host(request.subsystem_nqn, + request.host_nqn, request.psk) self.remove_host_psk_file(request.subsystem_nqn, request.host_nqn) try: - rpc_keyring.keyring_file_remove_key(self.spdk_rpc_client, psk_key_name) + rpc_keyring.keyring_file_remove_key(self.spdk_rpc_client, + psk_key_name) except Exception: pass if dhchap_file: - self.host_info.add_dhchap_host(request.subsystem_nqn, request.host_nqn, request.dhchap_key) + self.host_info.add_dhchap_host(request.subsystem_nqn, + request.host_nqn, request.dhchap_key) self.host_info.add_host_nqn(request.subsystem_nqn, request.host_nqn) except Exception as ex: if request.host_nqn == "*": @@ -2786,7 +3023,8 @@ def add_host_safe(self, request, context): # Update gateway state try: json_req = json_format.MessageToJson( - request, preserving_proto_field_name=True, including_default_value_fields=True) + request, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_host(request.subsystem_nqn, request.host_nqn, json_req) except Exception as ex: errmsg = f"Error persisting host {request.host_nqn} access addition" @@ -2821,30 +3059,33 @@ def remove_host_safe(self, request, context): """Removes a host from a subsystem.""" peer_msg = self.get_peer_message(context) - all_host_failure_prefix=f"Failure disabling open host access to {request.subsystem_nqn}" - host_failure_prefix=f"Failure removing host {request.host_nqn} access from {request.subsystem_nqn}" + all_host_failure_prefix = f"Failure disabling open host access to {request.subsystem_nqn}" + host_failure_prefix = f"Failure removing host {request.host_nqn} access " \ + f"from {request.subsystem_nqn}" if self.verify_nqns: rc = GatewayService.is_valid_host_nqn(request.host_nqn) if rc.status != 0: errmsg = f"{host_failure_prefix}: {rc.error_message}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc.status, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc.status, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): if request.host_nqn == "*": - errmsg=f"{all_host_failure_prefix}: Can't disable open host access to a discovery subsystem" + errmsg = f"{all_host_failure_prefix}: Can't disable open host access " \ + f"to a discovery subsystem" else: - errmsg=f"{host_failure_prefix}: Can't remove host access from a discovery subsystem" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: Can't remove host access from a " \ + f"discovery subsystem" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.host_nqn): if request.host_nqn == "*": - errmsg=f"{all_host_failure_prefix}: Can't use a discovery NQN as host's" + errmsg = f"{all_host_failure_prefix}: Can't use a discovery NQN as host's" else: - errmsg=f"{host_failure_prefix}: Can't use a discovery NQN as host's" - self.logger.error(f"{errmsg}") + errmsg = f"{host_failure_prefix}: Can't use a discovery NQN as host's" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) omap_lock = self.omap_lock.get_omap_lock_to_use(context) @@ -2914,61 +3155,69 @@ def change_host_key_safe(self, request, context): """Changes host's inband authentication key.""" peer_msg = self.get_peer_message(context) - failure_prefix=f"Failure changing DH-HMAC-CHAP key for host {request.host_nqn} on subsystem {request.subsystem_nqn}" + failure_prefix = f"Failure changing DH-HMAC-CHAP key for host {request.host_nqn} " \ + f"on subsystem {request.subsystem_nqn}" self.logger.info( - f"Received request to change inband authentication key for host {request.host_nqn} on subsystem {request.subsystem_nqn}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}") + f"Received request to change inband authentication key for host {request.host_nqn} " + f"on subsystem {request.subsystem_nqn}, dhchap: {request.dhchap_key}, " + f"context: {context}{peer_msg}") if request.host_nqn == "*": - errmsg=f"{failure_prefix}: Host NQN can't be '*'" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: Host NQN can't be '*'" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not GatewayState.is_key_element_valid(request.host_nqn): - errmsg = f"{failure_prefix}: Invalid host NQN \"{request.host_nqn}\", contains invalid characters" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"{failure_prefix}: Invalid host NQN \"{request.host_nqn}\", " \ + f"contains invalid characters" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not GatewayState.is_key_element_valid(request.subsystem_nqn): - errmsg = f"{failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\", contains invalid characters" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"{failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\", " \ + f"contains invalid characters" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if self.verify_nqns: rc = GatewayUtils.is_valid_nqn(request.subsystem_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) rc = GatewayUtils.is_valid_nqn(request.host_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): - errmsg=f"{failure_prefix}: Can't use a discovery NQN as subsystem's" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: Can't use a discovery NQN as subsystem's" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.host_nqn): - errmsg=f"{failure_prefix}: Can't use a discovery NQN as host's" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: Can't use a discovery NQN as host's" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) dhchap_ctrlr_key = self.host_info.get_subsystem_dhchap_key(request.subsystem_nqn) if dhchap_ctrlr_key and not request.dhchap_key: - errmsg=f"{failure_prefix}: Host must have a DH-HMAC-CHAP key if the subsystem has one" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: Host must have a DH-HMAC-CHAP key if the subsystem has one" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if request.dhchap_key and not dhchap_ctrlr_key: - self.logger.warning(f"Host {request.host_nqn} has a DH-HMAC-CHAP key but subsystem {request.subsystem_nqn} has no key, a unidirectional authentication will be used") + self.logger.warning(f"Host {request.host_nqn} has a DH-HMAC-CHAP key but " + f"subsystem {request.subsystem_nqn} has no key, " + f"a unidirectional authentication will be used") - host_already_exist = self.matching_host_exists(context, request.subsystem_nqn, request.host_nqn) + host_already_exist = self.matching_host_exists(context, request.subsystem_nqn, + request.host_nqn) if not host_already_exist and context: - errmsg=f"{failure_prefix}: Can't find host on subsystem" - self.logger.error(f"{errmsg}") + errmsg = f"{failure_prefix}: Can't find host on subsystem" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) dhchap_file = None @@ -2981,9 +3230,11 @@ def change_host_key_safe(self, request, context): dhchap_file, dhchap_key_name, dhchap_ctrlr_file, - dhchap_ctrlr_key_name) = self._create_dhchap_key_files( - request.subsystem_nqn, request.host_nqn, - request.dhchap_key, dhchap_ctrlr_key, failure_prefix) + dhchap_ctrlr_key_name) = self._create_dhchap_key_files(request.subsystem_nqn, + request.host_nqn, + request.dhchap_key, + dhchap_ctrlr_key, + failure_prefix) if key_files_status != 0: return pb2.req_status(status=key_files_status, error_message=key_file_errmsg) @@ -2996,7 +3247,8 @@ def change_host_key_safe(self, request, context): try: self._add_key_to_keyring("DH-HMAC-CHAP", dhchap_file, dhchap_key_name) - self._add_key_to_keyring("DH-HMAC-CHAP controller", dhchap_ctrlr_file, dhchap_ctrlr_key_name) + self._add_key_to_keyring("DH-HMAC-CHAP controller", + dhchap_ctrlr_file, dhchap_ctrlr_key_name) ret = rpc_nvmf.nvmf_subsystem_set_keys( self.spdk_rpc_client, request.subsystem_nqn, @@ -3022,7 +3274,8 @@ def change_host_key_safe(self, request, context): return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if dhchap_key_name: - self.host_info.add_dhchap_host(request.subsystem_nqn, request.host_nqn, request.dhchap_key) + self.host_info.add_dhchap_host(request.subsystem_nqn, + request.host_nqn, request.dhchap_key) else: self.host_info.remove_dhchap_host(request.subsystem_nqn, request.host_nqn) self.remove_all_host_key_files(request.subsystem_nqn, request.host_nqn) @@ -3036,10 +3289,12 @@ def change_host_key_safe(self, request, context): psk=host_psk, dhchap_key=request.dhchap_key) json_req = json_format.MessageToJson( - add_req, preserving_proto_field_name=True, including_default_value_fields=True) + add_req, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_host(request.subsystem_nqn, request.host_nqn, json_req) except Exception as ex: - errmsg = f"Error persisting host change key for host {request.host_nqn} in {request.subsystem_nqn}" + errmsg = f"Error persisting host change key for host {request.host_nqn}" \ + f" in {request.subsystem_nqn}" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -3054,12 +3309,13 @@ def list_hosts_safe(self, request, context): """List hosts.""" peer_msg = self.get_peer_message(context) - self.logger.info(f"Received request to list hosts for {request.subsystem}, context: {context}{peer_msg}") + self.logger.info(f"Received request to list hosts for " + f"{request.subsystem}, context: {context}{peer_msg}") try: ret = rpc_nvmf.nvmf_get_subsystems(self.spdk_rpc_client, nqn=request.subsystem) self.logger.debug(f"list_hosts: {ret}") except Exception as ex: - errmsg = f"Failure listing hosts, can't get subsystems" + errmsg = "Failure listing hosts, can't get subsystems" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3074,7 +3330,8 @@ def list_hosts_safe(self, request, context): for s in ret: try: if s["nqn"] != request.subsystem: - self.logger.warning(f'Got subsystem {s["nqn"]} instead of {request.subsystem}, ignore') + self.logger.warning(f'Got subsystem {s["nqn"]} instead of ' + f'{request.subsystem}, ignore') continue try: allow_any_host = s["allow_any_host"] @@ -3086,14 +3343,14 @@ def list_hosts_safe(self, request, context): host_nqn = h["nqn"] psk = self.host_info.is_psk_host(request.subsystem, host_nqn) dhchap = self.host_info.is_dhchap_host(request.subsystem, host_nqn) - one_host = pb2.host(nqn = host_nqn, use_psk = psk, use_dhchap = dhchap) + one_host = pb2.host(nqn=host_nqn, use_psk=psk, use_dhchap=dhchap) hosts.append(one_host) break except Exception: self.logger.exception(f"{s=} parse error") pass - return pb2.hosts_info(status = 0, error_message = os.strerror(0), allow_any_host=allow_any_host, + return pb2.hosts_info(status=0, error_message=os.strerror(0), allow_any_host=allow_any_host, subsystem_nqn=request.subsystem, hosts=hosts) def list_hosts(self, request, context=None): @@ -3104,18 +3361,21 @@ def list_connections_safe(self, request, context): peer_msg = self.get_peer_message(context) log_level = logging.INFO if context else logging.DEBUG - self.logger.log(log_level, f"Received request to list connections for {request.subsystem}, context: {context}{peer_msg}") + self.logger.log(log_level, + f"Received request to list connections for {request.subsystem}," + f" context: {context}{peer_msg}") if not request.subsystem: - errmsg = f"Failure listing connections, missing subsystem NQN" - self.logger.error(f"{errmsg}") - return pb2.connections_info(status=errno.EINVAL, error_message = errmsg, connections=[]) + errmsg = "Failure listing connections, missing subsystem NQN" + self.logger.error(errmsg) + return pb2.connections_info(status=errno.EINVAL, error_message=errmsg, connections=[]) try: - qpair_ret = rpc_nvmf.nvmf_subsystem_get_qpairs(self.spdk_rpc_client, nqn=request.subsystem) + qpair_ret = rpc_nvmf.nvmf_subsystem_get_qpairs(self.spdk_rpc_client, + nqn=request.subsystem) self.logger.debug(f"list_connections get_qpairs: {qpair_ret}") except Exception as ex: - errmsg = f"Failure listing connections, can't get qpairs" + errmsg = "Failure listing connections, can't get qpairs" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3126,10 +3386,11 @@ def list_connections_safe(self, request, context): return pb2.connections_info(status=status, error_message=errmsg, connections=[]) try: - ctrl_ret = rpc_nvmf.nvmf_subsystem_get_controllers(self.spdk_rpc_client, nqn=request.subsystem) + ctrl_ret = rpc_nvmf.nvmf_subsystem_get_controllers(self.spdk_rpc_client, + nqn=request.subsystem) self.logger.debug(f"list_connections get_controllers: {ctrl_ret}") except Exception as ex: - errmsg = f"Failure listing connections, can't get controllers" + errmsg = "Failure listing connections, can't get controllers" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3143,7 +3404,7 @@ def list_connections_safe(self, request, context): subsys_ret = rpc_nvmf.nvmf_get_subsystems(self.spdk_rpc_client, nqn=request.subsystem) self.logger.debug(f"list_connections subsystems: {subsys_ret}") except Exception as ex: - errmsg = f"Failure listing connections, can't get subsystems" + errmsg = "Failure listing connections, can't get subsystems" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3158,7 +3419,8 @@ def list_connections_safe(self, request, context): for s in subsys_ret: try: if s["nqn"] != request.subsystem: - self.logger.warning(f'Got subsystem {s["nqn"]} instead of {request.subsystem}, ignore') + self.logger.warning(f'Got subsystem {s["nqn"]} instead of ' + f'{request.subsystem}, ignore') continue try: subsys_hosts = s["hosts"] @@ -3223,7 +3485,8 @@ def list_connections_safe(self, request, context): dhchap = self.host_info.is_dhchap_host(request.subsystem, hostnqn) if request.subsystem in self.subsystem_listeners: - if (adrfam, traddr, trsvcid, True) in self.subsystem_listeners[request.subsystem]: + lstnr = (adrfam, traddr, trsvcid, True) + if lstnr in self.subsystem_listeners[request.subsystem]: secure = True if not trtype: @@ -3231,8 +3494,10 @@ def list_connections_safe(self, request, context): if not adrfam: adrfam = "ipv4" one_conn = pb2.connection(nqn=hostnqn, connected=True, - traddr=traddr, trsvcid=trsvcid, trtype=trtype, adrfam=adrfam, - qpairs_count=conn["num_io_qpairs"], controller_id=conn["cntlid"], + traddr=traddr, trsvcid=trsvcid, + trtype=trtype, adrfam=adrfam, + qpairs_count=conn["num_io_qpairs"], + controller_id=conn["cntlid"], secure=secure, use_psk=psk, use_dhchap=dhchap) connections.append(one_conn) if hostnqn in host_nqns: @@ -3247,11 +3512,12 @@ def list_connections_safe(self, request, context): psk = self.host_info.is_psk_host(request.subsystem, nqn) dhchap = self.host_info.is_dhchap_host(request.subsystem, nqn) one_conn = pb2.connection(nqn=nqn, connected=False, traddr="", trsvcid=0, - qpairs_count=-1, controller_id=-1, use_psk=psk, use_dhchap=dhchap) + qpairs_count=-1, controller_id=-1, + use_psk=psk, use_dhchap=dhchap) connections.append(one_conn) - return pb2.connections_info(status = 0, error_message = os.strerror(0), - subsystem_nqn=request.subsystem, connections=connections) + return pb2.connections_info(status=0, error_message=os.strerror(0), + subsystem_nqn=request.subsystem, connections=connections) def list_connections(self, request, context=None): return self.execute_grpc_function(self.list_connections_safe, request, context) @@ -3260,34 +3526,39 @@ def create_listener_safe(self, request, context): """Creates a listener for a subsystem at a given IP/Port.""" ret = True - create_listener_error_prefix = f"Failure adding {request.nqn} listener at {request.traddr}:{request.trsvcid}" + create_listener_error_prefix = f"Failure adding {request.nqn} listener at " \ + f"{request.traddr}:{request.trsvcid}" adrfam = GatewayEnumUtils.get_key_from_value(pb2.AddressFamily, request.adrfam) - if adrfam == None: - errmsg=f"{create_listener_error_prefix}: Unknown address family {request.adrfam}" - self.logger.error(f"{errmsg}") + if adrfam is None: + errmsg = f"{create_listener_error_prefix}: Unknown address family {request.adrfam}" + self.logger.error(errmsg) return pb2.req_status(status=errno.ENOKEY, error_message=errmsg) peer_msg = self.get_peer_message(context) self.logger.info(f"Received request to create {request.host_name}" f" TCP {adrfam} listener for {request.nqn} at" - f" {request.traddr}:{request.trsvcid}, secure: {request.secure}, context: {context}{peer_msg}") + f" {request.traddr}:{request.trsvcid}, secure: {request.secure}," + f" context: {context}{peer_msg}") traddr = GatewayUtils.unescape_address_if_ipv6(request.traddr, adrfam) if GatewayUtils.is_discovery_nqn(request.nqn): - errmsg=f"{create_listener_error_prefix}: Can't create a listener for a discovery subsystem" - self.logger.error(f"{errmsg}") + errmsg = f"{create_listener_error_prefix}: Can't create a " \ + f"listener for a discovery subsystem" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if not GatewayState.is_key_element_valid(request.host_name): - errmsg=f"{create_listener_error_prefix}: Host name \"{request.host_name}\" contains invalid characters" - self.logger.error(f"{errmsg}") + errmsg = f"{create_listener_error_prefix}: Host name " \ + f"\"{request.host_name}\" contains invalid characters" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if request.secure and self.host_info.is_any_host_allowed(request.nqn): - errmsg=f"{create_listener_error_prefix}: Secure channel is only allowed for subsystems in which \"allow any host\" is off" - self.logger.error(f"{errmsg}") + errmsg = f"{create_listener_error_prefix}: Secure channel is only allowed " \ + f"for subsystems in which \"allow any host\" is off" + self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) add_listener_args = {} @@ -3303,21 +3574,30 @@ def create_listener_safe(self, request, context): with omap_lock: try: if request.host_name == self.host_name: - if (adrfam, traddr, request.trsvcid, False) in self.subsystem_listeners[request.nqn] or (adrfam, traddr, request.trsvcid, True) in self.subsystem_listeners[request.nqn]: - self.logger.error(f"{request.nqn} already listens on address {request.traddr}:{request.trsvcid}") - return pb2.req_status(status=errno.EEXIST, - error_message=f"{create_listener_error_prefix}: Subsystem already listens on this address") - ret = rpc_nvmf.nvmf_subsystem_add_listener(self.spdk_rpc_client, **add_listener_args) + for secure in [False, True]: + lstnr = (adrfam, traddr, request.trsvcid, secure) + if lstnr in self.subsystem_listeners[request.nqn]: + errmsg = f"{create_listener_error_prefix}: Subsystem already " \ + f"listens on address {request.traddr}:{request.trsvcid}" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EEXIST, error_message=errmsg) + + ret = rpc_nvmf.nvmf_subsystem_add_listener(self.spdk_rpc_client, + **add_listener_args) self.logger.debug(f"create_listener: {ret}") - self.subsystem_listeners[request.nqn].add((adrfam, traddr, request.trsvcid, request.secure)) + self.subsystem_listeners[request.nqn].add((adrfam, traddr, + request.trsvcid, request.secure)) else: if context: - errmsg=f"{create_listener_error_prefix}: Gateway's host name must match current host ({self.host_name})" - self.logger.error(f"{errmsg}") + errmsg = f"{create_listener_error_prefix}: Gateway's host name must " \ + f"match current host ({self.host_name})" + self.logger.error(errmsg) return pb2.req_status(status=errno.ENODEV, error_message=errmsg) else: - errmsg=f"Listener not created as gateway's host name {self.host_name} differs from requested host {request.host_name}" - self.logger.debug(f"{errmsg}") + errmsg = f"Listener not created as gateway's host name " \ + f"{self.host_name} differs from requested host " \ + f"{request.host_name}" + self.logger.debug(errmsg) return pb2.req_status(status=0, error_message=errmsg) except Exception as ex: self.logger.exception(create_listener_error_prefix) @@ -3332,56 +3612,66 @@ def create_listener_safe(self, request, context): # Just in case SPDK failed with no exception if not ret: self.logger.error(create_listener_error_prefix) - return pb2.req_status(status=errno.EINVAL, error_message=create_listener_error_prefix) + return pb2.req_status(status=errno.EINVAL, + error_message=create_listener_error_prefix) try: - self.logger.debug(f"create_listener nvmf_subsystem_listener_set_ana_state {request=} set inaccessible for all ana groups") + self.logger.debug(f"create_listener nvmf_subsystem_listener_set_ana_state " + f"{request=} set inaccessible for all ana groups") _ana_state = "inaccessible" ret = rpc_nvmf.nvmf_subsystem_listener_set_ana_state( - self.spdk_rpc_client, - nqn=request.nqn, - ana_state=_ana_state, - trtype="TCP", - traddr=traddr, - trsvcid=str(request.trsvcid), - adrfam=adrfam) - self.logger.debug(f"create_listener nvmf_subsystem_listener_set_ana_state response {ret=}") + self.spdk_rpc_client, + nqn=request.nqn, + ana_state=_ana_state, + trtype="TCP", + traddr=traddr, + trsvcid=str(request.trsvcid), + adrfam=adrfam) + self.logger.debug(f"create_listener " + f"nvmf_subsystem_listener_set_ana_state response {ret=}") # have been provided with ana state for this nqn prior to creation # update optimized ana groups if self.ana_map[request.nqn]: - for x in range (self.subsys_max_ns[request.nqn]): - ana_grp = x+1 - if ana_grp in self.ana_map[request.nqn] and self.ana_map[request.nqn][ana_grp] == pb2.ana_state.OPTIMIZED: - _ana_state = "optimized" - self.logger.debug(f"using ana_map: set listener on nqn : {request.nqn} ana state : {_ana_state} for group : {ana_grp}") - ret = rpc_nvmf.nvmf_subsystem_listener_set_ana_state( - self.spdk_rpc_client, - nqn=request.nqn, - ana_state=_ana_state, - trtype="TCP", - traddr=traddr, - trsvcid=str(request.trsvcid), - adrfam=adrfam, - anagrpid=ana_grp ) - self.logger.debug(f"create_listener nvmf_subsystem_listener_set_ana_state response {ret=}") + for x in range(self.subsys_max_ns[request.nqn]): + ana_grp = x + 1 + if ana_grp in self.ana_map[request.nqn]: + if self.ana_map[request.nqn][ana_grp] == pb2.ana_state.OPTIMIZED: + _ana_state = "optimized" + self.logger.debug(f"using ana_map: set listener on nqn: " + f"{request.nqn} " + f"ana state: {_ana_state} for group: {ana_grp}") + ret = rpc_nvmf.nvmf_subsystem_listener_set_ana_state( + self.spdk_rpc_client, + nqn=request.nqn, + ana_state=_ana_state, + trtype="TCP", + traddr=traddr, + trsvcid=str(request.trsvcid), + adrfam=adrfam, + anagrpid=ana_grp) + self.logger.debug(f"create_listener " + f"nvmf_subsystem_listener_set_ana_state " + f"response {ret=}") except Exception as ex: - errmsg=f"{create_listener_error_prefix}: Error setting ANA state" + errmsg = f"{create_listener_error_prefix}: Error setting ANA state" self.logger.exception(errmsg) - errmsg=f"{errmsg}:\n{ex}" + errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) status = errno.EINVAL if resp: status = resp["code"] - errmsg = f"{create_listener_error_prefix}: Error setting ANA state: {resp['message']}" + errmsg = f"{create_listener_error_prefix}: Error setting ANA state: " \ + f"{resp['message']}" return pb2.req_status(status=status, error_message=errmsg) if context: # Update gateway state try: json_req = json_format.MessageToJson( - request, preserving_proto_field_name=True, including_default_value_fields=True) + request, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_listener(request.nqn, request.host_name, "TCP", request.traddr, @@ -3402,7 +3692,8 @@ def remove_listener_from_state(self, nqn, host_name, traddr, port, context): return pb2.req_status(status=0, error_message=os.strerror(0)) if context: - assert self.omap_lock.locked(), "OMAP is unlocked when calling remove_listener_from_state()" + assert self.omap_lock.locked(), "OMAP is unlocked when calling " \ + "remove_listener_from_state()" host_name = host_name.strip() listener_hosts = [] @@ -3416,7 +3707,8 @@ def remove_listener_from_state(self, nqn, host_name, traddr, port, context): listener = json.loads(val) listener_nqn = listener["nqn"] if listener_nqn != nqn: - self.logger.warning(f"Got subsystem {listener_nqn} instead of {nqn}, ignore") + self.logger.warning(f"Got subsystem {listener_nqn} " + f"instead of {nqn}, ignore") continue if listener["traddr"] != traddr: continue @@ -3435,7 +3727,8 @@ def remove_listener_from_state(self, nqn, host_name, traddr, port, context): try: self.gateway_state.remove_listener(nqn, one_host, "TCP", traddr, port) except Exception as ex: - errmsg = f"Error persisting deletion of {one_host} listener {traddr}:{port} from {nqn}" + errmsg = f"Error persisting deletion of {one_host} listener " \ + f"{traddr}:{port} from {nqn}" self.logger.exception(errmsg) if not req_status: errmsg = f"{errmsg}:\n{ex}" @@ -3450,11 +3743,12 @@ def delete_listener_safe(self, request, context): ret = True esc_traddr = GatewayUtils.escape_address_if_ipv6(request.traddr) - delete_listener_error_prefix = f"Listener {esc_traddr}:{request.trsvcid} failed to delete from {request.nqn}" + delete_listener_error_prefix = f"Listener {esc_traddr}:{request.trsvcid} " \ + f"failed to delete from {request.nqn}" adrfam = GatewayEnumUtils.get_key_from_value(pb2.AddressFamily, request.adrfam) - if adrfam == None: - errmsg=f"{delete_listener_error_prefix}. Unknown address family {request.adrfam}" + if adrfam is None: + errmsg = f"{delete_listener_error_prefix}. Unknown address family {request.adrfam}" self.logger.error(errmsg) return pb2.req_status(status=errno.ENOKEY, error_message=errmsg) @@ -3466,15 +3760,18 @@ def delete_listener_safe(self, request, context): self.logger.info(f"Received request to delete TCP listener of {host_msg}" f" for subsystem {request.nqn} at" - f" {esc_traddr}:{request.trsvcid}{force_msg}, context: {context}{peer_msg}") + f" {esc_traddr}:{request.trsvcid}{force_msg}," + f" context: {context}{peer_msg}") if request.host_name == "*" and not request.force: - errmsg=f"{delete_listener_error_prefix}. Must use the \"--force\" parameter when setting the host name to \"*\"." + errmsg = f"{delete_listener_error_prefix}. Must use the \"--force\"" \ + f" parameter when setting the host name to \"*\"." self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.nqn): - errmsg=f"{delete_listener_error_prefix}. Can't delete a listener from a discovery subsystem" + errmsg = f"{delete_listener_error_prefix}. " \ + f"Can't delete a listener from a discovery subsystem" self.logger.error(errmsg) return pb2.req_status(status=errno.EINVAL, error_message=errmsg) @@ -3482,7 +3779,8 @@ def delete_listener_safe(self, request, context): list_conn_req = pb2.list_connections_req(subsystem=request.nqn) list_conn_ret = self.list_connections_safe(list_conn_req, context) if list_conn_ret.status != 0: - errmsg=f"{delete_listener_error_prefix}. Can't verify there are no active connections for this address" + errmsg = f"{delete_listener_error_prefix}. " \ + f"Can't verify there are no active connections for this address" self.logger.error(errmsg) return pb2.req_status(status=errno.ENOTEMPTY, error_message=errmsg) for conn in list_conn_ret.connections: @@ -3492,7 +3790,10 @@ def delete_listener_safe(self, request, context): continue if conn.trsvcid != request.trsvcid: continue - errmsg=f"{delete_listener_error_prefix} due to active connections for {esc_traddr}:{request.trsvcid}. Deleting the listener terminates active connections. You can continue to delete the listener by adding the `--force` parameter." + errmsg = f"{delete_listener_error_prefix} due to active connections for " \ + f"{esc_traddr}:{request.trsvcid}. Deleting the listener terminates " \ + f"active connections. You can continue to delete the listener by " \ + f"adding the `--force` parameter." self.logger.error(errmsg) return pb2.req_status(status=errno.ENOTEMPTY, error_message=errmsg) @@ -3510,17 +3811,24 @@ def delete_listener_safe(self, request, context): ) self.logger.debug(f"delete_listener: {ret}") if request.nqn in self.subsystem_listeners: - if (adrfam, traddr, request.trsvcid, False) in self.subsystem_listeners[request.nqn]: - self.subsystem_listeners[request.nqn].remove((adrfam, traddr, request.trsvcid, False)) - if (adrfam, traddr, request.trsvcid, True) in self.subsystem_listeners[request.nqn]: - self.subsystem_listeners[request.nqn].remove((adrfam, traddr, request.trsvcid, True)) + if (adrfam, traddr, request.trsvcid, + False) in self.subsystem_listeners[request.nqn]: + self.subsystem_listeners[request.nqn].remove((adrfam, traddr, + request.trsvcid, False)) + if (adrfam, traddr, request.trsvcid, + True) in self.subsystem_listeners[request.nqn]: + self.subsystem_listeners[request.nqn].remove((adrfam, traddr, + request.trsvcid, True)) else: - errmsg=f"{delete_listener_error_prefix}. Gateway's host name must match current host ({self.host_name}). You can continue to delete the listener by adding the `--force` parameter." - self.logger.error(f"{errmsg}") + errmsg = f"{delete_listener_error_prefix}. Gateway's host name must " \ + f"match current host ({self.host_name}). You can continue to " \ + f"delete the listener by adding the `--force` parameter." + self.logger.error(errmsg) return pb2.req_status(status=errno.ENOENT, error_message=errmsg) except Exception as ex: self.logger.exception(delete_listener_error_prefix) - # It's OK for SPDK to fail in case we used a different host name, just continue to remove from OMAP + # It's OK for SPDK to fail in case we used a different host name, + # just continue to remove from OMAP if request.host_name == self.host_name: errmsg = f"{delete_listener_error_prefix}:\n{ex}" self.remove_listener_from_state(request.nqn, request.host_name, @@ -3538,7 +3846,8 @@ def delete_listener_safe(self, request, context): self.logger.error(delete_listener_error_prefix) self.remove_listener_from_state(request.nqn, request.host_name, traddr, request.trsvcid, context) - return pb2.req_status(status=errno.EINVAL, error_message=delete_listener_error_prefix) + return pb2.req_status(status=errno.EINVAL, + error_message=delete_listener_error_prefix) return self.remove_listener_from_state(request.nqn, request.host_name, traddr, request.trsvcid, context) @@ -3550,7 +3859,8 @@ def list_listeners_safe(self, request, context): """List listeners.""" peer_msg = self.get_peer_message(context) - self.logger.info(f"Received request to list listeners for {request.subsystem}, context: {context}{peer_msg}") + self.logger.info(f"Received request to list listeners for {request.subsystem}, " + f"context: {context}{peer_msg}") listeners = [] omap_lock = self.omap_lock.get_omap_lock_to_use(context) @@ -3564,23 +3874,24 @@ def list_listeners_safe(self, request, context): listener = json.loads(val) nqn = listener["nqn"] if nqn != request.subsystem: - self.logger.warning(f"Got subsystem {nqn} instead of {request.subsystem}, ignore") + self.logger.warning(f"Got subsystem {nqn} instead of " + f"{request.subsystem}, ignore") continue secure = False if "secure" in listener: secure = listener["secure"] - one_listener = pb2.listener_info(host_name = listener["host_name"], - trtype = "TCP", - adrfam = listener["adrfam"], - traddr = listener["traddr"], - trsvcid = listener["trsvcid"], - secure = secure) + one_listener = pb2.listener_info(host_name=listener["host_name"], + trtype="TCP", + adrfam=listener["adrfam"], + traddr=listener["traddr"], + trsvcid=listener["trsvcid"], + secure=secure) listeners.append(one_listener) except Exception: self.logger.exception(f"Got exception while parsing {val}") continue - return pb2.listeners_info(status = 0, error_message = os.strerror(0), listeners=listeners) + return pb2.listeners_info(status=0, error_message=os.strerror(0), listeners=listeners) def list_listeners(self, request, context=None): return self.execute_grpc_function(self.list_listeners_safe, request, context) @@ -3591,12 +3902,18 @@ def list_subsystems_safe(self, request, context): peer_msg = self.get_peer_message(context) log_level = logging.INFO if context else logging.DEBUG if request.subsystem_nqn: - self.logger.log(log_level, f"Received request to list subsystem {request.subsystem_nqn}, context: {context}{peer_msg}") + self.logger.log(log_level, + f"Received request to list subsystem {request.subsystem_nqn}, " + f"context: {context}{peer_msg}") else: if request.serial_number: - self.logger.log(log_level, f"Received request to list the subsystem with serial number {request.serial_number}, context: {context}{peer_msg}") + self.logger.log(log_level, + f"Received request to list the subsystem with serial number " + f"{request.serial_number}, context: {context}{peer_msg}") else: - self.logger.log(log_level, f"Received request to list all subsystems, context: {context}{peer_msg}") + self.logger.log(log_level, + f"Received request to list all subsystems, context: " + f"{context}{peer_msg}") subsystems = [] try: @@ -3606,7 +3923,7 @@ def list_subsystems_safe(self, request, context): ret = rpc_nvmf.nvmf_get_subsystems(self.spdk_rpc_client) self.logger.debug(f"list_subsystems: {ret}") except Exception as ex: - errmsg = f"Failure listing subsystems" + errmsg = "Failure listing subsystems" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3640,7 +3957,8 @@ def list_subsystems_safe(self, request, context): self.logger.exception(f"{s=} parse error") pass - return pb2.subsystems_info_cli(status = 0, error_message = os.strerror(0), subsystems=subsystems) + return pb2.subsystems_info_cli(status=0, error_message=os.strerror(0), + subsystems=subsystems) def get_subsystems_safe(self, request, context): """Gets subsystems.""" @@ -3651,7 +3969,7 @@ def get_subsystems_safe(self, request, context): try: ret = rpc_nvmf.nvmf_get_subsystems(self.spdk_rpc_subsystems_client) except Exception as ex: - self.logger.exception(f"get_subsystems failed") + self.logger.exception("get_subsystems failed") context.set_code(grpc.StatusCode.INTERNAL) context.set_details(f"{ex}") return pb2.subsystems_info() @@ -3666,7 +3984,8 @@ def get_subsystems_safe(self, request, context): with self.shared_state_lock: nonce = self.cluster_nonce[self.bdev_cluster[bdev]] n["nonce"] = nonce - find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(s["nqn"], n["nsid"]) + find_ret = self.subsystem_nsid_bdev_and_uuid.find_namespace(s["nqn"], + n["nsid"]) n["auto_visible"] = find_ret.auto_visible n["hosts"] = find_ret.host_list # Parse the JSON dictionary into the protobuf message @@ -3689,33 +4008,36 @@ def list_subsystems(self, request, context=None): def change_subsystem_key_safe(self, request, context): """Change subsystem key.""" peer_msg = self.get_peer_message(context) - failure_prefix=f"Failure changing DH-HMAC-CHAP key for subsystem {request.subsystem_nqn}" + failure_prefix = f"Failure changing DH-HMAC-CHAP key for subsystem {request.subsystem_nqn}" self.logger.info( - f"Received request to change inband authentication key for subsystem {request.subsystem_nqn}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}") + f"Received request to change inband authentication key for subsystem " + f"{request.subsystem_nqn}, dhchap: {request.dhchap_key}, context: {context}{peer_msg}") if not GatewayState.is_key_element_valid(request.subsystem_nqn): - errmsg = f"{failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\", contains invalid characters" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"{failure_prefix}: Invalid subsystem NQN \"{request.subsystem_nqn}\"," \ + f" contains invalid characters" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) if self.verify_nqns: rc = GatewayUtils.is_valid_nqn(request.subsystem_nqn) if rc[0] != 0: errmsg = f"{failure_prefix}: {rc[1]}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = rc[0], error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=rc[0], error_message=errmsg) if GatewayUtils.is_discovery_nqn(request.subsystem_nqn): errmsg = f"{failure_prefix}: Can't change DH-HMAC-CHAP key for a discovery subsystem" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) omap_lock = self.omap_lock.get_omap_lock_to_use(context) with omap_lock: subsys_entry = None if context: - # notice that the local state might not be up to date in case we're in the middle of update() but as the - # context is not None, we are not in an update(), the omap lock made sure that we got here with an updated local state + # notice that the local state might not be up to date in case we're in the middle + # of update() but as the context is not None, we are not in an update(), the OMAP + # lock made sure that we got here with an updated local state state = self.gateway_state.local.get_state() if request.dhchap_key: # We set the subsystem key, this requires that all hosts have keys too @@ -3723,29 +4045,33 @@ def change_subsystem_key_safe(self, request, context): for hostnqn in all_subsys_hosts: assert hostnqn, "Shouldn't get an empty host NQN" if not self.host_info.is_dhchap_host(request.subsystem_nqn, hostnqn): - errmsg = f"{failure_prefix}: Can't set a subsystem's DH-HMAC-CHAP key when it has hosts with no key, like host {hostnqn}" - self.logger.error(f"{errmsg}") - return pb2.req_status(status = errno.EINVAL, error_message = errmsg) + errmsg = f"{failure_prefix}: Can't set a subsystem's DH-HMAC-CHAP " \ + f"key when it has hosts with no key, like host {hostnqn}" + self.logger.error(errmsg) + return pb2.req_status(status=errno.EINVAL, error_message=errmsg) subsys_key = GatewayState.build_subsystem_key(request.subsystem_nqn) try: state_subsys = state[subsys_key] subsys_entry = json.loads(state_subsys) except Exception: - errmsg = f"{failure_prefix}: Can't find entry for subsystem {request.subsystem_nqn}" + errmsg = f"{failure_prefix}: Can't find entry for subsystem " \ + f"{request.subsystem_nqn}" self.logger.error(errmsg) return pb2.req_status(status=errno.ENOENT, error_message=errmsg) assert subsys_entry, f"Can't find entry for subsystem {request.subsystem_nqn}" try: - create_req = pb2.create_subsystem_req(subsystem_nqn=request.subsystem_nqn, - serial_number=subsys_entry["serial_number"], - max_namespaces=subsys_entry["max_namespaces"], - enable_ha=subsys_entry["enable_ha"], - no_group_append=subsys_entry["no_group_append"], - dhchap_key=request.dhchap_key) + create_req = pb2.create_subsystem_req( + subsystem_nqn=request.subsystem_nqn, + serial_number=subsys_entry["serial_number"], + max_namespaces=subsys_entry["max_namespaces"], + enable_ha=subsys_entry["enable_ha"], + no_group_append=subsys_entry["no_group_append"], + dhchap_key=request.dhchap_key) json_req = json_format.MessageToJson( - create_req, preserving_proto_field_name=True, including_default_value_fields=True) + create_req, preserving_proto_field_name=True, + including_default_value_fields=True) self.gateway_state.add_subsystem(request.subsystem_nqn, json_req) except Exception as ex: errmsg = f"Error persisting subsystem key change for {request.subsystem_nqn}" @@ -3754,7 +4080,8 @@ def change_subsystem_key_safe(self, request, context): return pb2.req_status(status=errno.EINVAL, error_message=errmsg) hosts = self.host_info.get_hosts_with_dhchap_key(request.subsystem_nqn).copy() - # We need to change the subsystem key before calling the host change key functions, so the new subsystem key will be used + # We need to change the subsystem key before calling the host change key functions, + # so the new subsystem key will be used # As we change the list now, we have to use a copy having the old values if request.dhchap_key: self.host_info.add_dhchap_key_to_subsystem(request.subsystem_nqn, request.dhchap_key) @@ -3769,7 +4096,6 @@ def change_subsystem_key_safe(self, request, context): except Exception: pass - return pb2.req_status(status=0, error_message=os.strerror(0)) def change_subsystem_key(self, request, context=None): @@ -3785,15 +4111,15 @@ def get_spdk_nvmf_log_flags_and_level_safe(self, request, context): nvmf_log_flags = {key: value for key, value in rpc_log.log_get_flags( self.spdk_rpc_client).items() if key.startswith('nvmf')} for flag, flagvalue in nvmf_log_flags.items(): - pb2_log_flag = pb2.spdk_log_flag_info(name = flag, enabled = flagvalue) + pb2_log_flag = pb2.spdk_log_flag_info(name=flag, enabled=flagvalue) log_flags.append(pb2_log_flag) spdk_log_level = rpc_log.log_get_level(self.spdk_rpc_client) spdk_log_print_level = rpc_log.log_get_print_level(self.spdk_rpc_client) - self.logger.debug(f"spdk log flags: {nvmf_log_flags}, " - f"spdk log level: {spdk_log_level}, " - f"spdk log print level: {spdk_log_print_level}") + self.logger.debug(f"spdk log flags: {nvmf_log_flags}, " + f"spdk log level: {spdk_log_level}, " + f"spdk log print level: {spdk_log_print_level}") except Exception as ex: - errmsg = f"Failure getting SPDK log levels and nvmf log flags" + errmsg = "Failure getting SPDK log levels and nvmf log flags" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3801,17 +4127,18 @@ def get_spdk_nvmf_log_flags_and_level_safe(self, request, context): if resp: status = resp["code"] errmsg = f"Failure getting SPDK log levels and nvmf log flags: {resp['message']}" - return pb2.spdk_nvmf_log_flags_and_level_info(status = status, error_message = errmsg) + return pb2.spdk_nvmf_log_flags_and_level_info(status=status, error_message=errmsg) return pb2.spdk_nvmf_log_flags_and_level_info( nvmf_log_flags=log_flags, - log_level = spdk_log_level, - log_print_level = spdk_log_print_level, - status = 0, - error_message = os.strerror(0)) + log_level=spdk_log_level, + log_print_level=spdk_log_print_level, + status=0, + error_message=os.strerror(0)) def get_spdk_nvmf_log_flags_and_level(self, request, context=None): - return self.execute_grpc_function(self.get_spdk_nvmf_log_flags_and_level_safe, request, context) + return self.execute_grpc_function(self.get_spdk_nvmf_log_flags_and_level_safe, + request, context) def set_spdk_nvmf_logs_safe(self, request, context): """Enables spdk nvmf logs""" @@ -3823,36 +4150,38 @@ def set_spdk_nvmf_logs_safe(self, request, context): peer_msg = self.get_peer_message(context) if request.HasField("log_level"): log_level = GatewayEnumUtils.get_key_from_value(pb2.LogLevel, request.log_level) - if log_level == None: - errmsg=f"Unknown log level {request.log_level}" - self.logger.error(f"{errmsg}") + if log_level is None: + errmsg = f"Unknown log level {request.log_level}" + self.logger.error(errmsg) return pb2.req_status(status=errno.ENOKEY, error_message=errmsg) if request.HasField("print_level"): print_level = GatewayEnumUtils.get_key_from_value(pb2.LogLevel, request.print_level) - if print_level == None: - errmsg=f"Unknown print level {request.print_level}" - self.logger.error(f"{errmsg}") + if print_level is None: + errmsg = f"Unknown print level {request.print_level}" + self.logger.error(errmsg) return pb2.req_status(status=errno.ENOKEY, error_message=errmsg) - self.logger.info(f"Received request to set SPDK nvmf logs: log_level: {log_level}, print_level: {print_level}{peer_msg}") + self.logger.info(f"Received request to set SPDK nvmf logs: log_level: {log_level}, " + f"print_level: {print_level}{peer_msg}") try: - nvmf_log_flags = [key for key in rpc_log.log_get_flags(self.spdk_rpc_client).keys() if key.startswith('nvmf')] + nvmf_log_flags = [key for key in rpc_log.log_get_flags(self.spdk_rpc_client).keys() + if key.startswith('nvmf')] ret = [rpc_log.log_set_flag( self.spdk_rpc_client, flag=flag) for flag in nvmf_log_flags] self.logger.debug(f"Set SPDK nvmf log flags {nvmf_log_flags} to TRUE: {ret}") - if log_level != None: + if log_level is not None: ret_log = rpc_log.log_set_level(self.spdk_rpc_client, level=log_level) self.logger.debug(f"Set log level to {log_level}: {ret_log}") - if print_level != None: + if print_level is not None: ret_print = rpc_log.log_set_print_level( self.spdk_rpc_client, level=print_level) self.logger.debug(f"Set log print level to {print_level}: {ret_print}") except Exception as ex: - errmsg="Failure setting SPDK log levels" + errmsg = "Failure setting SPDK log levels" self.logger.exception(errmsg) - errmsg="{errmsg}:\n{ex}" + errmsg = "{errmsg}:\n{ex}" for flag in nvmf_log_flags: rpc_log.log_clear_flag(self.spdk_rpc_client, flag=flag) resp = self.parse_json_exeption(ex) @@ -3864,10 +4193,10 @@ def set_spdk_nvmf_logs_safe(self, request, context): status = 0 errmsg = os.strerror(0) - if log_level != None and not ret_log: + if log_level is not None and not ret_log: status = errno.EINVAL errmsg = "Failure setting SPDK log level" - elif print_level != None and not ret_print: + elif print_level is not None and not ret_print: status = errno.EINVAL errmsg = "Failure setting SPDK print log level" elif not all(ret): @@ -3884,13 +4213,15 @@ def disable_spdk_nvmf_logs_safe(self, request, context): self.logger.info(f"Received request to disable SPDK nvmf logs{peer_msg}") try: - nvmf_log_flags = [key for key in rpc_log.log_get_flags(self.spdk_rpc_client).keys() if key.startswith('nvmf')] - ret = [rpc_log.log_clear_flag(self.spdk_rpc_client, flag=flag) for flag in nvmf_log_flags] + nvmf_log_flags = [key for key in rpc_log.log_get_flags(self.spdk_rpc_client).keys() + if key.startswith('nvmf')] + ret = [rpc_log.log_clear_flag(self.spdk_rpc_client, flag=flag) + for flag in nvmf_log_flags] logs_level = [rpc_log.log_set_level(self.spdk_rpc_client, level='NOTICE'), rpc_log.log_set_print_level(self.spdk_rpc_client, level='INFO')] ret.extend(logs_level) except Exception as ex: - errmsg = f"Failure in disable SPDK nvmf log flags" + errmsg = "Failure in disable SPDK nvmf log flags" self.logger.exception(errmsg) errmsg = f"{errmsg}:\n{ex}" resp = self.parse_json_exeption(ex) @@ -3935,28 +4266,29 @@ def get_gateway_info_safe(self, request, context): cli_version_string = request.cli_version addr = self.config.get_with_default("gateway", "addr", "") port = self.config.get_with_default("gateway", "port", "") - ret = pb2.gateway_info(cli_version = request.cli_version, - version = gw_version_string, - spdk_version = spdk_version_string, - name = self.gateway_name, - group = self.gateway_group, - addr = addr, - port = port, - load_balancing_group = self.group_id + 1, - bool_status = True, - hostname = self.host_name, - max_subsystems = self.max_subsystems, - max_namespaces = self.max_namespaces, - max_namespaces_per_subsystem = self.max_namespaces_per_subsystem, - max_hosts_per_subsystem = self.max_hosts_per_subsystem, - status = 0, - error_message = os.strerror(0)) + ret = pb2.gateway_info(cli_version=request.cli_version, + version=gw_version_string, + spdk_version=spdk_version_string, + name=self.gateway_name, + group=self.gateway_group, + addr=addr, + port=port, + load_balancing_group=self.group_id + 1, + bool_status=True, + hostname=self.host_name, + max_subsystems=self.max_subsystems, + max_namespaces=self.max_namespaces, + max_namespaces_per_subsystem=self.max_namespaces_per_subsystem, + max_hosts_per_subsystem=self.max_hosts_per_subsystem, + status=0, + error_message=os.strerror(0)) cli_ver = self.parse_version(cli_version_string) gw_ver = self.parse_version(gw_version_string) - if cli_ver != None and gw_ver != None and cli_ver < gw_ver: + if cli_ver is not None and gw_ver is not None and cli_ver < gw_ver: ret.bool_status = False ret.status = errno.EINVAL - ret.error_message = f"CLI version {cli_version_string} is older than gateway's version {gw_version_string}" + ret.error_message = f"CLI version {cli_version_string} is older " \ + f"than gateway's version {gw_version_string}" elif not gw_version_string: ret.bool_status = False ret.status = errno.ENOKEY @@ -3966,9 +4298,10 @@ def get_gateway_info_safe(self, request, context): ret.status = errno.EINVAL ret.error_message = f"Invalid gateway's version {gw_version_string}" if not cli_version_string: - self.logger.warning(f"No CLI version specified, can't check version compatibility") + self.logger.warning("No CLI version specified, can't check version compatibility") elif not cli_ver: - self.logger.warning(f"Invalid CLI version {cli_version_string}, can't check version compatibility") + self.logger.warning(f"Invalid CLI version {cli_version_string}, " + f"can't check version compatibility") if ret.status == 0: log_func = self.logger.debug else: @@ -3987,19 +4320,21 @@ def get_gateway_log_level(self, request, context=None): log_level = GatewayEnumUtils.get_key_from_value(pb2.GwLogLevel, self.logger.level) except Exception: self.logger.exception(f"Can't get string value for log level {self.logger.level}") - return pb2.gateway_log_level_info(status = errno.ENOKEY, - error_message=f"Invalid gateway log level") - self.logger.info(f"Received request to get gateway's log level. Level is {log_level}{peer_msg}") - return pb2.gateway_log_level_info(status = 0, error_message=os.strerror(0), log_level=log_level) + return pb2.gateway_log_level_info(status=errno.ENOKEY, + error_message="Invalid gateway log level") + self.logger.info(f"Received request to get gateway's log level. " + f"Level is {log_level}{peer_msg}") + return pb2.gateway_log_level_info(status=0, error_message=os.strerror(0), + log_level=log_level) def set_gateway_log_level(self, request, context=None): """Set gateway's log level""" peer_msg = self.get_peer_message(context) log_level = GatewayEnumUtils.get_key_from_value(pb2.GwLogLevel, request.log_level) - if log_level == None: - errmsg=f"Unknown log level {request.log_level}" - self.logger.error(f"{errmsg}") + if log_level is None: + errmsg = f"Unknown log level {request.log_level}" + self.logger.error(errmsg) return pb2.req_status(status=errno.ENOKEY, error_message=errmsg) log_level = log_level.upper() @@ -4011,12 +4346,14 @@ def set_gateway_log_level(self, request, context=None): except FileNotFoundError: pass except Exception: - self.logger.exception(f"Failure removing \"{GatewayLogger.NVME_GATEWAY_LOG_LEVEL_FILE_PATH}\"") + self.logger.exception(f"Failure removing " + f"\"{GatewayLogger.NVME_GATEWAY_LOG_LEVEL_FILE_PATH}\"") try: with open(GatewayLogger.NVME_GATEWAY_LOG_LEVEL_FILE_PATH, "w") as f: f.write(str(request.log_level)) except Exception: - self.logger.exception(f"Failure writing log level to \"{GatewayLogger.NVME_GATEWAY_LOG_LEVEL_FILE_PATH}\"") + self.logger.exception(f"Failure writing log level to " + f"\"{GatewayLogger.NVME_GATEWAY_LOG_LEVEL_FILE_PATH}\"") return pb2.req_status(status=0, error_message=os.strerror(0)) diff --git a/control/prometheus.py b/control/prometheus.py index 411b6057..56971c8a 100644 --- a/control/prometheus.py +++ b/control/prometheus.py @@ -25,6 +25,7 @@ logger = None + class RBD(NamedTuple): pool: str namespace: str @@ -421,7 +422,7 @@ def collect(self): yield subsystem_metadata yield subsystem_listeners yield subsystem_host_count - yield subsystem_namespace_count + yield subsystem_namespace_count yield subsystem_namespace_limit yield subsystem_namespace_metadata yield host_connection_state diff --git a/control/rebalance.py b/control/rebalance.py index 59e6b211..18ffd4cb 100755 --- a/control/rebalance.py +++ b/control/rebalance.py @@ -7,16 +7,13 @@ # Authors: leonidc@il.ibm.com # -#import uuid -import errno import threading import time -import json -import re -from .utils import GatewayLogger -from .config import GatewayConfig from .proto import gateway_pb2 as pb2 + MIN_LOAD = 2000 + + class Rebalance: """Miscellaneous functions which do rebalance of ANA groups """ @@ -25,11 +22,11 @@ def __init__(self, gateway_service): self.logger = gateway_service.logger self.gw_srv = gateway_service self.ceph_utils = gateway_service.ceph_utils - self.rebalance_period_sec = gateway_service.config.getint_with_default("gateway", "rebalance_period_sec", 7) + self.rebalance_period_sec = gateway_service.config.getint_with_default("gateway", "rebalance_period_sec", 7) self.rebalance_max_ns_to_change_lb_grp = gateway_service.config.getint_with_default("gateway", "max_ns_to_change_lb_grp", 8) self.rebalance_event = threading.Event() self.auto_rebalance = threading.Thread(target=self.auto_rebalance_task, daemon=True, args=(self.rebalance_event,)) - self.auto_rebalance.start() #start the thread + self.auto_rebalance.start() # start the thread def auto_rebalance_task(self, death_event): """Periodically calls for auto rebalance.""" @@ -41,47 +38,47 @@ def auto_rebalance_task(self, death_event): self.logger.debug(f"Nothing found for rebalance, break at {i} iteration") break except Exception: - self.logger.exception(f"Exception in auto rebalance") - if death_event: - death_event.set() - raise - time.sleep(0.01) #release lock for 10ms after rebalancing each 1 NS + self.logger.exception("Exception in auto rebalance") + if death_event: + death_event.set() + raise + time.sleep(0.01) # release lock for 10ms after rebalancing each 1 NS time.sleep(self.rebalance_period_sec) - def find_min_loaded_group(self, grp_list)->int: + def find_min_loaded_group(self, grp_list) -> int: min_load = MIN_LOAD chosen_ana_group = 0 chosen_nqn = "null" - for ana_grp in self.gw_srv.ana_grp_ns_load : - if ana_grp in grp_list : - self.logger.debug(f" ana-group {ana_grp} total load {self.gw_srv.ana_grp_ns_load[ana_grp]}") - if self.gw_srv.ana_grp_ns_load[ana_grp] <= min_load: - min_load = self.gw_srv.ana_grp_ns_load[ana_grp] - chosen_ana_group = ana_grp + for ana_grp in self.gw_srv.ana_grp_ns_load: + if ana_grp in grp_list: + self.logger.debug(f" ana-group {ana_grp} total load {self.gw_srv.ana_grp_ns_load[ana_grp]}") + if self.gw_srv.ana_grp_ns_load[ana_grp] <= min_load: + min_load = self.gw_srv.ana_grp_ns_load[ana_grp] + chosen_ana_group = ana_grp min_load = MIN_LOAD self.logger.debug(f"chosen ana-group {chosen_ana_group}") - if chosen_ana_group != 0 : - for nqn in self.gw_srv.ana_grp_subs_load[chosen_ana_group] : - self.logger.debug(f"chosen ana-group {chosen_ana_group} nqn {nqn} load {self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn]}") - if self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn] < min_load: - min_load = self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn] - chosen_nqn = nqn + if chosen_ana_group != 0: + for nqn in self.gw_srv.ana_grp_subs_load[chosen_ana_group]: + self.logger.debug(f"chosen ana-group {chosen_ana_group} nqn {nqn} load {self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn]}") + if self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn] < min_load: + min_load = self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn] + chosen_nqn = nqn return chosen_ana_group, chosen_nqn - def find_min_loaded_group_in_subsys(self, nqn, grp_list)->int: + def find_min_loaded_group_in_subsys(self, nqn, grp_list) -> int: min_load = MIN_LOAD chosen_ana_group = 0 - for ana_grp in grp_list : - if self.gw_srv.ana_grp_ns_load[ana_grp] == 0: + for ana_grp in grp_list: + if self.gw_srv.ana_grp_ns_load[ana_grp] == 0: self.gw_srv.ana_grp_subs_load[ana_grp][nqn] = 0 return 0, ana_grp - for ana_grp in self.gw_srv.ana_grp_subs_load : - if ana_grp in grp_list : + for ana_grp in self.gw_srv.ana_grp_subs_load: + if ana_grp in grp_list: if nqn in self.gw_srv.ana_grp_subs_load[ana_grp]: - if self.gw_srv.ana_grp_subs_load[ana_grp][nqn] <= min_load: + if self.gw_srv.ana_grp_subs_load[ana_grp][nqn] <= min_load: min_load = self.gw_srv.ana_grp_subs_load[ana_grp][nqn] chosen_ana_group = ana_grp - else: #still no load on this ana and subs + else: # still no load on this ana and subs chosen_ana_group = ana_grp self.gw_srv.ana_grp_subs_load[chosen_ana_group][nqn] = 0 min_load = 0 @@ -89,64 +86,63 @@ def find_min_loaded_group_in_subsys(self, nqn, grp_list)->int: return min_load, chosen_ana_group # 1. Not allowed to perform regular rebalance when scale_down rebalance is ongoing - # 2. Monitor each time defines what GW is responsible for regular rebalance(fairness logic), so there will not be collisions between the GWs + # 2. Monitor each time defines what GW is responsible for regular rebalance(fairness logic), so there will not be collisions between the GWs # and reballance results will be accurate. Monitor in nvme-gw show response publishes the index of ANA group that is currently responsible for rebalance - def rebalance_logic(self, request, context)->int: + def rebalance_logic(self, request, context) -> int: worker_ana_group = self.ceph_utils.get_rebalance_ana_group() self.logger.debug(f"Called rebalance logic: current rebalancing ana group {worker_ana_group}") ongoing_scale_down_rebalance = False grps_list = self.ceph_utils.get_number_created_gateways(self.gw_srv.gateway_pool, self.gw_srv.gateway_group) if not self.ceph_utils.is_rebalance_supported(): - self.logger.info(f"Auto rebalance is not supported with the curent ceph version") + self.logger.info("Auto rebalance is not supported with the curent ceph version") return 1 for ana_grp in self.gw_srv.ana_grp_state: - if self.gw_srv.ana_grp_ns_load[ana_grp] != 0 : #internally valid group - if ana_grp not in grps_list: #monitor considers it invalid since GW owner was deleted + if self.gw_srv.ana_grp_ns_load[ana_grp] != 0: # internally valid group + if ana_grp not in grps_list: # monitor considers it invalid since GW owner was deleted ongoing_scale_down_rebalance = True - self.logger.info(f"Scale-down rebalance is ongoing for ANA group {ana_grp} current load {self.gw_srv.ana_grp_ns_load[ana_grp]}") + self.logger.info(f"Scale-down rebalance is ongoing for ANA group {ana_grp} current load {self.gw_srv.ana_grp_ns_load[ana_grp]}") break num_active_ana_groups = len(grps_list) for ana_grp in self.gw_srv.ana_grp_state: - if self.gw_srv.ana_grp_state[ana_grp] == pb2.ana_state.OPTIMIZED : + if self.gw_srv.ana_grp_state[ana_grp] == pb2.ana_state.OPTIMIZED: if ana_grp not in grps_list: self.logger.info(f"Found optimized ana group {ana_grp} that handles the group of deleted GW." f"Number NS in group {self.gw_srv.ana_grp_ns_load[ana_grp]} - Start NS rebalance") if self.gw_srv.ana_grp_ns_load[ana_grp] >= self.rebalance_max_ns_to_change_lb_grp: - num = self.rebalance_max_ns_to_change_lb_grp + num = self.rebalance_max_ns_to_change_lb_grp else: - num = self.gw_srv.ana_grp_ns_load[ana_grp] - if num > 0 : + num = self.gw_srv.ana_grp_ns_load[ana_grp] + if num > 0: min_ana_grp, chosen_nqn = self.find_min_loaded_group(grps_list) self.logger.info(f"Start rebalance (scale down) destination ana group {min_ana_grp}, subsystem {chosen_nqn}") - self.ns_rebalance(context, ana_grp, min_ana_grp, 1, "0")#scale down rebalance + self.ns_rebalance(context, ana_grp, min_ana_grp, 1, "0") # scale down rebalance return 0 - else : + else: self.logger.info(f"warning: empty group {ana_grp} of Deleting GW still appears Optimized") return 1 - else : - if not ongoing_scale_down_rebalance and (self.gw_srv.ana_grp_state[worker_ana_group] == pb2.ana_state.OPTIMIZED) : - # if my optimized ana group == worker-ana-group or worker-ana-group is also in optimized state on this GW machine - for nqn in self.gw_srv.ana_grp_subs_load[ana_grp] : #need to search all nqns not only inside the current load + else: + if not ongoing_scale_down_rebalance and (self.gw_srv.ana_grp_state[worker_ana_group] == pb2.ana_state.OPTIMIZED): + # if my optimized ana group == worker-ana-group or worker-ana-group is also in optimized state on this GW machine + for nqn in self.gw_srv.ana_grp_subs_load[ana_grp]: # need to search all nqns not only inside the current load num_ns_in_nqn = self.gw_srv.subsystem_nsid_bdev_and_uuid.get_namespace_count(nqn, None, 0) - target_subs_per_ana = num_ns_in_nqn/num_active_ana_groups + target_subs_per_ana = num_ns_in_nqn / num_active_ana_groups self.logger.debug(f"loop: nqn {nqn} ana group {ana_grp} load {self.gw_srv.ana_grp_subs_load[ana_grp][nqn]}, " f"num-ns in nqn {num_ns_in_nqn}, target_subs_per_ana {target_subs_per_ana} ") if self.gw_srv.ana_grp_subs_load[ana_grp][nqn] > target_subs_per_ana: self.logger.debug(f"max-nqn load {self.gw_srv.ana_grp_subs_load[ana_grp][nqn]} nqn {nqn} ") min_load, min_ana_grp = self.find_min_loaded_group_in_subsys(nqn, grps_list) - if ( - (self.gw_srv.ana_grp_subs_load[min_ana_grp][nqn] + 1) <= target_subs_per_ana - or (self.gw_srv.ana_grp_subs_load[min_ana_grp][nqn] + 1) == (self.gw_srv.ana_grp_subs_load[ana_grp][nqn] - 1) - ): - self.logger.info(f"Start rebalance (regular) in subsystem {nqn}, dest ana {min_ana_grp}, dest ana load per subs {min_load}") - self.ns_rebalance(context, ana_grp, min_ana_grp, 1, nqn) #regular rebalance - return 0 + le_target = (self.gw_srv.ana_grp_subs_load[min_ana_grp][nqn] + 1) <= target_subs_per_ana + load_eq = (self.gw_srv.ana_grp_subs_load[min_ana_grp][nqn] + 1) == (self.gw_srv.ana_grp_subs_load[ana_grp][nqn] - 1) + if le_target or load_eq: + self.logger.info(f"Start rebalance (regular) in subsystem {nqn}, dest ana {min_ana_grp}, dest ana load per subs {min_load}") + self.ns_rebalance(context, ana_grp, min_ana_grp, 1, nqn) # regular rebalance + return 0 else: self.logger.debug(f"Found min loaded subsystem {nqn}, ana {min_ana_grp}, load {min_load} does not fit rebalance criteria!") continue return 1 - def ns_rebalance(self, context, ana_id, dest_ana_id, num, subs_nqn) ->int: + def ns_rebalance(self, context, ana_id, dest_ana_id, num, subs_nqn) -> int: now = time.time() num_rebalanced = 0 self.logger.info(f"== rebalance started == for subsystem {subs_nqn}, anagrp {ana_id}, destination anagrp {dest_ana_id}, num ns {num} time {now} ") @@ -155,12 +151,12 @@ def ns_rebalance(self, context, ana_id, dest_ana_id, num, subs_nqn) ->int: for nsid, subsys in ns_list: self.logger.debug(f"nsid {nsid} for nqn {subsys} to rebalance:") if subsys == subs_nqn or subs_nqn == "0": - self.logger.info(f"nsid for change_load_balancing : {nsid}, {subsys}, anagrpid: {ana_id}") - change_lb_group_req = pb2.namespace_change_load_balancing_group_req(subsystem_nqn=subsys, nsid= nsid, anagrpid=dest_ana_id, auto_lb_logic=True) - ret = self.gw_srv.namespace_change_load_balancing_group_safe(change_lb_group_req, context) - self.logger.debug(f"ret namespace_change_load_balancing_group {ret}") - num_rebalanced += 1 - if num_rebalanced >= num : - self.logger.info(f"== Completed rebalance in {time.time() - now } sec for {num} namespaces from anagrp {ana_id} to {dest_ana_id} ") - return 0 + self.logger.info(f"nsid for change_load_balancing : {nsid}, {subsys}, anagrpid: {ana_id}") + change_lb_group_req = pb2.namespace_change_load_balancing_group_req(subsystem_nqn=subsys, nsid=nsid, anagrpid=dest_ana_id, auto_lb_logic=True) + ret = self.gw_srv.namespace_change_load_balancing_group_safe(change_lb_group_req, context) + self.logger.debug(f"ret namespace_change_load_balancing_group {ret}") + num_rebalanced += 1 + if num_rebalanced >= num: + self.logger.info(f"== Completed rebalance in {time.time() - now } sec for {num} namespaces from anagrp {ana_id} to {dest_ana_id} ") + return 0 return 0 diff --git a/control/server.py b/control/server.py index fdbea7ee..4494a582 100644 --- a/control/server.py +++ b/control/server.py @@ -35,12 +35,14 @@ from .cephutils import CephUtils from .prometheus import start_exporter + def sigterm_handler(signum, frame): """Handle SIGTERM, runs when a gateway is terminated gracefully.""" logger = GatewayLogger().logger logger.info(f"GatewayServer: SIGTERM received {signum=}") raise SystemExit(0) + def sigchld_handler(signum, frame): """Handle SIGCHLD, runs when a child process, like the spdk, terminates.""" logger = GatewayLogger().logger @@ -50,7 +52,7 @@ def sigchld_handler(signum, frame): pid, wait_status = os.waitpid(-1, os.WNOHANG) logger.error(f"PID of terminated child process is {pid}") except OSError: - logger.exception(f"waitpid error") + logger.exception("waitpid error") # eat the exception, in signal handler context pass @@ -59,10 +61,12 @@ def sigchld_handler(signum, frame): # GW process should exit now raise SystemExit(f"Gateway subprocess terminated {pid=} {exit_code=}") + def int_to_bitmask(n): """Converts an integer n to a bitmask string""" return f"0x{hex((1 << n) - 1)[2:].upper()}" + def cpumask_set(args): """Check if reactor cpu mask is set in command line args""" @@ -77,6 +81,7 @@ def cpumask_set(args): return False + class GatewayServer: """Runs SPDK and receives client requests for the gateway service. @@ -195,19 +200,19 @@ def _wait_for_group_id(self): self.monitor_event.wait() self.monitor_event = None self.logger.info("Stopping the MonitorGroup server...") - grace = self.config.getfloat_with_default("gateway", "monitor_stop_grace", 1/1000) + grace = self.config.getfloat_with_default("gateway", "monitor_stop_grace", 1 / 1000) self.monitor_server.stop(grace).wait() self.logger.info("The MonitorGroup gRPC server stopped...") self.monitor_server = None def start_prometheus(self): - ###Starts the prometheus endpoint if enabled by the config.### + """Starts the prometheus endpoint if enabled by the config.""" if self.config.getboolean_with_default("gateway", "enable_prometheus_exporter", True): self.logger.info("Prometheus endpoint is enabled") start_exporter(self.spdk_rpc_client, self.config, self.gateway_rpc, self.logger) else: - self.logger.info(f"Prometheus endpoint is disabled. To enable, set the config option 'enable_prometheus_exporter = True'") + self.logger.info("Prometheus endpoint is disabled. To enable, set the config option 'enable_prometheus_exporter = True'") def serve(self): """Starts gateway server.""" @@ -266,10 +271,10 @@ def _register_service_map(self): metadata = { "id": self.name.removeprefix("client.nvmeof."), "pool_name": self.config.get("ceph", "pool"), - "daemon_type": "gateway", # "nvmeof: 3 active (3 hosts)" + "daemon_type": "gateway", # "nvmeof: 3 active (3 hosts)" "group": self.config.get_with_default("gateway", "group", ""), - } - self.ceph_utils.service_daemon_register(conn, metadata) + } + self.ceph_utils.service_daemon_register(conn, metadata) def _monitor_client_version(self) -> str: """Return monitor client version string.""" @@ -298,20 +303,20 @@ def _start_monitor_client(self): rados_id = self.config.get_with_default("ceph", "id", "client.admin") if not rados_id.startswith(client_prefix): rados_id = client_prefix + rados_id - cmd = [ self.monitor_client, - "--gateway-name", self.name, - "--gateway-address", self._gateway_address(), - "--gateway-pool", self.config.get("ceph", "pool"), - "--gateway-group", self.config.get_with_default("gateway", "group", ""), - "--monitor-group-address", self._monitor_address(), - '-c', '/etc/ceph/ceph.conf', - '-n', rados_id, - '-k', '/etc/ceph/keyring'] + cmd = [self.monitor_client, + "--gateway-name", self.name, + "--gateway-address", self._gateway_address(), + "--gateway-pool", self.config.get("ceph", "pool"), + "--gateway-group", self.config.get_with_default("gateway", "group", ""), + "--monitor-group-address", self._monitor_address(), + '-c', '/etc/ceph/ceph.conf', + '-n', rados_id, + '-k', '/etc/ceph/keyring'] if self.config.getboolean("gateway", "enable_auth"): cmd += [ "--server-cert", self.config.get("mtls", "server_cert"), "--client-key", self.config.get("mtls", "client_key"), - "--client-cert", self.config.get("mtls", "client_cert") ] + "--client-cert", self.config.get("mtls", "client_cert")] self.monitor_client_log_file = None self.monitor_client_log_file_path = None @@ -335,7 +340,7 @@ def _start_monitor_client(self): # wait for monitor notification of the group id self._wait_for_group_id() except Exception: - self.logger.exception(f"Unable to start CEPH monitor client:") + self.logger.exception("Unable to start CEPH monitor client") raise def _start_discovery_service(self): @@ -348,7 +353,7 @@ def _start_discovery_service(self): try: rpc_nvmf.nvmf_delete_subsystem(self.spdk_rpc_client, GatewayUtils.DISCOVERY_NQN) except Exception: - self.logger.exception(f"Delete Discovery subsystem returned with error") + self.logger.exception("Delete Discovery subsystem returned with error") raise # run ceph nvmeof discovery service in sub-process @@ -389,7 +394,7 @@ def _grpc_server(self, address): enable_auth = self.config.getboolean("gateway", "enable_auth") if enable_auth: - self.logger.info(f"mTLS authenciation has been enabled") + self.logger.info("mTLS authentication has been enabled") # Read in key and certificates for authentication server_key = self.config.get("mtls", "server_key") server_cert = self.config.get("mtls", "server_cert") @@ -470,7 +475,7 @@ def _start_spdk(self, omap_state): sockname = self.config.get_with_default("spdk", "rpc_socket_name", "spdk.sock") if sockname.find("/") >= 0: self.logger.error(f"Invalid SPDK socket name \"{sockname}\". Name should not contain a \"/\".") - raise RuntimeError(f"Invalid SPDK socket name.") + raise RuntimeError("Invalid SPDK socket name.") self.spdk_rpc_socket_path = sockdir + sockname self.logger.info(f"SPDK Socket: {self.spdk_rpc_socket_path}") spdk_tgt_cmd_extra_args = self.config.get_with_default( @@ -487,10 +492,9 @@ def _start_spdk(self, omap_state): self.logger.info(f"SPDK will not use huge pages, mem size: {spdk_memsize}") cmd += ["--no-huge", "-s", str(spdk_memsize)] else: - self.logger.info(f"SPDK will use huge pages, probing...") + self.logger.info("SPDK will use huge pages, probing...") self.probe_huge_pages() - # If not provided in configuration, # calculate cpu mask available for spdk reactors if not cpumask_set(cmd): @@ -515,7 +519,7 @@ def _start_spdk(self, omap_state): time.sleep(2) # this is a temporary hack, we have a timing issue here. Once we solve it the sleep will ve removed self.spdk_process = subprocess.Popen(cmd, stdout=self.spdk_log_file, stderr=log_stderr) except Exception: - self.logger.exception(f"Unable to start SPDK") + self.logger.exception("Unable to start SPDK") raise # Initialization @@ -559,7 +563,7 @@ def _start_spdk(self, omap_state): conn_retries=conn_retries, ) except Exception: - self.logger.exception(f"Unable to initialize SPDK") + self.logger.exception("Unable to initialize SPDK") raise # Implicitly create transports @@ -576,12 +580,12 @@ def _start_spdk(self, omap_state): except KeyError: self.logger.error(f"Can't find SPDK version string in {return_version}") except Exception: - self.logger.exception(f"Can't read SPDK version") + self.logger.exception("Can't read SPDK version") pass def _stop_subprocess(self, proc, timeout): """Stops SPDK process.""" - assert proc is not None # should be verified by the caller + assert proc is not None # should be verified by the caller return_code = proc.returncode @@ -598,7 +602,7 @@ def _stop_subprocess(self, proc, timeout): except subprocess.TimeoutExpired: self.logger.exception(f"({self.name}) pid {proc.pid} " f"timeout occurred while terminating sub process:") - proc.kill() # kill -9, send KILL signal + proc.kill() # kill -9, send KILL signal def _stop_monitor_client(self): """Stops Monitor client.""" @@ -640,7 +644,7 @@ def _stop_spdk(self): def _stop_discovery(self): """Stops Discovery service process.""" - assert self.discovery_pid is not None # should be verified by the caller + assert self.discovery_pid is not None # should be verified by the caller self.logger.info("Terminating discovery service...") # discovery service selector loop should exit due to KeyboardInterrupt exception @@ -648,7 +652,7 @@ def _stop_discovery(self): os.kill(self.discovery_pid, signal.SIGINT) os.waitpid(self.discovery_pid, 0) except (ChildProcessError, ProcessLookupError): - pass # ignore + pass # ignore self.logger.info("Discovery service terminated") self.discovery_pid = None @@ -669,7 +673,7 @@ def _create_transport(self, trtype): raise try: - rpc_nvmf.nvmf_create_transport( self.spdk_rpc_client, **args) + rpc_nvmf.nvmf_create_transport(self.spdk_rpc_client, **args) except Exception: self.logger.exception(f"Create Transport {trtype} returned with error") raise @@ -677,7 +681,7 @@ def _create_transport(self, trtype): def keep_alive(self): """Continuously confirms communication with SPDK process.""" allowed_consecutive_spdk_ping_failures = self.config.getint_with_default("gateway", - "allowed_consecutive_spdk_ping_failures", 1) + "allowed_consecutive_spdk_ping_failures", 1) spdk_ping_interval_in_seconds = self.config.getfloat_with_default("gateway", "spdk_ping_interval_in_seconds", 2.0) if spdk_ping_interval_in_seconds < 0.0: self.logger.warning(f"Invalid SPDK ping interval {spdk_ping_interval_in_seconds}, will reset to 0") @@ -693,8 +697,8 @@ def keep_alive(self): while True: if self.gateway_rpc: if self.gateway_rpc.rebalance.rebalance_event.is_set(): - self.logger.critical(f"Failure in rebalance, aborting") - raise SystemExit(f"Failure in rebalance, quitting gateway") + self.logger.critical("Failure in rebalance, aborting") + raise SystemExit("Failure in rebalance, quitting gateway") timedout = self.server.wait_for_termination(timeout=1) if not timedout: break @@ -705,7 +709,7 @@ def keep_alive(self): consecutive_ping_failures += 1 if consecutive_ping_failures >= allowed_consecutive_spdk_ping_failures: self.logger.critical(f"SPDK ping failed {consecutive_ping_failures} times, aborting") - raise SystemExit(f"SPDK ping failed, quitting gateway") + raise SystemExit("SPDK ping failed, quitting gateway") else: self.logger.warning(f"SPDK ping failed {consecutive_ping_failures} times, will keep trying") else: @@ -717,7 +721,7 @@ def _ping(self): spdk.rpc.spdk_get_version(self.spdk_rpc_ping_client) return True except Exception: - self.logger.exception(f"spdk_get_version failed") + self.logger.exception("spdk_get_version failed") return False def probe_huge_pages(self): diff --git a/control/state.py b/control/state.py index 6d2faeb2..736313b7 100644 --- a/control/state.py +++ b/control/state.py @@ -20,6 +20,7 @@ from google.protobuf import json_format from .proto import gateway_pb2 as pb2 + class GatewayState(ABC): """Persists gateway NVMeoF target state. @@ -70,11 +71,12 @@ def build_namespace_qos_key(subsystem_nqn: str, nsid) -> str: key += GatewayState.OMAP_KEY_DELIMITER + str(nsid) return key - def build_namespace_host_key(subsystem_nqn: str, nsid, host : str) -> str: + def build_namespace_host_key(subsystem_nqn: str, nsid, host: str) -> str: key = GatewayState.NAMESPACE_HOST_PREFIX + subsystem_nqn if nsid is not None: key += GatewayState.OMAP_KEY_DELIMITER + str(nsid) - key += GatewayState.OMAP_KEY_DELIMITER + host + if host: + key += GatewayState.OMAP_KEY_DELIMITER + host return key def build_subsystem_key(subsystem_nqn: str) -> str: @@ -139,8 +141,14 @@ def remove_namespace(self, subsystem_nqn: str, nsid: str): # Delete all keys related to the namespace state = self.get_state() for key in state.keys(): - if (key.startswith(GatewayState.build_namespace_qos_key(subsystem_nqn, nsid)) or - key.startswith(GatewayState.build_namespace_host_key(subsystem_nqn, nsid, ""))): + # Separate if to several statements to keep flake8 happy + if key.startswith(GatewayState.build_namespace_qos_key(subsystem_nqn, nsid)): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_host_key(subsystem_nqn, nsid, "")): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_visibility_key(subsystem_nqn, nsid)): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_lbgroup_key(subsystem_nqn, nsid)): self._remove_key(key) def add_namespace_qos(self, subsystem_nqn: str, nsid: str, val: str): @@ -153,12 +161,12 @@ def remove_namespace_qos(self, subsystem_nqn: str, nsid: str): key = GatewayState.build_namespace_qos_key(subsystem_nqn, nsid) self._remove_key(key) - def add_namespace_host(self, subsystem_nqn: str, nsid: str, host : str, val: str): + def add_namespace_host(self, subsystem_nqn: str, nsid: str, host: str, val: str): """Adds namespace's host to the state data store.""" key = GatewayState.build_namespace_host_key(subsystem_nqn, nsid, host) self._add_key(key, val) - def remove_namespace_host(self, subsystem_nqn: str, nsid: str, host : str): + def remove_namespace_host(self, subsystem_nqn: str, nsid: str, host: str): """Removes namespace's host from the state data store.""" key = GatewayState.build_namespace_host_key(subsystem_nqn, nsid, host) self._remove_key(key) @@ -176,11 +184,24 @@ def remove_subsystem(self, subsystem_nqn: str): # Delete all keys related to subsystem state = self.get_state() for key in state.keys(): - if (key.startswith(GatewayState.build_namespace_key(subsystem_nqn, None)) or - key.startswith(GatewayState.build_namespace_qos_key(subsystem_nqn, None)) or - key.startswith(GatewayState.build_namespace_host_key(subsystem_nqn, None, "")) or - key.startswith(GatewayState.build_host_key(subsystem_nqn, None)) or - key.startswith(GatewayState.build_partial_listener_key(subsystem_nqn, None))): + # Separate if to several statements to keep flake8 happy + if key.startswith(GatewayState.build_namespace_key(subsystem_nqn, None)): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_qos_key(subsystem_nqn, None)): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_host_key(subsystem_nqn, None, "")): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_visibility_key(subsystem_nqn, None)): + self._remove_key(key) + elif key.startswith(GatewayState.build_namespace_lbgroup_key(subsystem_nqn, None)): + self._remove_key(key) + elif key.startswith(GatewayState.build_host_key(subsystem_nqn, None)): + self._remove_key(key) + elif key.startswith(GatewayState.build_host_key_key(subsystem_nqn, None)): + self._remove_key(key) + elif key.startswith(GatewayState.build_subsystem_key_key(subsystem_nqn)): + self._remove_key(key) + elif key.startswith(GatewayState.build_partial_listener_key(subsystem_nqn, None)): self._remove_key(key) def add_host(self, subsystem_nqn: str, host_nqn: str, val: str): @@ -195,6 +216,12 @@ def remove_host(self, subsystem_nqn: str, host_nqn: str): if key in state.keys(): self._remove_key(key) + # Delete all keys related to the host + state = self.get_state() + for key in state.keys(): + if key.startswith(GatewayState.build_host_key_key(subsystem_nqn, host_nqn)): + self._remove_key(key) + def add_listener(self, subsystem_nqn: str, gateway: str, trtype: str, traddr: str, trsvcid: int, val: str): """Adds a listener to the state data store.""" key = GatewayState.build_listener_key(subsystem_nqn, gateway, trtype, traddr, trsvcid) @@ -243,6 +270,7 @@ def reset(self, omap_state): """Resets dictionary with OMAP state.""" self.state = omap_state + class ReleasedLock: def __init__(self, lock: threading.Lock): self.lock = lock @@ -255,6 +283,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): self.lock.acquire() + class OmapLock: OMAP_FILE_LOCK_NAME = "omap_file_lock" OMAP_FILE_LOCK_COOKIE = "omap_file_cookie" @@ -269,12 +298,12 @@ def __init__(self, omap_state, gateway_state, rpc_lock: threading.Lock) -> None: self.omap_file_update_reloads = self.omap_state.config.getint_with_default("gateway", "omap_file_update_reloads", 10) self.omap_file_lock_retries = self.omap_state.config.getint_with_default("gateway", "omap_file_lock_retries", 30) self.omap_file_lock_retry_sleep_interval = self.omap_state.config.getfloat_with_default("gateway", - "omap_file_lock_retry_sleep_interval", 1.0) + "omap_file_lock_retry_sleep_interval", 1.0) self.lock_start_time = 0.0 # This is used for testing purposes only. To allow us testing locking from two gateways at the same time self.omap_file_disable_unlock = self.omap_state.config.getboolean_with_default("gateway", "omap_file_disable_unlock", False) if self.omap_file_disable_unlock: - self.logger.warning(f"Will not unlock OMAP file for testing purposes") + self.logger.warning("Will not unlock OMAP file for testing purposes") # # We pass the context from the different functions here. It should point to a real object in case we come from a real @@ -335,28 +364,29 @@ def lock_omap(self): assert self.rpc_lock.locked(), "The RPC lock is not locked." if not self.omap_state.ioctx: - self.logger.warning(f"Not locking OMAP as Rados connection is closed") + self.logger.warning("Not locking OMAP as Rados connection is closed") raise Exception("An attempt to lock OMAP file after Rados connection was closed") for i in range(0, self.omap_file_lock_retries + 1): try: self.omap_state.ioctx.lock_exclusive(self.omap_state.omap_name, self.OMAP_FILE_LOCK_NAME, - self.OMAP_FILE_LOCK_COOKIE, "OMAP file changes lock", self.omap_file_lock_duration, 0) + self.OMAP_FILE_LOCK_COOKIE, "OMAP file changes lock", + self.omap_file_lock_duration, 0) got_lock = True if i > 0: self.logger.info(f"Succeeded to lock OMAP file after {i} retries") break except rados.ObjectExists: - self.logger.info(f"We already locked the OMAP file") + self.logger.info("We already locked the OMAP file") got_lock = True break except rados.ObjectBusy: self.logger.warning( - f"The OMAP file is locked, will try again in {self.omap_file_lock_retry_sleep_interval} seconds") + f"The OMAP file is locked, will try again in {self.omap_file_lock_retry_sleep_interval} seconds") with ReleasedLock(self.rpc_lock): time.sleep(self.omap_file_lock_retry_sleep_interval) except Exception: - self.logger.exception(f"Unable to lock OMAP file, exiting") + self.logger.exception("Unable to lock OMAP file, exiting") raise if not got_lock: @@ -368,15 +398,14 @@ def lock_omap(self): local_version = self.omap_state.get_local_version() if omap_version > local_version: - self.logger.warning( - f"Local version {local_version} differs from OMAP file version {omap_version}." - f" The file is not current, will reload it and try again") + self.logger.warning(f"Local version {local_version} differs from OMAP file version {omap_version}." + " The file is not current, will reload it and try again") self.unlock_omap() raise OSError(errno.EAGAIN, "Unable to lock OMAP file, file not current", self.omap_state.omap_name) def unlock_omap(self): if self.omap_file_disable_unlock: - self.logger.warning(f"OMAP file unlock was disabled, will not unlock file") + self.logger.warning("OMAP file unlock was disabled, will not unlock file") return if not self.omap_state.ioctx: @@ -387,15 +416,16 @@ def unlock_omap(self): self.omap_state.ioctx.unlock(self.omap_state.omap_name, self.OMAP_FILE_LOCK_NAME, self.OMAP_FILE_LOCK_COOKIE) except rados.ObjectNotFound: if self.is_locked: - self.logger.warning(f"No such lock, the lock duration might have passed") + self.logger.warning("No such lock, the lock duration might have passed") except Exception: - self.logger.exception(f"Unable to unlock OMAP file") + self.logger.exception("Unable to unlock OMAP file") pass self.is_locked = False def locked(self): return self.is_locked + class OmapGatewayState(GatewayState): """Persists gateway NVMeoF target state to an OMAP object. @@ -442,7 +472,7 @@ def __init__(self, config, id_text=""): except rados.ObjectExists: self.logger.info(f"{self.omap_name} OMAP object already exists.") except Exception: - self.logger.exception(f"Unable to create OMAP, exiting!") + self.logger.exception("Unable to create OMAP, exiting!") raise def __exit__(self, exc_type, exc_value, traceback): @@ -451,8 +481,8 @@ def __exit__(self, exc_type, exc_value, traceback): def check_for_old_format_omap_files(self): omap_dict = self.get_state() for omap_item_key in omap_dict.keys(): - if omap_item_key.startswith("bdev"): - raise Exception("Old OMAP file format, still contains bdevs, please remove file and try again") + if omap_item_key.startswith("bdev"): + raise Exception("Old OMAP file format, still contains bdevs, please remove file and try again") def open_rados_connection(self, config): ceph_pool = config.get("ceph", "pool") @@ -475,7 +505,7 @@ def set_local_version(self, version_update: int): def get_omap_version(self) -> int: """Returns OMAP version.""" if not self.ioctx: - self.logger.warning(f"Trying to get OMAP version when Rados connection is closed") + self.logger.warning("Trying to get OMAP version when Rados connection is closed") return -1 with rados.ReadOpCtx() as read_op: @@ -497,7 +527,7 @@ def get_state(self) -> Dict[str, str]: omap_list = [("", 0)] # Dummy, non empty, list value. Just so we would enter the while omap_dict = {} if not self.ioctx: - self.logger.warning(f"Trying to get OMAP state when Rados connection is closed") + self.logger.warning("Trying to get OMAP state when Rados connection is closed") return omap_dict # The number of items returned is limited by Ceph, so we need to read in a loop until no more items are returned while len(omap_list) > 0: @@ -527,14 +557,14 @@ def _add_key(self, key: str, val: str): self.version = version_update self.logger.debug(f"omap_key generated: {key}") except Exception: - self.logger.exception(f"Unable to add key to OMAP, exiting!") + self.logger.exception("Unable to add key to OMAP, exiting!") raise # Notify other gateways within the group of change try: - self.ioctx.notify(self.omap_name, timeout_ms = self.notify_timeout) + self.ioctx.notify(self.omap_name, timeout_ms=self.notify_timeout) except Exception: - self.logger.warning(f"Failed to notify.") + self.logger.warning("Failed to notify.") def _remove_key(self, key: str): """Removes key from the OMAP.""" @@ -554,14 +584,14 @@ def _remove_key(self, key: str): self.version = version_update self.logger.debug(f"omap_key removed: {key}") except Exception: - self.logger.exception(f"Unable to remove key from OMAP, exiting!") + self.logger.exception("Unable to remove key from OMAP, exiting!") raise # Notify other gateways within the group of change try: - self.ioctx.notify(self.omap_name, timeout_ms = self.notify_timeout) + self.ioctx.notify(self.omap_name, timeout_ms=self.notify_timeout) except Exception: - self.logger.warning(f"Failed to notify.") + self.logger.warning("Failed to notify.") def delete_state(self): """Deletes OMAP object contents.""" @@ -575,9 +605,9 @@ def delete_state(self): self.ioctx.set_omap(write_op, (self.OMAP_VERSION_KEY,), (str(1),)) self.ioctx.operate_write_op(write_op, self.omap_name) - self.logger.info(f"Deleted OMAP contents.") + self.logger.info("Deleted OMAP contents.") except Exception: - self.logger.exception(f"Error deleting OMAP contents, exiting!") + self.logger.exception("Error deleting OMAP contents, exiting!") raise def register_watch(self, notify_event): @@ -593,11 +623,11 @@ def _watcher_callback(notify_id, notifier_id, watch_id, data): try: self.watch = self.ioctx.watch(self.omap_name, _watcher_callback) except Exception: - self.logger.exception(f"Unable to initiate watch") + self.logger.exception("Unable to initiate watch") else: - self.logger.info(f"Watch already exists.") + self.logger.info("Watch already exists.") - def cleanup_omap(self, omap_lock = None): + def cleanup_omap(self, omap_lock=None): self.logger.info(f"Cleanup OMAP on exit ({self.id_text})") if self.watch: try: @@ -622,6 +652,7 @@ def cleanup_omap(self, omap_lock = None): self.conn.shutdown() self.conn = None + class GatewayStateHandler: """Maintains consistency in NVMeoF target state store instances. @@ -673,12 +704,12 @@ def remove_namespace_qos(self, subsystem_nqn: str, nsid: str): self.omap.remove_namespace_qos(subsystem_nqn, nsid) self.local.remove_namespace_qos(subsystem_nqn, nsid) - def add_namespace_host(self, subsystem_nqn: str, nsid: str, host : str, val: str): + def add_namespace_host(self, subsystem_nqn: str, nsid: str, host: str, val: str): """Adds namespace's host to the state data store.""" self.omap.add_namespace_host(subsystem_nqn, nsid, host, val) self.local.add_namespace_host(subsystem_nqn, nsid, host, val) - def remove_namespace_host(self, subsystem_nqn: str, nsid: str, host : str): + def remove_namespace_host(self, subsystem_nqn: str, nsid: str, host: str): """Removes namespace's host from the state data store.""" self.omap.remove_namespace_host(subsystem_nqn, nsid, host) self.local.remove_namespace_host(subsystem_nqn, nsid, host) @@ -798,7 +829,7 @@ def host_only_key_changed(self, old_val, new_val): old_req = None new_req = None try: - old_req = json_format.Parse(old_val, pb2.add_host_req(), ignore_unknown_fields=True ) + old_req = json_format.Parse(old_val, pb2.add_host_req(), ignore_unknown_fields=True) except json_format.ParseError: self.logger.exception(f"Got exception parsing {old_val}") return (False, None) @@ -827,7 +858,7 @@ def subsystem_only_key_changed(self, old_val, new_val): old_req = None new_req = None try: - old_req = json_format.Parse(old_val, pb2.create_subsystem_req(), ignore_unknown_fields=True ) + old_req = json_format.Parse(old_val, pb2.create_subsystem_req(), ignore_unknown_fields=True) except json_format.ParseError: self.logger.exception(f"Got exception parsing {old_val}") return (False, None) @@ -855,7 +886,7 @@ def break_namespace_key(self, ns_key: str): if not ns_key.startswith(GatewayState.NAMESPACE_PREFIX): self.logger.warning(f"Invalid namespace key \"{ns_key}\", can't find key parts") return (None, None) - key_end = ns_key[len(GatewayState.NAMESPACE_PREFIX) : ] + key_end = ns_key[len(GatewayState.NAMESPACE_PREFIX):] key_parts = key_end.split(GatewayState.OMAP_KEY_DELIMITER) if len(key_parts) != 2: self.logger.warning(f"Invalid namespace key \"{ns_key}\", can't find key parts") @@ -876,7 +907,7 @@ def break_host_key(self, host_key: str): if not host_key.startswith(GatewayState.HOST_PREFIX): self.logger.warning(f"Invalid host key \"{host_key}\", can't find key parts") return (None, None) - key_end = host_key[len(GatewayState.HOST_PREFIX) : ] + key_end = host_key[len(GatewayState.HOST_PREFIX):] key_parts = key_end.split(GatewayState.OMAP_KEY_DELIMITER) if len(key_parts) != 2: self.logger.warning(f"Invalid host key \"{host_key}\", can't find key parts") @@ -921,11 +952,11 @@ def update(self) -> bool: """Checks for updated OMAP state and initiates local update.""" if self.update_is_active_lock.locked(): - self.logger.warning(f"An update is already running, ignore") + self.logger.warning("An update is already running, ignore") return False if not self.omap.ioctx: - self.logger.warning(f"Can't update when Rados connection is closed") + self.logger.warning("Can't update when Rados connection is closed") return False with self.update_is_active_lock: @@ -1016,7 +1047,7 @@ def update(self) -> bool: including_default_value_fields=True) added[lbgroup_key] = json_req except Exception: - self.logger.exception(f"Exception formatting change namespace load balancing group request") + self.logger.exception("Exception formatting change namespace load balancing group request") for ns_key, new_visibility in only_visibility_changed: ns_nqn = None @@ -1035,7 +1066,7 @@ def update(self) -> bool: including_default_value_fields=True) added[visibility_key] = json_req except Exception: - self.logger.exception(f"Exception formatting change namespace visibility request") + self.logger.exception("Exception formatting change namespace visibility request") for host_key, new_dhchap_key in only_host_key_changed: subsys_nqn = None @@ -1046,13 +1077,12 @@ def update(self) -> bool: except Exception: self.logger.exception(f"Exception removing {host_key} from {changed}") if host_nqn == "*": - self.logger.warning(f"Something went wrong, host \"*\" can't have DH-HMAC-CHAP keys, ignore") + self.logger.warning("Something went wrong, host \"*\" can't have DH-HMAC-CHAP keys, ignore") continue if subsys_nqn and host_nqn: try: host_key_key = GatewayState.build_host_key_key(subsys_nqn, host_nqn) - req = pb2.change_host_key_req(subsystem_nqn=subsys_nqn, host_nqn=host_nqn, - dhchap_key=new_dhchap_key) + req = pb2.change_host_key_req(subsystem_nqn=subsys_nqn, host_nqn=host_nqn, dhchap_key=new_dhchap_key) json_req = json_format.MessageToJson(req, preserving_proto_field_name=True, including_default_value_fields=True) added[host_key_key] = json_req diff --git a/control/utils.py b/control/utils.py index 570f5415..65d16ba5 100644 --- a/control/utils.py +++ b/control/utils.py @@ -21,7 +21,7 @@ class GatewayEnumUtils: - def get_value_from_key(e_type, keyval, ignore_case = False): + def get_value_from_key(e_type, keyval, ignore_case=False): val = None try: key_index = e_type.keys().index(keyval) @@ -31,10 +31,13 @@ def get_value_from_key(e_type, keyval, ignore_case = False): except IndexError: pass - if ignore_case and val == None and type(keyval) == str: - val = get_value_from_key(e_type, keyval.lower(), False) - if ignore_case and val == None and type(keyval) == str: - val = get_value_from_key(e_type, keyval.upper(), False) + if val is not None or not ignore_case: + return val + + if isinstance(keyval, str): + val = GatewayEnumUtils.get_value_from_key(e_type, keyval.lower(), False) + if val is None and isinstance(keyval, str): + val = GatewayEnumUtils.get_value_from_key(e_type, keyval.upper(), False) return val @@ -49,17 +52,18 @@ def get_key_from_value(e_type, val): pass return keyval + class GatewayUtils: DISCOVERY_NQN = "nqn.2014-08.org.nvmexpress.discovery" # We need to enclose IPv6 addresses in brackets before concatenating a colon and port number to it - def escape_address_if_ipv6(addr : str) -> str: + def escape_address_if_ipv6(addr: str) -> str: ret_addr = addr if ":" in addr and not addr.strip().startswith("["): ret_addr = f"[{addr}]" return ret_addr - def unescape_address_if_ipv6(addr : str, adrfam : str) -> str: + def unescape_address_if_ipv6(addr: str, adrfam: str) -> str: ret_addr = addr.strip() if adrfam.lower() == "ipv6": ret_addr = ret_addr.removeprefix("[").removesuffix("]") @@ -74,7 +78,7 @@ def is_valid_rev_domain(rev_domain): domain_parts = rev_domain.split(".") for lbl in domain_parts: if not lbl: - return (errno.EINVAL, f"empty domain label doesn't start with a letter") + return (errno.EINVAL, "empty domain label doesn't start with a letter") if len(lbl) > DOMAIN_LABEL_MAX_LEN: return (errno.EINVAL, f"domain label {lbl} is too long") @@ -111,7 +115,7 @@ def is_valid_uuid(uuid_val) -> bool: for u in uuid_parts: try: - n = int(u, 16) + int(u, 16) except ValueError: return False @@ -125,11 +129,11 @@ def is_valid_nqn(nqn): NQN_UUID_PREFIX = "nqn.2014-08.org.nvmexpress:uuid:" NQN_UUID_PREFIX_LENGTH = len(NQN_UUID_PREFIX) - if type(nqn) != str: + if not isinstance(nqn, str): return (errno.EINVAL, f"Invalid type {type(nqn)} for NQN, must be a string") try: - b = nqn.encode(encoding="utf-8") + nqn.encode(encoding="utf-8") except UnicodeEncodeError: return (errno.EINVAL, f"Invalid NQN \"{nqn}\", must have an UTF-8 encoding") @@ -145,7 +149,7 @@ def is_valid_nqn(nqn): if nqn.startswith(NQN_UUID_PREFIX): if len(nqn) != NQN_UUID_PREFIX_LENGTH + UUID_STRING_LENGTH: return (errno.EINVAL, f"Invalid NQN \"{nqn}\": UUID is not the correct length") - uuid_part = nqn[NQN_UUID_PREFIX_LENGTH : ] + uuid_part = nqn[NQN_UUID_PREFIX_LENGTH:] if not GatewayUtils.is_valid_uuid(uuid_part): return (errno.EINVAL, f"Invalid NQN \"{nqn}\": UUID is not formatted correctly") return (0, os.strerror(0)) @@ -153,18 +157,18 @@ def is_valid_nqn(nqn): if not nqn.startswith(NQN_PREFIX): return (errno.EINVAL, f"Invalid NQN \"{nqn}\", doesn't start with \"{NQN_PREFIX}\"") - nqn_no_prefix = nqn[len(NQN_PREFIX) : ] - date_part = nqn_no_prefix[ : 8] - rev_domain_part = nqn_no_prefix[8 : ] + nqn_no_prefix = nqn[len(NQN_PREFIX):] + date_part = nqn_no_prefix[:8] + rev_domain_part = nqn_no_prefix[8:] if not date_part.endswith("."): return (errno.EINVAL, f"Invalid NQN \"{nqn}\": invalid date code") - date_part = date_part[ : -1] + date_part = date_part[:-1] try: year_part, month_part = date_part.split("-") if len(year_part) != 4 or len(month_part) != 2: return (errno.EINVAL, f"Invalid NQN \"{nqn}\": invalid date code") - n = int(year_part) - n = int(month_part) + int(year_part) + int(month_part) except ValueError: return (errno.EINVAL, f"Invalid NQN \"{nqn}\": invalid date code") @@ -182,6 +186,7 @@ def is_valid_nqn(nqn): return (0, os.strerror(0)) + class GatewayLogger: CEPH_LOG_DIRECTORY = "/var/log/ceph/" MAX_LOG_FILE_SIZE_DEFAULT = 10 @@ -247,8 +252,8 @@ def __init__(self, config=None): os.makedirs(self.log_directory, 0o755, True) logdir_ok = True self.handler = logging.handlers.RotatingFileHandler(self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME, - maxBytes = max_log_file_size * 1024 * 1024, - backupCount = max_log_files_count) + maxBytes=max_log_file_size * 1024 * 1024, + backupCount=max_log_files_count) self.handler.setFormatter(frmtr) if log_files_rotation_enabled: self.handler.rotator = GatewayLogger.log_file_rotate @@ -270,19 +275,19 @@ def __init__(self, config=None): if not logdir_ok: self.logger.error(f"Failed to create directory {self.log_directory}, the log wouldn't be saved to a file") elif not self.handler: - self.logger.error(f"Failed to set up log file handler, the log wouldn't be saved to a file") + self.logger.error("Failed to set up log file handler, the log wouldn't be saved to a file") else: rot_msg = "" if log_files_rotation_enabled: rot_msg = ", using rotation" self.logger.info(f"Log files will be saved in {self.log_directory}{rot_msg}") else: - self.logger.warning(f"Log files are disabled, the log wouldn't be saved to a file") + self.logger.warning("Log files are disabled, the log wouldn't be saved to a file") GatewayLogger.init_executed = True def rotate_backup_directories(dirname, count): try: - shutil.rmtree(dirname + f".bak{count}", ignore_errors = True) + shutil.rmtree(dirname + f".bak{count}", ignore_errors=True) except Exception: pass for i in range(count, 2, -1): @@ -291,22 +296,22 @@ def rotate_backup_directories(dirname, count): except Exception: pass try: - os.rename(dirname + f".bak", dirname + f".bak2") + os.rename(dirname + ".bak", dirname + ".bak2") except Exception: pass try: - os.rename(dirname, dirname + f".bak") + os.rename(dirname, dirname + ".bak") except Exception: pass # Just to be on the safe side, in case the rename failed try: - shutil.rmtree(dirname, ignore_errors = True) + shutil.rmtree(dirname, ignore_errors=True) except Exception: pass def set_log_level(self, log_level): - if type(log_level) == str: + if isinstance(log_level, str): log_level = log_level.upper() self.logger.setLevel(log_level) logger_parent = self.logger.parent @@ -326,7 +331,6 @@ def log_file_rotate(src, dest): GatewayLogger.logger.info(m) for e in errs: GatewayLogger.logger.error(e) - else: os.rename(src, dest) @@ -377,7 +381,7 @@ def compress_final_log_file(self, gw_name): return if not gw_name: - self.logger.error(f"No gateway name, can't compress the log file") + self.logger.error("No gateway name, can't compress the log file") return if not self.log_directory.endswith(gw_name): @@ -390,8 +394,7 @@ def compress_final_log_file(self, gw_name): dest_name = self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME + ".gz" if os.access(self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME + ".1", - os.F_OK) and not os.access(self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME + ".0", - os.F_OK): + os.F_OK) and not os.access(self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME + ".0", os.F_OK): dest_name = self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME + ".0" msgs, errs = GatewayLogger.compress_file(self.log_directory + "/" + GatewayLogger.NVME_LOG_FILE_NAME, dest_name) diff --git a/tests/conftest.py b/tests/conftest.py index 73f4baa5..6bf54826 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,5 @@ import pytest -import rados from control.config import GatewayConfig -from control.state import OmapGatewayState def pytest_addoption(parser): diff --git a/tests/test_cli.py b/tests/test_cli.py index 4c4513ad..8b6a15c0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -69,6 +69,7 @@ config = "ceph-nvmeof.conf" group_name = "GROUPNAME" + @pytest.fixture(scope="module") def gateway(config): """Sets up and tears down Gateway""" @@ -101,6 +102,7 @@ def gateway(config): gateway.server.stop(grace=1) gateway.gateway_rpc.gateway_state.delete_state() + class TestGet: def test_get_subsystems(self, caplog, gateway): caplog.clear() @@ -158,7 +160,7 @@ def test_get_gateway_info(self, caplog, gateway): caplog.clear() spdk_ver = os.getenv("NVMEOF_SPDK_VERSION") gw_info = cli_test(["gw", "info"]) - assert gw_info != None + assert gw_info is not None assert gw_info.cli_version == cli_ver assert gw_info.version == cli_ver assert gw_info.spdk_version == spdk_ver @@ -171,46 +173,50 @@ def test_get_gateway_info(self, caplog, gateway): assert gw_info.status == 0 assert gw_info.bool_status + class TestCreate: def test_create_subsystem(self, caplog, gateway): caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2016", "--no-group-append"]) - assert f'NQN "nqn.2016" is too short, minimal length is 11' in caplog.text + assert 'NQN "nqn.2016" is too short, minimal length is 11' in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", -"nqn.2016-06XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "--no-group-append"]) - assert f"is too long, maximal length is 223" in caplog.text + "nqn.2016-06XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "--no-group-append"]) + assert "is too long, maximal length is 223" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2014-08.org.nvmexpress:uuid:0", "--no-group-append"]) - assert f"UUID is not the correct length" in caplog.text + assert "UUID is not the correct length" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2014-08.org.nvmexpress:uuid:9e9134-3cb431-4f3e-91eb-a13cefaabebf", "--no-group-append"]) - assert f"UUID is not formatted correctly" in caplog.text + assert "UUID is not formatted correctly" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "qqn.2016-06.io.spdk:cnode1", "--no-group-append"]) - assert f"doesn't start with" in caplog.text + assert "doesn't start with" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.016-206.io.spdk:cnode1", "--no-group-append"]) - assert f"invalid date code" in caplog.text + assert "invalid date code" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2X16-06.io.spdk:cnode1", "--no-group-append"]) - assert f"invalid date code" in caplog.text + assert "invalid date code" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2016-06.io.spdk:", "--no-group-append"]) - assert f"must contain a user specified name starting with" in caplog.text + assert "must contain a user specified name starting with" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2016-06.io..spdk:cnode1", "--no-group-append"]) - assert f"reverse domain is not formatted correctly" in caplog.text + assert "reverse domain is not formatted correctly" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2016-06.io.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.spdk:cnode1", "--no-group-append"]) - assert f"reverse domain is not formatted correctly" in caplog.text + assert "reverse domain is not formatted correctly" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", "nqn.2016-06.io.-spdk:cnode1", "--no-group-append"]) - assert f"reverse domain is not formatted correctly" in caplog.text + assert "reverse domain is not formatted correctly" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", f"{subsystem}_X", "--no-group-append"]) - assert f"Invalid NQN" in caplog.text - assert f"contains invalid characters" in caplog.text + assert "Invalid NQN" in caplog.text + assert "contains invalid characters" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", subsystem, "--max-namespaces", "2049", "--no-group-append"]) assert f"The requested max number of namespaces for subsystem {subsystem} (2049) is greater than the global limit on the number of namespaces (12), will continue" in caplog.text @@ -218,7 +224,7 @@ def test_create_subsystem(self, caplog, gateway): cli(["--format", "json", "subsystem", "list"]) assert f'"serial_number": "{serial}"' not in caplog.text assert f'"nqn": "{subsystem}"' in caplog.text - assert f'"max_namespaces": 2049' in caplog.text + assert '"max_namespaces": 2049' in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", subsystem, "--max-namespaces", "2049", "--no-group-append"]) assert f"Failure creating subsystem {subsystem}: Subsystem already exists" in caplog.text @@ -250,14 +256,14 @@ def test_create_subsystem(self, caplog, gateway): assert f'{subsystem2}' in caplog.text caplog.clear() cli(["subsystem", "list", "--serial-number", "JUNK"]) - assert f"No subsystem with serial number JUNK" in caplog.text + assert "No subsystem with serial number JUNK" in caplog.text caplog.clear() cli(["subsystem", "list", "--subsystem", "JUNK"]) - assert f"Failure listing subsystems: No such device" in caplog.text - assert f'"nqn": "JUNK"' in caplog.text + assert "Failure listing subsystems: No such device" in caplog.text + assert '"nqn": "JUNK"' in caplog.text caplog.clear() subs_list = cli_test(["--format", "text", "subsystem", "list"]) - assert subs_list != None + assert subs_list is not None assert subs_list.status == 0 assert subs_list.subsystems[0].nqn == subsystem assert subs_list.subsystems[1].nqn == subsystem2 @@ -266,7 +272,7 @@ def test_create_subsystem(self, caplog, gateway): assert f"Failure creating subsystem {subsystem8}: Serial number {serial} is already used by subsystem {subsystem2}" in caplog.text caplog.clear() subs_list = cli_test(["subsystem", "list"]) - assert subs_list != None + assert subs_list is not None assert subs_list.status == 0 assert subs_list.subsystems[0].nqn == subsystem assert subs_list.subsystems[1].nqn == subsystem2 @@ -295,7 +301,7 @@ def test_add_namespace_wrong_balancing_group(self, caplog, gateway): caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image4, "--size", "16MB", "--rbd-create-image", "--load-balancing-group", "100", "--force"]) assert f"Failure adding namespace to {subsystem}:" in caplog.text - assert f"Load balancing group 100 doesn't exist" in caplog.text + assert "Load balancing group 100 doesn't exist" in caplog.text def test_add_namespace_wrong_size(self, caplog, gateway): caplog.clear() @@ -321,21 +327,21 @@ def test_add_namespace_wrong_size_grpc(self, caplog, gateway): gw, stub = gateway caplog.clear() add_namespace_req = pb2.namespace_add_req(subsystem_nqn=subsystem, rbd_pool_name=pool, rbd_image_name="junkimage", - block_size=512, create_image=True, size=16*1024*1024+20) + block_size=512, create_image=True, size=16 * 1024 * 1024 + 20) ret = stub.namespace_add(add_namespace_req) assert ret.status != 0 - assert f"Failure adding namespace" in caplog.text - assert f"image size must be aligned to MiBs" in caplog.text + assert "Failure adding namespace" in caplog.text + assert "image size must be aligned to MiBs" in caplog.text def test_add_namespace_wrong_block_size(self, caplog, gateway): gw, stub = gateway caplog.clear() add_namespace_req = pb2.namespace_add_req(subsystem_nqn=subsystem, rbd_pool_name=pool, rbd_image_name="junkimage", - create_image=True, size=16*1024*1024, force=True) + create_image=True, size=16 * 1024 * 1024, force=True) ret = stub.namespace_add(add_namespace_req) assert ret.status != 0 - assert f"Failure adding namespace" in caplog.text - assert f"block size can't be zero" in caplog.text + assert "Failure adding namespace" in caplog.text + assert "block size can't be zero" in caplog.text def test_add_namespace_double_uuid(self, caplog, gateway): caplog.clear() @@ -354,7 +360,7 @@ def test_add_namespace_double_nsid(self, caplog, gateway): assert f"Adding namespace 1 to {subsystem}: Successful" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image3, "--nsid", "1", "--size", "16MB", "--rbd-create-image", "--force"]) - assert f"Failure adding namespace, NSID 1 is already in use" in caplog.text + assert "Failure adding namespace, NSID 1 is already in use" in caplog.text caplog.clear() cli(["namespace", "del", "--subsystem", subsystem, "--nsid", "1"]) assert f"Deleting namespace 1 from {subsystem}: Successful" in caplog.text @@ -362,13 +368,13 @@ def test_add_namespace_double_nsid(self, caplog, gateway): def test_add_namespace(self, caplog, gateway): caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", "junk", "--rbd-image", image2, "--uuid", uuid, "--size", "16MB", "--rbd-create-image", "--load-balancing-group", anagrpid]) - assert f"RBD pool junk doesn't exist" in caplog.text + assert "RBD pool junk doesn't exist" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image2, "--uuid", uuid, "--size", "16MB", "--rbd-create-image", "--load-balancing-group", anagrpid, "--force"]) assert f"Adding namespace 1 to {subsystem}: Successful" in caplog.text assert f"Allocated cluster name='cluster_context_{anagrpid}_0'" in caplog.text assert f"get_cluster cluster_name='cluster_context_{anagrpid}_0'" in caplog.text - assert f"no_auto_visible: False" in caplog.text + assert "no_auto_visible: False" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image2, "--size", "36", "--rbd-create-image", "--load-balancing-group", anagrpid, "--force"]) assert f"Image {pool}/{image2} already exists with a size of 16777216 bytes which differs from the requested size of 37748736 bytes" in caplog.text @@ -440,8 +446,8 @@ def test_add_namespace(self, caplog, gateway): assert f'"uuid": "{uuid}"' in caplog.text caplog.clear() cli(["namespace", "change_load_balancing_group", "--subsystem", subsystem, "--nsid", nsid, "--load-balancing-group", "10"]) - assert f"Failure changing load balancing group for namespace with NSID {nsid} in {subsystem}" in caplog.text - assert f"Load balancing group 10 doesn't exist" in caplog.text + assert f"Failure changing load balancing group for namespace with ID {nsid} in {subsystem}" in caplog.text + assert "Load balancing group 10 doesn't exist" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image3, "--size", "4GB", "--rbd-create-image"]) assert f"Adding namespace 3 to {subsystem}: Successful" in caplog.text @@ -452,14 +458,14 @@ def test_add_namespace(self, caplog, gateway): def test_add_namespace_ipv6(self, caplog, gateway): caplog.clear() - cli(["--server-address", server_addr_ipv6, "namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image, "--load-balancing-group", anagrpid, "--nsid", "4", "--force"]) + cli(["--server-address", server_addr_ipv6, "namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image, "--load-balancing-group", anagrpid, "--nsid", "4", "--force"]) assert f"Adding namespace 4 to {subsystem}: Successful" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "4"]) assert f'"load_balancing_group": {anagrpid}' in caplog.text - cli(["--server-address", server_addr_ipv6, "namespace", "add", "--subsystem", subsystem, "--nsid", "5", "--rbd-pool", pool, "--rbd-image", image, "--load-balancing-group", anagrpid, "--force"]) + cli(["--server-address", server_addr_ipv6, "namespace", "add", "--subsystem", subsystem, "--nsid", "5", "--rbd-pool", pool, "--rbd-image", image, "--load-balancing-group", anagrpid, "--force"]) assert f"Adding namespace 5 to {subsystem}: Successful" in caplog.text - assert f'will continue as the "force" argument was used' in caplog.text + assert 'will continue as the "force" argument was used' in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "5"]) assert f'"load_balancing_group": {anagrpid}' in caplog.text @@ -472,26 +478,26 @@ def test_add_namespace_same_image(self, caplog, gateway): caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", img_name, "--size", "16MB", "--load-balancing-group", anagrpid, "--rbd-create-image", "--nsid", "7"]) assert f"RBD image {pool}/{img_name} is already used by a namespace" in caplog.text - assert f"you can find the offending namespace by using" in caplog.text + assert "you can find the offending namespace by using" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", img_name, "--load-balancing-group", anagrpid, "--force", "--nsid", "7"]) assert f"Adding namespace 7 to {subsystem}: Successful" in caplog.text assert f"RBD image {pool}/{img_name} is already used by a namespace" in caplog.text - assert f'will continue as the "force" argument was used' in caplog.text + assert 'will continue as the "force" argument was used' in caplog.text def test_add_namespace_no_auto_visible(self, caplog, gateway): caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image5, "--size", "16MB", "--rbd-create-image", "--no-auto-visible"]) assert f"Adding namespace 8 to {subsystem}: Successful" in caplog.text - assert f"no_auto_visible: True" in caplog.text + assert "no_auto_visible: True" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image6, "--size", "16MB", "--rbd-create-image", "--no-auto-visible"]) assert f"Adding namespace 9 to {subsystem}: Successful" in caplog.text - assert f"no_auto_visible: True" in caplog.text + assert "no_auto_visible: True" in caplog.text caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image7, "--size", "16MB", "--rbd-create-image", "--no-auto-visible"]) assert f"Adding namespace 10 to {subsystem}: Successful" in caplog.text - assert f"no_auto_visible: True" in caplog.text + assert "no_auto_visible: True" in caplog.text def test_add_host_to_namespace(self, caplog, gateway): caplog.clear() @@ -531,9 +537,9 @@ def test_change_namespace_visibility(self, caplog, gateway): assert f"No change to namespace 8 in {subsystem} visibility, nothing to do" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "8"]) - assert f'"nsid": 8' in caplog.text - assert f'"auto_visible": true' in caplog.text - assert f'"hosts": []' in caplog.text + assert '"nsid": 8' in caplog.text + assert '"auto_visible": true' in caplog.text + assert '"hosts": []' in caplog.text caplog.clear() cli(["namespace", "add_host", "--subsystem", subsystem, "--nsid", "8", "--host-nqn", host8]) assert f"Failure adding host {host8} to namespace 8 on {subsystem}: Namespace is visible to all hosts" in caplog.text @@ -542,17 +548,17 @@ def test_change_namespace_visibility(self, caplog, gateway): assert f'Changing visibility of namespace 8 in {subsystem} to "visible to selected hosts": Successful' in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "8"]) - assert f'"nsid": 8' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text - assert f'"hosts": []' in caplog.text + assert '"nsid": 8' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text + assert '"hosts": []' in caplog.text caplog.clear() cli(["namespace", "add_host", "--subsystem", subsystem, "--nsid", "8", "--host-nqn", host8]) assert f"Adding host {host8} to namespace 8 on {subsystem}: Successful" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "8"]) - assert f'"nsid": 8' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text - assert f'"hosts": []' not in caplog.text + assert '"nsid": 8' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text + assert '"hosts": []' not in caplog.text assert f"{host8}" in caplog.text def test_change_namespace_visibility_wrong_params(self, caplog, gateway): @@ -608,7 +614,7 @@ def test_add_too_many_namespaces_to_a_subsystem(self, caplog, gateway): caplog.clear() cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image9, "--nsid", "3000", "--size", "16MB", "--rbd-create-image"]) assert f"Failure adding namespace using NSID 3000 to {subsystem}: Requested NSID 3000 is bigger than the maximal one (2049)" in caplog.text - assert f"Received request to delete bdev" in caplog.text + assert "Received request to delete bdev" in caplog.text caplog.clear() cli(["subsystem", "add", "--subsystem", subsystem5, "--no-group-append", "--max-namespaces", "1"]) assert f"Adding subsystem {subsystem5}: Successful" in caplog.text @@ -618,7 +624,7 @@ def test_add_too_many_namespaces_to_a_subsystem(self, caplog, gateway): caplog.clear() cli(["namespace", "add", "--subsystem", subsystem5, "--rbd-pool", pool, "--rbd-image", image10, "--size", "16MB", "--rbd-create-image"]) assert f"Failure adding namespace to {subsystem5}: Subsystem's maximal number of namespaces (1) has already been reached" in caplog.text - assert f"Received request to delete bdev" in caplog.text + assert "Received request to delete bdev" in caplog.text caplog.clear() cli(["subsystem", "del", "--subsystem", subsystem5, "--force"]) assert f"Deleting subsystem {subsystem5}: Successful" in caplog.text @@ -659,10 +665,10 @@ def test_add_too_many_namespaces_with_hosts(self, caplog, gateway): def test_list_namespace_with_hosts(self, caplog, gateway): caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "9"]) - assert f'"nsid": 9' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text + assert '"nsid": 9' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text assert f'"{host8}"' in caplog.text - assert f'"hosts": []' not in caplog.text + assert '"hosts": []' not in caplog.text def test_del_namespace_host(self, caplog, gateway): caplog.clear() @@ -688,19 +694,19 @@ def test_del_namespace_host(self, caplog, gateway): assert f"Failure deleting host {host8} from namespace 9 on {subsystemX}: Can't find subsystem" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "9"]) - assert f'"nsid": 9' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text + assert '"nsid": 9' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text assert f'"{host8}"' in caplog.text - assert f'"hosts": []' not in caplog.text + assert '"hosts": []' not in caplog.text caplog.clear() cli(["namespace", "del_host", "--subsystem", subsystem, "--nsid", "9", "--host-nqn", host8]) assert f"Deleting host {host8} from namespace 9 on {subsystem}: Successful" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "9"]) - assert f'"nsid": 9' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text + assert '"nsid": 9' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text assert f'"{host8}"' not in caplog.text - assert f'"hosts": []' in caplog.text + assert '"hosts": []' in caplog.text caplog.clear() cli(["namespace", "del_host", "--subsystem", subsystem, "--nsid", "9", "--host-nqn", hostxx]) assert f"Failure deleting host {hostxx} from namespace 9 on {subsystem}: Host is not found in namespace's host list" in caplog.text @@ -713,12 +719,12 @@ def test_add_namespace_multiple_hosts(self, caplog, gateway): assert f"Adding host {host10} to namespace 9 on {subsystem}: Successful" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "9"]) - assert f'"nsid": 9' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text + assert '"nsid": 9' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text assert f'"{host8}"' in caplog.text assert f'"{host9}"' in caplog.text assert f'"{host10}"' in caplog.text - assert f'"hosts": []' not in caplog.text + assert '"hosts": []' not in caplog.text def test_del_namespace_multiple_hosts(self, caplog, gateway): caplog.clear() @@ -728,19 +734,19 @@ def test_del_namespace_multiple_hosts(self, caplog, gateway): assert f"Deleting host {host10} from namespace 9 on {subsystem}: Successful" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "9"]) - assert f'"nsid": 9' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text + assert '"nsid": 9' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text assert f'"{host8}"' not in caplog.text assert f'"{host9}"' not in caplog.text assert f'"{host10}"' not in caplog.text - assert f'"hosts": []' in caplog.text + assert '"hosts": []' in caplog.text def test_list_namespace_with_no_hosts(self, caplog, gateway): caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "10"]) - assert f'"nsid": 10' in caplog.text - assert '"auto_visible":' not in caplog.text or f'"auto_visible": false' in caplog.text - assert f'"hosts": []' in caplog.text + assert '"nsid": 10' in caplog.text + assert '"auto_visible":' not in caplog.text or '"auto_visible": false' in caplog.text + assert '"hosts": []' in caplog.text def test_add_too_many_namespaces(self, caplog, gateway): caplog.clear() @@ -754,19 +760,19 @@ def test_resize_namespace(self, caplog, gateway): gw, stub = gateway caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert '"block_size": 512' in caplog.text assert '"rbd_image_size": "16777216"' in caplog.text assert f'"uuid": "{uuid2}"' in caplog.text caplog.clear() cli(["namespace", "resize", "--subsystem", subsystem, "--nsid", "6", "--size", "2MB"]) - assert f"new size 2097152 bytes is smaller than current size 16777216 bytes" in caplog.text + assert "new size 2097152 bytes is smaller than current size 16777216 bytes" in caplog.text caplog.clear() cli(["namespace", "resize", "--subsystem", subsystem, "--nsid", "6", "--size", "2"]) - assert f"new size 2097152 bytes is smaller than current size 16777216 bytes" in caplog.text + assert "new size 2097152 bytes is smaller than current size 16777216 bytes" in caplog.text caplog.clear() cli(["namespace", "resize", "--subsystem", subsystem, "--nsid", "6", "--size", "3145728B"]) - assert f"new size 3145728 bytes is smaller than current size 16777216 bytes" in caplog.text + assert "new size 3145728 bytes is smaller than current size 16777216 bytes" in caplog.text caplog.clear() cli(["namespace", "resize", "--subsystem", subsystem, "--nsid", "6", "--size", "32MB"]) assert f"Resizing namespace 6 in {subsystem} to 32 MiB: Successful" in caplog.text @@ -799,7 +805,7 @@ def test_resize_namespace(self, caplog, gateway): assert rc == 2 caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert '"block_size": 512' in caplog.text assert '"rbd_image_size": "33554432"' in caplog.text assert f'"uuid": "{uuid2}"' in caplog.text @@ -831,7 +837,7 @@ def test_resize_namespace(self, caplog, gateway): assert f"Resizing namespace 6 in {subsystem} to 64 MiB: Successful" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--uuid", uuid2]) - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert '"block_size": 512' in caplog.text assert '"rbd_image_size": "67108864"' in caplog.text assert f'"uuid": "{uuid2}"' in caplog.text @@ -847,7 +853,7 @@ def test_resize_namespace(self, caplog, gateway): cli(["namespace", "resize", "--subsystem", subsystem, "--nsid", "6", "--size", "32MB"]) assert f"Failure resizing namespace 6 on {subsystem}: new size 33554432 bytes is smaller than current size 67108864 bytes" in caplog.text ns = cli_test(["namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert ns != None + assert ns is not None assert ns.status == 0 assert len(ns.namespaces) == 1 assert ns.namespaces[0].rbd_image_size == 67108864 @@ -861,7 +867,7 @@ def test_resize_namespace(self, caplog, gateway): def test_set_namespace_qos_limits(self, caplog, gateway): caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert '"rw_ios_per_second": "0"' in caplog.text assert '"rw_mbytes_per_second": "0"' in caplog.text assert '"r_mbytes_per_second": "0"' in caplog.text @@ -872,7 +878,7 @@ def test_set_namespace_qos_limits(self, caplog, gateway): assert f"No previous QOS limits found, this is the first time the limits are set for namespace 6 on {subsystem}" in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert f'"uuid": "{uuid2}"' in caplog.text assert '"rw_ios_per_second": "2000"' in caplog.text assert '"rw_mbytes_per_second": "0"' in caplog.text @@ -885,7 +891,7 @@ def test_set_namespace_qos_limits(self, caplog, gateway): caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--uuid", uuid2]) assert f'"uuid": "{uuid2}"' in caplog.text - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert '"rw_ios_per_second": "2000"' in caplog.text assert '"rw_mbytes_per_second": "30"' in caplog.text assert '"r_mbytes_per_second": "0"' in caplog.text @@ -897,7 +903,7 @@ def test_set_namespace_qos_limits(self, caplog, gateway): assert f"No previous QOS limits found, this is the first time the limits are set for namespace 6 on {subsystem}" not in caplog.text caplog.clear() cli(["--format", "json", "namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert '"rw_ios_per_second": "2000"' in caplog.text assert '"rw_mbytes_per_second": "30"' in caplog.text assert '"r_mbytes_per_second": "15"' in caplog.text @@ -927,15 +933,15 @@ def test_namespace_io_stats(self, caplog, gateway): assert f'IO statistics for namespace 6 in {subsystem}' in caplog.text caplog.clear() cli(["--format", "json", "namespace", "get_io_stats", "--subsystem", subsystem, "--nsid", "6"]) - assert f'"status": 0' in caplog.text + assert '"status": 0' in caplog.text assert f'"subsystem_nqn": "{subsystem}"' in caplog.text - assert f'"nsid": 6' in caplog.text + assert '"nsid": 6' in caplog.text assert f'"uuid": "{uuid2}"' in caplog.text - assert f'"ticks":' in caplog.text - assert f'"bytes_written":' in caplog.text - assert f'"bytes_read":' in caplog.text - assert f'"max_write_latency_ticks":' in caplog.text - assert f'"io_error":' in caplog.text + assert '"ticks":' in caplog.text + assert '"bytes_written":' in caplog.text + assert '"bytes_read":' in caplog.text + assert '"max_write_latency_ticks":' in caplog.text + assert '"io_error":' in caplog.text caplog.clear() rc = 0 try: @@ -978,18 +984,18 @@ def test_add_host(self, caplog, host): def test_add_host_invalid_nqn(self, caplog): caplog.clear() cli(["host", "add", "--subsystem", subsystem, "--host-nqn", "nqn.2016"]) - assert f'NQN "nqn.2016" is too short, minimal length is 11' in caplog.text + assert 'NQN "nqn.2016" is too short, minimal length is 11' in caplog.text caplog.clear() cli(["host", "add", "--subsystem", subsystem, "--host-nqn", "nqn.2X16-06.io.spdk:host1"]) - assert f"invalid date code" in caplog.text + assert "invalid date code" in caplog.text caplog.clear() cli(["host", "add", "--subsystem", subsystem, "--host-nqn", "nqn.2016-06.io.spdk:host1_X"]) - assert f"Invalid host NQN" in caplog.text - assert f"contains invalid characters" in caplog.text + assert "Invalid host NQN" in caplog.text + assert "contains invalid characters" in caplog.text caplog.clear() cli(["host", "add", "--subsystem", f"{subsystem}_X", "--host-nqn", "nqn.2016-06.io.spdk:host2"]) - assert f"Invalid subsystem NQN" in caplog.text - assert f"contains invalid characters" in caplog.text + assert "Invalid subsystem NQN" in caplog.text + assert "contains invalid characters" in caplog.text def test_host_list(self, caplog): caplog.clear() @@ -1008,7 +1014,6 @@ def test_create_listener(self, caplog, listener, gateway): assert "ipv4" in caplog.text.lower() assert f"Adding {subsystem} listener at {listener[1]}:{listener[3]}: Successful" in caplog.text - @pytest.mark.parametrize("listener_ipv6", listener_list_ipv6) def test_create_listener_ipv6(self, caplog, listener_ipv6, gateway): caplog.clear() @@ -1031,10 +1036,10 @@ def test_list_listeners(self, caplog, listener, listener_ipv6, gateway): assert f'"host_name": "{host_name}"' in caplog.text assert f'"traddr": "{listener[1]}"' in caplog.text assert f'"trsvcid": {listener[3]}' in caplog.text - assert f'"adrfam": "ipv4"' in caplog.text + assert '"adrfam": "ipv4"' in caplog.text assert f'"traddr": "[{listener_ipv6[1]}]"' in caplog.text assert f'"trsvcid": {listener_ipv6[3]}' in caplog.text - assert f'"adrfam": "ipv6"' in caplog.text + assert '"adrfam": "ipv6"' in caplog.text @pytest.mark.parametrize("listener", listener_list_negative_port) def test_create_listener_negative_port(self, caplog, listener, gateway): @@ -1084,6 +1089,7 @@ def test_create_listener_on_discovery(self, caplog, listener, gateway): cli(["listener", "add", "--host-name", host_name] + listener) assert "Can't create a listener for a discovery subsystem" in caplog.text + class TestDelete: @pytest.mark.parametrize("host", host_list) def test_remove_host(self, caplog, host, gateway): @@ -1170,7 +1176,7 @@ def test_remove_namespace(self, caplog, gateway): gw, stub = gateway caplog.clear() ns_list = cli_test(["namespace", "list", "--subsystem", subsystem, "--nsid", "6"]) - assert ns_list != None + assert ns_list is not None assert ns_list.status == 0 assert len(ns_list.namespaces) == 1 bdev_name = ns_list.namespaces[0].bdev_name @@ -1238,6 +1244,7 @@ def test_delete_subsystem_with_discovery_nqn(self, caplog, gateway): assert "Can't delete a discovery subsystem" in caplog.text assert rc == 2 + class TestCreateWithAna: def test_create_subsystem_ana(self, caplog, gateway): caplog.clear() @@ -1267,8 +1274,8 @@ def test_create_listener_ana(self, caplog, listener, gateway): assert "ipv4" in caplog.text.lower() assert f"Adding {subsystem} listener at {listener[1]}:{listener[3]}: Successful" in caplog.text -class TestDeleteAna: +class TestDeleteAna: @pytest.mark.parametrize("listener", listener_list) def test_delete_listener_ana(self, caplog, listener, gateway): caplog.clear() @@ -1288,6 +1295,7 @@ def test_delete_subsystem_ana(self, caplog, gateway): cli(["subsystem", "list"]) assert "No subsystems" in caplog.text + class TestSubsysWithGroupName: def test_create_subsys_group_name(self, caplog, gateway): caplog.clear() @@ -1301,12 +1309,13 @@ def test_create_subsys_group_name(self, caplog, gateway): caplog.clear() cli(["subsystem", "add", "--subsystem", subsystem4, "--no-group-append"]) assert f"Adding subsystem {subsystem4}: Successful" in caplog.text - assert f"Subsystem NQN will not be changed" in caplog.text + assert "Subsystem NQN will not be changed" in caplog.text assert f"Adding subsystem {subsystem4}.{group_name}: Successful" not in caplog.text cli(["--format", "json", "subsystem", "list"]) assert f'"nqn": "{subsystem4}.{group_name}"' not in caplog.text assert f'"nqn": "{subsystem4}"' in caplog.text + class TestTooManySubsystemsAndHosts: def test_add_too_many_subsystem(self, caplog, gateway): caplog.clear() @@ -1334,6 +1343,7 @@ def test_too_many_hosts(self, caplog, gateway): cli(["host", "add", "--subsystem", subsystem6, "--host-nqn", host5]) assert f"Failure adding host {host5} to {subsystem6}: Maximal number of hosts for subsystem (4) has already been reached" in caplog.text + class TestGwLogLevel: def test_gw_log_level(self, caplog, gateway): caplog.clear() @@ -1341,13 +1351,13 @@ def test_gw_log_level(self, caplog, gateway): assert 'Gateway log level is "debug"' in caplog.text caplog.clear() cli(["gw", "set_log_level", "--level", "error"]) - assert f'Set gateway log level to "error": Successful' in caplog.text + assert 'Set gateway log level to "error": Successful' in caplog.text caplog.clear() cli(["gw", "get_log_level"]) assert 'Gateway log level is "error"' in caplog.text caplog.clear() cli(["gw", "set_log_level", "-l", "CRITICAL"]) - assert f'Set gateway log level to "critical": Successful' in caplog.text + assert 'Set gateway log level to "critical": Successful' in caplog.text caplog.clear() cli(["gw", "get_log_level"]) assert 'Gateway log level is "critical"' in caplog.text @@ -1362,14 +1372,15 @@ def test_gw_log_level(self, caplog, gateway): assert rc == 2 caplog.clear() cli(["--format", "json", "gw", "get_log_level"]) - assert f'"log_level": "critical"' in caplog.text + assert '"log_level": "critical"' in caplog.text caplog.clear() cli(["--log-level", "critical", "gw", "set_log_level", "--level", "DEBUG"]) - assert f'Set gateway log level to "debug": Successful' not in caplog.text + assert 'Set gateway log level to "debug": Successful' not in caplog.text caplog.clear() cli(["gw", "get_log_level"]) assert 'Gateway log level is "debug"' in caplog.text + class TestSPDKLOg: def test_log_flags(self, caplog, gateway): caplog.clear() diff --git a/tests/test_cli_change_keys.py b/tests/test_cli_change_keys.py index f3392117..0de55b40 100644 --- a/tests/test_cli_change_keys.py +++ b/tests/test_cli_change_keys.py @@ -23,6 +23,7 @@ hostpsk1 = "NVMeTLSkey-1:01:YzrPElk4OYy1uUERriPwiiyEJE/+J5ckYpLB+5NHMsR2iBuT:" config = "ceph-nvmeof.conf" + @pytest.fixture(scope="module") def two_gateways(config): """Sets up and tears down two Gateways""" @@ -67,6 +68,7 @@ def two_gateways(config): gatewayA.server.stop(grace=1) gatewayB.server.stop(grace=1) + def test_change_host_key(caplog, two_gateways): gatewayA, stubA, gatewayB, stubB = two_gateways caplog.clear() diff --git a/tests/test_cli_change_lb.py b/tests/test_cli_change_lb.py index fdfd0207..c628a188 100755 --- a/tests/test_cli_change_lb.py +++ b/tests/test_cli_change_lb.py @@ -19,6 +19,7 @@ config = "ceph-nvmeof.conf" namespace_count = 20 + @pytest.fixture(scope="module") def two_gateways(config): """Sets up and tears down two Gateways""" @@ -64,16 +65,19 @@ def two_gateways(config): gatewayA.server.stop(grace=1) gatewayB.server.stop(grace=1) + def verify_one_namespace_lb_group(caplog, gw_port, subsys, nsid_to_verify, grp): caplog.clear() cli(["--server-port", gw_port, "--format", "json", "namespace", "list", "--subsystem", subsys, "--nsid", nsid_to_verify]) assert f'"nsid": {nsid_to_verify},' in caplog.text assert f'"load_balancing_group": {grp},' in caplog.text + def verify_namespaces(caplog, gw_port, subsys, first_nsid, last_nsid, grp): for ns in range(first_nsid, last_nsid + 1): verify_one_namespace_lb_group(caplog, gw_port, subsys, str(ns), grp) + def verify_namespaces_using_get_subsystems(caplog, gw_port, subsys, first_nsid, last_nsid, grp): caplog.clear() subsys_info = cli_test(["--server-port", gw_port, "get_subsystems"]) @@ -84,6 +88,7 @@ def verify_namespaces_using_get_subsystems(caplog, gw_port, subsys, first_nsid, assert subsys_info.subsystems[0].namespaces[ns - 1].nsid == ns assert subsys_info.subsystems[0].namespaces[ns - 1].anagrpid == grp + def verify_namespaces_using_spdk_get_subsystems(caplog, gw, subsys, first_nsid, last_nsid, grp): caplog.clear() subsys_info = rpc_nvmf.nvmf_get_subsystems(gw.gateway_rpc.spdk_rpc_client) @@ -94,6 +99,7 @@ def verify_namespaces_using_spdk_get_subsystems(caplog, gw, subsys, first_nsid, assert subsys_info[0]["namespaces"][ns - 1]["nsid"] == ns assert subsys_info[0]["namespaces"][ns - 1]["anagrpid"] == grp + def create_namespaces(caplog, ns_count, subsys): for i in range(1, 1 + (ns_count // 2)): caplog.clear() @@ -104,12 +110,14 @@ def create_namespaces(caplog, ns_count, subsys): cli(["namespace", "add", "--subsystem", subsys, "--rbd-pool", pool, "--rbd-image", f"{image}{i}", "--size", "16MB", "--rbd-create-image", "--load-balancing-group", anagrpid2]) assert f"Adding namespace {i} to {subsys}: Successful" in caplog.text + def try_change_one_namespace_lb_group_no_listeners(caplog, subsys, nsid_to_change, new_group): caplog.clear() cli(["--server-port", "5502", "namespace", "change_load_balancing_group", "--subsystem", subsys, "--nsid", nsid_to_change, "--load-balancing-group", new_group]) time.sleep(8) assert "is owned by gateway None so try this command from it" in caplog.text + def change_one_namespace_lb_group(caplog, subsys, nsid_to_change, new_group): caplog.clear() cli(["--server-port", "5502", "namespace", "change_load_balancing_group", "--subsystem", subsys, "--nsid", nsid_to_change, "--load-balancing-group", new_group]) @@ -120,10 +128,11 @@ def change_one_namespace_lb_group(caplog, subsys, nsid_to_change, new_group): time.sleep(8) assert f"Changing load balancing group of namespace {nsid_to_change} in {subsys} to {new_group}: Successful" in caplog.text - assert f"Received manual request to change load balancing group for namespace with NSID {nsid_to_change} in {subsys} to {new_group}, context: 6}" return hostnqn + def create_resource_by_index(stub, i, caplog): subsystem = f"{subsystem_prefix}{i}" subsystem_req = pb2.create_subsystem_req(subsystem_nqn=subsystem, max_namespaces=256, enable_ha=True, no_group_append=True) ret_subsystem = stub.create_subsystem(subsystem_req) assert ret_subsystem.status == 0 - if caplog != None: + if caplog is not None: assert f"create_subsystem {subsystem}: True" in caplog.text assert f"Failure creating subsystem {subsystem}" not in caplog.text namespace_req = pb2.namespace_add_req(subsystem_nqn=subsystem, rbd_pool_name=pool, rbd_image_name=image, block_size=4096, - create_image=True, size=16*1024*1024, force=True) + create_image=True, size=16 * 1024 * 1024, force=True) ret_namespace = stub.namespace_add(namespace_req) assert ret_namespace.status == 0 hostnqn = build_host_nqn(i) @@ -158,12 +158,13 @@ def create_resource_by_index(stub, i, caplog): host_req = pb2.add_host_req(subsystem_nqn=subsystem, host_nqn="*") ret_host = stub.add_host(host_req) assert ret_host.status == 0 - if caplog != None: + if caplog is not None: assert f"add_host {hostnqn}: True" in caplog.text assert "add_host *: True" in caplog.text assert f"Failure allowing open host access to {subsystem}" not in caplog.text assert f"Failure adding host {hostnqn} to {subsystem}" not in caplog.text + def check_resource_by_index(i, subsys_list, hosts_info): subsystem = f"{subsystem_prefix}{i}" hostnqn = build_host_nqn(i) @@ -182,6 +183,7 @@ def check_resource_by_index(i, subsys_list, hosts_info): pass assert found_host + def test_multi_gateway_omap_reread(config, conn_omap_reread, caplog): """Tests reading out of date OMAP file """ @@ -195,7 +197,7 @@ def test_multi_gateway_omap_reread(config, conn_omap_reread, caplog): subsystem_req = pb2.create_subsystem_req(subsystem_nqn=nqn, serial_number=serial, max_namespaces=256, enable_ha=True, no_group_append=True) namespace_req = pb2.namespace_add_req(subsystem_nqn=nqn, nsid=nsid, rbd_pool_name=pool, rbd_image_name=image, block_size=4096, - create_image=True, size=16*1024*1024, force=True) + create_image=True, size=16 * 1024 * 1024, force=True) subsystem_list_req = pb2.list_subsystems_req() ret_subsystem = stubA.create_subsystem(subsystem_req) @@ -215,9 +217,9 @@ def test_multi_gateway_omap_reread(config, conn_omap_reread, caplog): assert len(listA) == num_subsystems ns2_req = pb2.namespace_add_req(subsystem_nqn=nqn, - rbd_pool_name=pool, - rbd_image_name=image, - block_size=4096, create_image=True, size=16*1024*1024, force=True) + rbd_pool_name=pool, + rbd_image_name=image, + block_size=4096, create_image=True, size=16 * 1024 * 1024, force=True) ret_ns2 = stubB.namespace_add(ns2_req) assert ret_ns2.status == 0 assert "The file is not current, will reload it and try again" in caplog.text @@ -227,15 +229,15 @@ def test_multi_gateway_omap_reread(config, conn_omap_reread, caplog): stubB.list_subsystems(subsystem_list_req), preserving_proto_field_name=True, including_default_value_fields=True))['subsystems'] assert len(listB) == num_subsystems - assert listB[num_subsystems-1]["nqn"] == nqn - assert listB[num_subsystems-1]["serial_number"] == serial - assert listB[num_subsystems-1]["namespace_count"] == num_subsystems # We created one namespace on each subsystem + assert listB[num_subsystems - 1]["nqn"] == nqn + assert listB[num_subsystems - 1]["serial_number"] == serial + assert listB[num_subsystems - 1]["namespace_count"] == num_subsystems # We created one namespace on each subsystem caplog.clear() ns3_req = pb2.namespace_add_req(subsystem_nqn=nqn, - rbd_pool_name=pool, - rbd_image_name=image, - block_size=4096, create_image=True, size=16*1024*1024, force=True) + rbd_pool_name=pool, + rbd_image_name=image, + block_size=4096, create_image=True, size=16 * 1024 * 1024, force=True) ret_ns3 = stubB.namespace_add(ns3_req) assert ret_ns3.status == 0 assert "The file is not current, will reload it and try again" not in caplog.text @@ -248,19 +250,21 @@ def test_multi_gateway_omap_reread(config, conn_omap_reread, caplog): assert len(bdevsB) == 3 assert bdevsA[0]["uuid"] == bdevsB[0]["uuid"] + def test_trying_to_lock_twice(config, image, conn_lock_twice, caplog): """Tests an attempt to lock the OMAP file from two gateways at the same time """ caplog.clear() stubA, stubB = conn_lock_twice - with pytest.raises(Exception) as ex: + with pytest.raises(Exception): create_resource_by_index(stubA, 100000, None) create_resource_by_index(stubB, 100001, None) assert "OMAP file unlock was disabled, will not unlock file" in caplog.text assert "The OMAP file is locked, will try again in" in caplog.text assert "Unable to lock OMAP file" in caplog.text + def test_multi_gateway_concurrent_changes(config, image, conn_concurrent, caplog): """Tests concurrent changes to the OMAP from two gateways """ @@ -281,7 +285,7 @@ def test_multi_gateway_concurrent_changes(config, image, conn_concurrent, caplog listener_ret = stubA.create_listener(listener_req) assert listener_ret.status == 0 assert f"Received request to create {gwA.host_name} TCP ipv4 listener for {subsystem_prefix}0 at 127.0.0.1:5001" in caplog.text - assert f"create_listener: True" in caplog.text + assert "create_listener: True" in caplog.text timeout = 15 # Maximum time to wait (in seconds) start_time = time.time() @@ -313,6 +317,7 @@ def test_multi_gateway_concurrent_changes(config, image, conn_concurrent, caplog check_resource_by_index(i, subListA, hostListA) check_resource_by_index(i, subListB, hostListB) + def test_multi_gateway_listener_update(config, image, conn_concurrent, caplog): """Tests listener update after subsystem deletion """ @@ -327,24 +332,24 @@ def test_multi_gateway_listener_update(config, image, conn_concurrent, caplog): assert f"Failure creating subsystem {subsystem}" not in caplog.text caplog.clear() listenerA_req = pb2.create_listener_req(nqn=subsystem, - host_name=gwA.host_name, - adrfam="ipv4", - traddr="127.0.0.1", - trsvcid=5101) + host_name=gwA.host_name, + adrfam="ipv4", + traddr="127.0.0.1", + trsvcid=5101) listener_ret = stubA.create_listener(listenerA_req) assert listener_ret.status == 0 assert f"Received request to create {gwA.host_name} TCP ipv4 listener for {subsystem} at 127.0.0.1:5101" in caplog.text - assert f"create_listener: True" in caplog.text + assert "create_listener: True" in caplog.text caplog.clear() listenerB_req = pb2.create_listener_req(nqn=subsystem, - host_name=gwB.host_name, - adrfam="ipv4", - traddr="127.0.0.1", - trsvcid=5102) + host_name=gwB.host_name, + adrfam="ipv4", + traddr="127.0.0.1", + trsvcid=5102) listener_ret = stubB.create_listener(listenerB_req) assert listener_ret.status == 0 assert f"Received request to create {gwB.host_name} TCP ipv4 listener for {subsystem} at 127.0.0.1:5102" in caplog.text - assert f"create_listener: True" in caplog.text + assert "create_listener: True" in caplog.text caplog.clear() subsystem_del_req = pb2.delete_subsystem_req(subsystem_nqn=subsystem) ret_subsystem = stubA.delete_subsystem(subsystem_del_req) @@ -360,11 +365,11 @@ def test_multi_gateway_listener_update(config, image, conn_concurrent, caplog): listener_ret = stubA.create_listener(listenerA_req) assert listener_ret.status == 0 assert f"Received request to create {gwA.host_name} TCP ipv4 listener for {subsystem} at 127.0.0.1:5101" in caplog.text - assert f"create_listener: True" in caplog.text + assert "create_listener: True" in caplog.text assert f"Failure adding {subsystem} listener at 127.0.0.1:5101" not in caplog.text caplog.clear() listener_ret = stubB.create_listener(listenerB_req) assert listener_ret.status == 0 assert f"Received request to create {gwB.host_name} TCP ipv4 listener for {subsystem} at 127.0.0.1:5102" in caplog.text - assert f"create_listener: True" in caplog.text + assert "create_listener: True" in caplog.text assert f"Failure adding {subsystem} listener at 127.0.0.1:5102" not in caplog.text diff --git a/tests/test_psk.py b/tests/test_psk.py index 536035c8..4f787bb4 100644 --- a/tests/test_psk.py +++ b/tests/test_psk.py @@ -4,13 +4,7 @@ from control.cli import main as cli from control.cli import main_test as cli_test from control.cephutils import CephUtils -from control.utils import GatewayUtils -from control.config import GatewayConfig import grpc -from control.proto import gateway_pb2 as pb2 -from control.proto import gateway_pb2_grpc as pb2_grpc -import os -import os.path image = "mytestdevimage" pool = "rbd" @@ -30,7 +24,7 @@ hostpsk1 = "NVMeTLSkey-1:01:YzrPElk4OYy1uUERriPwiiyEJE/+J5ckYpLB+5NHMsR2iBuT:" hostpsk2 = "NVMeTLSkey-1:02:FTFds4vH4utVcfrOforxbrWIgv+Qq4GQHgMdWwzDdDxE1bAqK2mOoyXxmbJxGeueEVVa/Q==:" -hostpsk3 = "junk" +hostpsk3 = "junk" hostpsk4 = "NVMeTLSkey-1:01:YzrPElk4OYy1uUERriPwiiyEJE/+J5ckYpLB+5NHMsR2iBuT:" hostdhchap1 = "DHHC-1:00:MWPqcx1Ug1debg8fPIGpkqbQhLcYUt39k7UWirkblaKEH1kE:" @@ -39,6 +33,7 @@ addr = "127.0.0.1" config = "ceph-nvmeof.conf" + @pytest.fixture(scope="module") def gateway(config): """Sets up and tears down Gateway""" @@ -57,15 +52,15 @@ def gateway(config): gateway.serve() # Bind the client and Gateway - channel = grpc.insecure_channel(f"{addr}:{port}") + grpc.insecure_channel(f"{addr}:{port}") yield gateway.gateway_rpc # Stop gateway gateway.server.stop(grace=1) gateway.gateway_rpc.gateway_state.delete_state() + def test_setup(caplog, gateway): - gw = gateway caplog.clear() cli(["subsystem", "add", "--subsystem", subsystem]) assert f"create_subsystem {subsystem}: True" in caplog.text @@ -73,17 +68,19 @@ def test_setup(caplog, gateway): cli(["namespace", "add", "--subsystem", subsystem, "--rbd-pool", pool, "--rbd-image", image, "--rbd-create-image", "--size", "16MB"]) assert f"Adding namespace 1 to {subsystem}: Successful" in caplog.text + def test_create_secure_with_any_host(caplog, gateway): caplog.clear() cli(["host", "add", "--subsystem", subsystem, "--host-nqn", "*"]) assert f"Allowing open host access to {subsystem}: Successful" in caplog.text caplog.clear() cli(["listener", "add", "--subsystem", subsystem, "--host-name", host_name, "-a", addr, "-s", "5001", "--secure"]) - assert f"Secure channel is only allowed for subsystems in which \"allow any host\" is off" in caplog.text + assert "Secure channel is only allowed for subsystems in which \"allow any host\" is off" in caplog.text caplog.clear() cli(["host", "del", "--subsystem", subsystem, "--host-nqn", "*"]) assert f"Disabling open host access to {subsystem}: Successful" in caplog.text + def test_create_secure(caplog, gateway): caplog.clear() cli(["listener", "add", "--subsystem", subsystem, "--host-name", host_name, "-a", addr, "-s", "5001", "--secure"]) @@ -98,6 +95,7 @@ def test_create_secure(caplog, gateway): cli(["host", "add", "--subsystem", subsystem, "--host-nqn", hostnqn4, "--psk", hostpsk4]) assert f"Adding host {hostnqn4} to {subsystem}: Successful" in caplog.text + def test_create_not_secure(caplog, gateway): caplog.clear() cli(["listener", "add", "--subsystem", subsystem, "--host-name", host_name, "-a", addr, "-s", "5002"]) @@ -109,6 +107,7 @@ def test_create_not_secure(caplog, gateway): cli(["host", "add", "--subsystem", subsystem, "--host-nqn", hostnqn7]) assert f"Adding host {hostnqn7} to {subsystem}: Successful" in caplog.text + def test_create_secure_list(caplog, gateway): caplog.clear() rc = 0 @@ -118,13 +117,15 @@ def test_create_secure_list(caplog, gateway): rc = int(str(sysex)) pass assert rc == 2 - assert f"error: Can't have more than one host NQN when PSK keys are used" in caplog.text + assert "error: Can't have more than one host NQN when PSK keys are used" in caplog.text + def test_create_secure_junk_key(caplog, gateway): caplog.clear() cli(["host", "add", "--subsystem", subsystem, "--host-nqn", hostnqn3, "--psk", hostpsk3]) assert f"Failure adding host {hostnqn3} to {subsystem}" in caplog.text + def test_create_secure_no_key(caplog, gateway): caplog.clear() rc = 0 @@ -134,7 +135,8 @@ def test_create_secure_no_key(caplog, gateway): rc = int(str(sysex)) pass assert rc == 2 - assert f"error: argument --psk/-p: expected one argument" in caplog.text + assert "error: argument --psk/-p: expected one argument" in caplog.text + def test_list_psk_hosts(caplog, gateway): caplog.clear() @@ -161,6 +163,7 @@ def test_list_psk_hosts(caplog, gateway): assert False assert found == 5 + def test_allow_any_host_with_psk(caplog, gateway): caplog.clear() rc = 0 @@ -170,7 +173,8 @@ def test_allow_any_host_with_psk(caplog, gateway): rc = int(str(sysex)) pass assert rc == 2 - assert f"error: PSK key is only allowed for specific hosts" in caplog.text + assert "error: PSK key is only allowed for specific hosts" in caplog.text + def test_psk_with_dhchap(caplog, gateway): caplog.clear() @@ -178,22 +182,24 @@ def test_psk_with_dhchap(caplog, gateway): assert f"Adding host {hostnqn10} to {subsystem}: Successful" in caplog.text assert f"Host {hostnqn10} has a DH-HMAC-CHAP key but subsystem {subsystem} has no key, a unidirectional authentication will be used" in caplog.text + def test_list_listeners(caplog, gateway): caplog.clear() listeners = cli_test(["listener", "list", "--subsystem", subsystem]) assert len(listeners.listeners) == 2 found = 0 - for l in listeners.listeners: - if l.trsvcid == 5001: + for lstnr in listeners.listeners: + if lstnr.trsvcid == 5001: found += 1 - assert l.secure - elif l.trsvcid == 5002: + assert lstnr.secure + elif lstnr.trsvcid == 5002: found += 1 - assert not l.secure + assert not lstnr.secure else: assert False assert found == 2 + def test_add_host_with_key_host_list(caplog, gateway): caplog.clear() rc = 0 diff --git a/tests/test_server.py b/tests/test_server.py index fdfd209c..d0dee43f 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -7,9 +7,10 @@ import unittest from control.server import GatewayServer + class TestServer(unittest.TestCase): # Location of core files in test env. - core_dir="/tmp/coredump" + core_dir = "/tmp/coredump" @pytest.fixture(autouse=True) def _config(self, config): @@ -18,11 +19,11 @@ def _config(self, config): def validate_exception(self, e): pattern = r'Gateway subprocess terminated pid=(\d+) exit_code=(-?\d+)' m = re.match(pattern, e.code) - assert(m) + assert m pid = int(m.group(1)) code = int(m.group(2)) - assert(pid > 0) - assert(code) + assert pid > 0 + assert code def remove_core_files(self, directory_path): # List all files starting with "core." in the core directory @@ -38,9 +39,9 @@ def remove_core_files(self, directory_path): print(f"Removed: {file_path}") def assert_no_core_files(self, directory_path): - assert(os.path.exists(directory_path) and os.path.isdir(directory_path)) + assert os.path.exists(directory_path) and os.path.isdir(directory_path) files = [f for f in os.listdir(directory_path) if os.path.isfile(os.path.join(directory_path, f)) and f.startswith("core.")] - assert(len(files) == 0) + assert len(files) == 0 def test_spdk_exception(self): """Tests spdk sub process exiting with error.""" @@ -62,13 +63,13 @@ def test_no_coredumps_on_gracefull_shutdown(self): gateway.serve() time.sleep(10) # exited context, sub processes should terminate gracefully - time.sleep(10) # let it dump + time.sleep(10) # let it dump self.assert_no_core_files(self.core_dir) def test_monc_exit(self): """Tests monitor client sub process abort.""" config_monc_abort = copy.deepcopy(self.config) - signals = [ signal.SIGABRT, signal.SIGTERM, signal.SIGKILL ] + signals = [signal.SIGABRT, signal.SIGTERM, signal.SIGKILL] for sig in signals: with self.assertRaises(SystemExit) as cm: @@ -80,7 +81,7 @@ def test_monc_exit(self): time.sleep(2) # Send SIGABRT (abort signal) to the monitor client process - assert(gateway.monitor_client_process) + assert gateway.monitor_client_process gateway.monitor_client_process.send_signal(signal.SIGABRT) # Block on running keep alive ping @@ -106,15 +107,13 @@ def test_spdk_multi_gateway_exception(self): configB.config["spdk"]["tgt_cmd_extra_args"] = "-m 0x343435545" with self.assertRaises(SystemExit) as cm: - with ( - GatewayServer(configA) as gatewayA, - GatewayServer(configB) as gatewayB, - ): + with GatewayServer(configA) as gatewayA, GatewayServer(configB) as gatewayB: gatewayA.set_group_id(0) gatewayA.serve() gatewayB.set_group_id(1) gatewayB.serve() self.validate_exception(cm.exception) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_state.py b/tests/test_state.py index fda1366d..f46eae2c 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -107,7 +107,7 @@ def test_state_notify_update(config, ioctx, local_state, omap_state): """Confirms use of OMAP watch/notify for updates.""" update_counter = 0 - notify_event = threading.Event() # Event to signal when notify is called + notify_event = threading.Event() # Event to signal when notify is called def _state_notify_update(update, is_add_req): nonlocal update_counter @@ -181,8 +181,8 @@ def _state_notify_update(update, is_add_req): # to test notify capability elapsed = time.time() - start wait_interval = update_interval_sec - elapsed - 0.5 - assert(wait_interval > 0) - assert(wait_interval < update_interval_sec) + assert wait_interval > 0 + assert wait_interval < update_interval_sec time.sleep(wait_interval) # expect 4 updates: addition, two-step change and removal diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..a8f8341e --- /dev/null +++ b/tox.ini @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 100 +ignore = + E501