diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/scripts/spdkcli | |
parent | Initial commit. (diff) | |
download | ceph-upstream/18.2.2.tar.xz ceph-upstream/18.2.2.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-x | src/spdk/scripts/spdkcli.py | 84 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/__init__.py | 1 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_node.py | 861 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_node_iscsi.py | 639 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_node_nvmf.py | 363 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_root.py | 560 |
6 files changed, 2508 insertions, 0 deletions
diff --git a/src/spdk/scripts/spdkcli.py b/src/spdk/scripts/spdkcli.py new file mode 100755 index 000000000..3d7c63baa --- /dev/null +++ b/src/spdk/scripts/spdkcli.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +import sys +import argparse +from configshell_fb import ConfigShell, shell, ExecutionError +from pyparsing import (alphanums, Optional, Suppress, Word, Regex, + removeQuotes, dblQuotedString, OneOrMore) +from rpc.client import JSONRPCException, JSONRPCClient +from spdkcli import UIRoot + + +def add_quotes_to_shell(spdk_shell): + command = shell.locatedExpr(Word(alphanums + '_'))('command') + value = dblQuotedString.addParseAction(removeQuotes) + value_word = Word(alphanums + r';,=_\+/.<>()~@:-%[]') + keyword = Word(alphanums + r'_\-') + kparam = shell.locatedExpr(keyword + Suppress('=') + + Optional(value | value_word, default=''))('kparams*') + pparam = shell.locatedExpr(value | value_word)('pparams*') + parameters = OneOrMore(kparam | pparam) + bookmark = Regex(r'@([A-Za-z0-9:_.]|-)+') + pathstd = Regex(r'([A-Za-z0-9:_.\[\]]|-)*' + '/' + r'([A-Za-z0-9:_.\[\]/]|-)*') \ + | '..' | '.' + path = shell.locatedExpr(bookmark | pathstd | '*')('path') + spdk_shell._parser = Optional(path) + Optional(command) + Optional(parameters) + + +def main(): + """ + Start SPDK CLI + :return: + """ + spdk_shell = ConfigShell("~/.scripts") + spdk_shell.interactive = True + add_quotes_to_shell(spdk_shell) + + parser = argparse.ArgumentParser(description="SPDK command line interface") + parser.add_argument('-s', dest='server_addr', + help='RPC domain socket path or IP address', default='/var/tmp/spdk.sock') + parser.add_argument('-p', dest='port', + help='RPC port number (if server_addr is IP address)', + default=None, type=int) + parser.add_argument("-v", dest="verbose", help="Print request/response JSON for configuration calls", + default=False, action="store_true") + parser.add_argument("commands", metavar="command", type=str, nargs="*", default="", + help="commands to execute by SPDKCli as one-line command") + args = parser.parse_args() + + try: + client = JSONRPCClient(args.server_addr, port=args.port) + except JSONRPCException as e: + spdk_shell.log.error("%s. SPDK not running?" % e) + sys.exit(1) + + with client: + root_node = UIRoot(client, spdk_shell) + root_node.verbose = args.verbose + try: + root_node.refresh() + except BaseException: + pass + + if args.commands: + try: + spdk_shell.interactive = False + spdk_shell.run_cmdline(" ".join(args.commands)) + except Exception as e: + sys.stderr.write("%s\n" % e) + sys.exit(1) + sys.exit(0) + + spdk_shell.con.display("SPDK CLI v0.1") + spdk_shell.con.display("") + + while not spdk_shell._exit: + try: + spdk_shell.run_interactive() + except (JSONRPCException, ExecutionError) as e: + spdk_shell.log.error("%s" % e) + except BrokenPipeError as e: + spdk_shell.log.error("Lost connection with SPDK: %s" % e) + + +if __name__ == "__main__": + main() diff --git a/src/spdk/scripts/spdkcli/__init__.py b/src/spdk/scripts/spdkcli/__init__.py new file mode 100644 index 000000000..571d49a8f --- /dev/null +++ b/src/spdk/scripts/spdkcli/__init__.py @@ -0,0 +1 @@ +from .ui_root import UIRoot diff --git a/src/spdk/scripts/spdkcli/ui_node.py b/src/spdk/scripts/spdkcli/ui_node.py new file mode 100644 index 000000000..c681c0660 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_node.py @@ -0,0 +1,861 @@ +from configshell_fb import ConfigNode, ExecutionError +from uuid import UUID +from rpc.client import JSONRPCException +import json + + +def convert_bytes_to_human(size): + if not size: + return "" + for x in ["bytes", "K", "M", "G", "T"]: + if size < 1024.0: + return "%3.1f%s" % (size, x) + size /= 1024.0 + + +class UINode(ConfigNode): + def __init__(self, name, parent=None, shell=None): + ConfigNode.__init__(self, name, parent, shell) + + def refresh(self): + for child in self.children: + child.refresh() + + def refresh_node(self): + self.refresh() + + def ui_command_refresh(self): + self.refresh() + + def ui_command_ll(self, path=None, depth=None): + """ + Alias for ls. + """ + self.ui_command_ls(path, depth) + + def execute_command(self, command, pparams=[], kparams={}): + try: + result = ConfigNode.execute_command(self, command, + pparams, kparams) + except Exception as e: + raise e + else: + self.shell.log.debug("Command %s succeeded." % command) + return result + finally: + if self.shell.interactive and\ + command in ["create", "delete", "delete_all", "add_initiator", + "allow_any_host", "bdev_split_create", "add_lun", + "iscsi_target_node_add_pg_ig_maps", "remove_target", "add_secret", + "bdev_split_delete", "bdev_pmem_delete_pool", + "bdev_pmem_create_pool", "delete_secret_all", + "delete_initiator", "set_auth", "delete_secret", + "iscsi_target_node_remove_pg_ig_maps", "load_config", + "load_subsystem_config"]: + self.get_root().refresh() + self.refresh_node() + + +class UIBdevs(UINode): + def __init__(self, parent): + UINode.__init__(self, "bdevs", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + UIMallocBdev(self) + UIAIOBdev(self) + UILvolBdev(self) + UINvmeBdev(self) + UINullBdev(self) + UIErrorBdev(self) + UISplitBdev(self) + UIPmemBdev(self) + UIRbdBdev(self) + UIiSCSIBdev(self) + UIVirtioBlkBdev(self) + UIVirtioScsiBdev(self) + UIRaidBdev(self) + + +class UILvolStores(UINode): + def __init__(self, parent): + UINode.__init__(self, "lvol_stores", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for lvs in self.get_root().bdev_lvol_get_lvstores(): + UILvsObj(lvs, self) + + def delete(self, name, uuid): + if name is None and uuid is None: + self.shell.log.error("Please specify one of the identifiers: " + "lvol store name or UUID") + self.get_root().bdev_lvol_delete_lvstore(lvs_name=name, uuid=uuid) + + def ui_command_create(self, name, bdev_name, cluster_size=None): + """ + Creates logical volume store on target bdev. + + Arguments: + name - Friendly name to use alongside with UUID identifier. + bdev_name - On which bdev to create the lvol store. + cluster_size - Cluster size to use when creating lvol store, in bytes. Default: 4194304. + """ + + cluster_size = self.ui_eval_param(cluster_size, "number", None) + self.get_root().bdev_lvol_create_lvstore(lvs_name=name, bdev_name=bdev_name, cluster_sz=cluster_size) + + def ui_command_delete(self, name=None, uuid=None): + """ + Deletes logical volume store from configuration. + This will also delete all logical volume bdevs created on this lvol store! + + Arguments: + name - Friendly name of the logical volume store to be deleted. + uuid - UUID number of the logical volume store to be deleted. + """ + self.delete(name, uuid) + + def ui_command_delete_all(self): + rpc_messages = "" + for lvs in self._children: + try: + self.delete(None, lvs.lvs.uuid) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Lvol stores: %s" % len(self.children), None + + +class UIBdev(UINode): + def __init__(self, name, parent): + UINode.__init__(self, name, parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for bdev in self.get_root().bdev_get_bdevs(self.name): + UIBdevObj(bdev, self) + + def ui_command_get_bdev_iostat(self, name=None): + ret = self.get_root().bdev_get_iostat(name=name) + self.shell.log.info(json.dumps(ret, indent=2)) + + def ui_command_delete_all(self): + """Delete all bdevs from this tree node.""" + rpc_messages = "" + for bdev in self._children: + try: + self.delete(bdev.name) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Bdevs: %d" % len(self.children), None + + +class UIMallocBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "malloc", parent) + + def delete(self, name): + self.get_root().bdev_malloc_delete(name=name) + + def ui_command_create(self, size, block_size, name=None, uuid=None): + """ + Construct a Malloc bdev. + + Arguments: + size - Size in megabytes. + block_size - Integer, block size to use when constructing bdev. + name - Optional argument. Custom name to use for bdev. If not provided + then name will be "MallocX" where X is next available ID. + uuid - Optional parameter. Custom UUID to use. If empty then random + will be generated. + """ + + size = self.ui_eval_param(size, "number", None) + block_size = self.ui_eval_param(block_size, "number", None) + ret_name = self.get_root().create_malloc_bdev(num_blocks=size * 1024 * 1024 // block_size, + block_size=block_size, + name=name, uuid=uuid) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes malloc bdev from configuration. + + Arguments: + name - Is a unique identifier of the malloc bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UIAIOBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "aio", parent) + + def delete(self, name): + self.get_root().bdev_aio_delete(name=name) + + def ui_command_create(self, name, filename, block_size): + """ + Construct an AIO bdev. + Backend file must exist before trying to create an AIO bdev. + + Arguments: + name - Optional argument. Custom name to use for bdev. If not provided + then name will be "MallocX" where X is next available ID. + filename - Path to AIO backend. + block_size - Integer, block size to use when constructing bdev. + """ + + block_size = self.ui_eval_param(block_size, "number", None) + ret_name = self.get_root().bdev_aio_create(name=name, + block_size=int(block_size), + filename=filename) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes aio bdev from configuration. + + Arguments: + name - Is a unique identifier of the aio bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UILvolBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "logical_volume", parent) + + def delete(self, name): + self.get_root().bdev_lvol_delete(name=name) + + def ui_command_create(self, name, size, lvs, thin_provision=None): + """ + Construct a Logical Volume bdev. + + Arguments: + name - Friendly name to use for creating logical volume bdev. + size - Size in megabytes. + lvs - Identifier of logical volume store on which the bdev should be + created. Can be either a friendly name or UUID. + thin_provision - Whether the bdev should be thick or thin provisioned. + Default is False, and created bdevs are thick-provisioned. + """ + uuid = None + lvs_name = None + try: + UUID(lvs) + uuid = lvs + except ValueError: + lvs_name = lvs + + size = self.ui_eval_param(size, "number", None) + size *= (1024 * 1024) + thin_provision = self.ui_eval_param(thin_provision, "bool", False) + + ret_uuid = self.get_root().create_lvol_bdev(lvol_name=name, size=size, + lvs_name=lvs_name, uuid=uuid, + thin_provision=thin_provision) + self.shell.log.info(ret_uuid) + + def ui_command_delete(self, name): + """ + Deletes lvol bdev from configuration. + + Arguments: + name - Is a unique identifier of the lvol bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UINvmeBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "nvme", parent) + + def delete(self, name): + self.get_root().bdev_nvme_detach_controller(name=name) + + def ui_command_create(self, name, trtype, traddr, + adrfam=None, trsvcid=None, subnqn=None): + if "rdma" in trtype and None in [adrfam, trsvcid, subnqn]: + self.shell.log.error("Using RDMA transport type." + "Please provide arguments for adrfam, trsvcid and subnqn.") + ret_name = self.get_root().create_nvme_bdev(name=name, trtype=trtype, + traddr=traddr, adrfam=adrfam, + trsvcid=trsvcid, subnqn=subnqn) + self.shell.log.info(ret_name) + + def ui_command_delete_all(self): + rpc_messages = "" + ctrlrs = [x.name for x in self._children] + ctrlrs = [x.rsplit("n", 1)[0] for x in ctrlrs] + ctrlrs = set(ctrlrs) + for ctrlr in ctrlrs: + try: + self.delete(ctrlr) + except JSONRPCException as e: + rpc_messages += e.messages + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def ui_command_delete(self, name): + """ + Deletes NVMe controller from configuration. + + Arguments: + name - Is a unique identifier of the NVMe controller to be deleted. + """ + self.delete(name) + + +class UINullBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "null", parent) + + def delete(self, name): + self.get_root().bdev_null_delete(name=name) + + def ui_command_create(self, name, size, block_size, uuid=None): + """ + Construct a Null bdev. + + Arguments: + name - Name to use for bdev. + size - Size in megabytes. + block_size - Integer, block size to use when constructing bdev. + uuid - Optional parameter. Custom UUID to use. If empty then random + will be generated. + """ + + size = self.ui_eval_param(size, "number", None) + block_size = self.ui_eval_param(block_size, "number", None) + num_blocks = size * 1024 * 1024 // block_size + ret_name = self.get_root().bdev_null_create(num_blocks=num_blocks, + block_size=block_size, + name=name, uuid=uuid) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes null bdev from configuration. + + Arguments: + name - Is a unique identifier of the null bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UIErrorBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "error", parent) + + def delete(self, name): + self.get_root().bdev_error_delete(name=name) + + def ui_command_create(self, base_name): + """ + Construct a error injection bdev. + + Arguments: + base_name - base bdev name on top of which error bdev will be created. + """ + + self.get_root().create_error_bdev(base_name=base_name) + + def ui_command_delete(self, name): + """ + Deletes error bdev from configuration. + + Arguments: + name - Is a unique identifier of the error bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UISplitBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "split_disk", parent) + + def delete(self, name): + pass + + def ui_command_bdev_split_create(self, base_bdev, split_count, split_size_mb=None): + """ + Create split block devices from a base bdev. + + Arguments: + base_bdev - Name of bdev to split + split_count - Number of split bdevs to create + split_size_mb- Size of each split volume in MiB (optional) + """ + + split_count = self.ui_eval_param(split_count, "number", None) + split_size_mb = self.ui_eval_param(split_size_mb, "number", None) + + ret_name = self.get_root().bdev_split_create(base_bdev=base_bdev, + split_count=split_count, + split_size_mb=split_size_mb) + self.shell.log.info(ret_name) + + def ui_command_bdev_split_delete(self, base_bdev): + """Delete split block devices associated with base bdev. + + Args: + base_bdev: name of previously split bdev + """ + + self.get_root().bdev_split_delete(base_bdev=base_bdev) + + +class UIPmemBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "pmemblk", parent) + + def delete(self, name): + self.get_root().bdev_pmem_delete(name=name) + + def ui_command_bdev_pmem_create_pool(self, pmem_file, total_size, block_size): + total_size = self.ui_eval_param(total_size, "number", None) + block_size = self.ui_eval_param(block_size, "number", None) + num_blocks = int((total_size * 1024 * 1024) / block_size) + + self.get_root().bdev_pmem_create_pool(pmem_file=pmem_file, + num_blocks=num_blocks, + block_size=block_size) + + def ui_command_bdev_pmem_delete_pool(self, pmem_file): + self.get_root().bdev_pmem_delete_pool(pmem_file=pmem_file) + + def ui_command_bdev_pmem_get_pool_info(self, pmem_file): + ret = self.get_root().bdev_pmem_get_pool_info(pmem_file=pmem_file) + self.shell.log.info(json.dumps(ret, indent=2)) + + def ui_command_create(self, pmem_file, name): + ret_name = self.get_root().bdev_pmem_create(pmem_file=pmem_file, + name=name) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes pmem bdev from configuration. + + Arguments: + name - Is a unique identifier of the pmem bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UIRbdBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "rbd", parent) + + def delete(self, name): + self.get_root().bdev_rbd_delete(name=name) + + def ui_command_create(self, pool_name, rbd_name, block_size, name=None): + block_size = self.ui_eval_param(block_size, "number", None) + + ret_name = self.get_root().create_rbd_bdev(pool_name=pool_name, + rbd_name=rbd_name, + block_size=block_size, + name=name) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes rbd bdev from configuration. + + Arguments: + name - Is a unique identifier of the rbd bdev to be deleted - UUID number or name alias. + """ + self.delete(name) + + +class UIiSCSIBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "iscsi", parent) + + def delete(self, name): + self.get_root().bdev_iscsi_delete(name=name) + + def ui_command_create(self, name, url, initiator_iqn): + """ + Create iSCSI bdev in configuration by connecting to remote + iSCSI target. + + Arguments: + name - name to be used as an ID for created iSCSI bdev. + url - iscsi url pointing to LUN on remote iSCSI target. + Example: iscsi://127.0.0.1:3260/iqn.2018-06.org.spdk/0. + initiator_iqn - IQN to use for initiating connection with the target. + """ + ret_name = self.get_root().create_iscsi_bdev(name=name, + url=url, + initiator_iqn=initiator_iqn) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes iSCSI bdev from configuration. + + Arguments: + name - name of the iscsi bdev to be deleted. + """ + self.delete(name) + + +class UIVirtioBlkBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "virtioblk_disk", parent) + + def ui_command_create(self, name, trtype, traddr, + vq_count=None, vq_size=None): + + vq_count = self.ui_eval_param(vq_count, "number", None) + vq_size = self.ui_eval_param(vq_size, "number", None) + + ret = self.get_root().create_virtio_dev(name=name, + trtype=trtype, + traddr=traddr, + dev_type="blk", + vq_count=vq_count, + vq_size=vq_size) + + self.shell.log.info(ret) + + def ui_command_delete(self, name): + """ + Deletes virtio scsi bdev from configuration. + + Arguments: + name - Is a unique identifier of the virtio scsi bdev to be deleted - UUID number or name alias. + """ + self.get_root().bdev_virtio_detach_controller(name=name) + + +class UIVirtioScsiBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "virtioscsi_disk", parent) + + def refresh(self): + self._children = set([]) + for bdev in self.get_root().bdev_virtio_scsi_get_devices(): + UIVirtioScsiBdevObj(bdev, self) + + def ui_command_create(self, name, trtype, traddr, + vq_count=None, vq_size=None): + + vq_count = self.ui_eval_param(vq_count, "number", None) + vq_size = self.ui_eval_param(vq_size, "number", None) + + ret = self.get_root().create_virtio_dev(name=name, + trtype=trtype, + traddr=traddr, + dev_type="scsi", + vq_count=vq_count, + vq_size=vq_size) + + self.shell.log.info(ret) + + def ui_command_delete(self, name): + self.get_root().bdev_virtio_detach_controller(name=name) + + +class UIBdevObj(UINode): + def __init__(self, bdev, parent): + self.bdev = bdev + # Using bdev name also for lvol bdevs, which results in displying + # UUID instead of alias. This is because alias naming convention + # (lvol_store_name/lvol_bdev_name) conflicts with configshell paths + # ("/" as separator). + # Solution: show lvol alias in "summary field" for now. + # TODO: Possible next steps: + # - Either change default separator in tree for smth else + # - or add a UI command which would be able to autocomplete + # "cd" command based on objects alias and match is to the + # "main" bdev name. + UINode.__init__(self, self.bdev.name, parent) + + def ui_command_show_details(self): + self.shell.log.info(json.dumps(vars(self.bdev), indent=2)) + + def summary(self): + size = convert_bytes_to_human(self.bdev.block_size * self.bdev.num_blocks) + size = "=".join(["Size", size]) + + in_use = "Not claimed" + if bool(self.bdev.claimed): + in_use = "Claimed" + + alias = None + if self.bdev.aliases: + alias = self.bdev.aliases[0] + + info = ", ".join([_f for _f in [alias, size, in_use] if _f]) + return info, True + + +class UIVirtioScsiBdevObj(UIBdevObj): + def __init__(self, bdev, parent): + UIBdevObj.__init__(self, bdev, parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for bdev in self.get_root().bdev_get_bdevs("virtio_scsi_disk"): + if self.bdev.name in bdev.name: + UIBdevObj(bdev, self) + + def summary(self): + if "socket" in list(self.bdev.virtio.keys()): + info = self.bdev.virtio["socket"] + if "pci_address" in list(self.bdev.virtio.keys()): + info = self.bdev.virtio["pci_address"] + return info, True + + +class UILvsObj(UINode): + def __init__(self, lvs, parent): + UINode.__init__(self, lvs.name, parent) + self.lvs = lvs + + def ui_command_show_details(self): + self.shell.log.info(json.dumps(vars(self.lvs), indent=2)) + + def summary(self): + size = convert_bytes_to_human(self.lvs.total_data_clusters * self.lvs.cluster_size) + free = convert_bytes_to_human(self.lvs.free_clusters * self.lvs.cluster_size) + if not free: + free = "0" + size = "=".join(["Size", size]) + free = "=".join(["Free", free]) + info = ", ".join([str(size), str(free)]) + return info, True + + +class UIVhosts(UINode): + def __init__(self, parent): + UINode.__init__(self, "vhost", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + self.get_root().list_vhost_ctrls() + UIVhostBlk(self) + UIVhostScsi(self) + + +class UIVhost(UINode): + def __init__(self, name, parent): + UINode.__init__(self, name, parent) + self.refresh() + + def ui_command_delete(self, name): + """ + Delete a Vhost controller from configuration. + + Arguments: + name - Controller name. + """ + self.get_root().vhost_delete_controller(ctrlr=name) + + +class UIVhostBlk(UIVhost): + def __init__(self, parent): + UIVhost.__init__(self, "block", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for ctrlr in self.get_root().vhost_get_controllers(ctrlr_type=self.name): + UIVhostBlkCtrlObj(ctrlr, self) + + def ui_command_create(self, name, bdev, cpumask=None, readonly=False): + """ + Create a Vhost BLK controller. + + Arguments: + name - Controller name. + bdev - Which bdev to attach to the controller. + cpumask - Optional. Integer to specify mask of CPUs to use. + Default: 1. + readonly - Whether controller should be read only or not. + Default: False. + """ + self.get_root().vhost_create_blk_controller(ctrlr=name, + dev_name=bdev, + cpumask=cpumask, + readonly=bool(readonly)) + + +class UIVhostScsi(UIVhost): + def __init__(self, parent): + UIVhost.__init__(self, "scsi", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for ctrlr in self.get_root().vhost_get_controllers(ctrlr_type=self.name): + UIVhostScsiCtrlObj(ctrlr, self) + + def ui_command_create(self, name, cpumask=None): + """ + Create a Vhost SCSI controller. + + Arguments: + name - Controller name. + cpumask - Optional. Integer to specify mask of CPUs to use. + Default: 1. + """ + self.get_root().vhost_create_scsi_controller(ctrlr=name, + cpumask=cpumask) + + +class UIVhostCtrl(UINode): + # Base class for SCSI and BLK controllers, do not instantiate + def __init__(self, ctrlr, parent): + self.ctrlr = ctrlr + UINode.__init__(self, self.ctrlr.ctrlr, parent) + self.refresh() + + def ui_command_show_details(self): + self.shell.log.info(json.dumps(vars(self.ctrlr), indent=2)) + + def ui_command_set_coalescing(self, delay_base_us, iops_threshold): + delay_base_us = self.ui_eval_param(delay_base_us, "number", None) + iops_threshold = self.ui_eval_param(iops_threshold, "number", None) + + self.get_root().vhost_controller_set_coalescing(ctrlr=self.ctrlr.ctrlr, + delay_base_us=delay_base_us, + iops_threshold=iops_threshold) + + +class UIVhostScsiCtrlObj(UIVhostCtrl): + def refresh(self): + self._children = set([]) + for lun in self.ctrlr.backend_specific["scsi"]: + UIVhostTargetObj(lun, self) + + def ui_command_remove_target(self, target_num): + """ + Remove target node from SCSI controller. + + Arguments: + target_num - Integer identifier of target node to delete. + """ + self.get_root().vhost_scsi_controller_remove_target(ctrlr=self.ctrlr.ctrlr, + scsi_target_num=int(target_num)) + for ctrlr in self.get_root().vhost_get_controllers(ctrlr_type="scsi"): + if ctrlr.ctrlr == self.ctrlr.ctrlr: + self.ctrlr = ctrlr + + def ui_command_add_lun(self, target_num, bdev_name): + """ + Add LUN to SCSI target node. + Currently only one LUN (which is LUN ID 0) per target is supported. + Adding LUN to not existing target node will create that node. + + Arguments: + target_num - Integer identifier of target node to modify. + bdev - Which bdev to add as LUN. + """ + self.get_root().vhost_scsi_controller_add_target(ctrlr=self.ctrlr.ctrlr, + scsi_target_num=int(target_num), + bdev_name=bdev_name) + for ctrlr in self.get_root().vhost_get_controllers(ctrlr_type="scsi"): + if ctrlr.ctrlr == self.ctrlr.ctrlr: + self.ctrlr = ctrlr + + def summary(self): + info = self.ctrlr.socket + return info, True + + +class UIVhostBlkCtrlObj(UIVhostCtrl): + def refresh(self): + self._children = set([]) + UIVhostLunDevObj(self.ctrlr.backend_specific["block"]["bdev"], self) + + def summary(self): + ro = None + if self.ctrlr.backend_specific["block"]["readonly"]: + ro = "Readonly" + info = ", ".join([_f for _f in [self.ctrlr.socket, ro] if _f]) + return info, True + + +class UIVhostTargetObj(UINode): + def __init__(self, target, parent): + self.target = target + # Next line: configshell does not allow paths with spaces. + UINode.__init__(self, target["target_name"].replace(" ", "_"), parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for target in self.target["luns"]: + UIVhostLunDevObj(target["bdev_name"], self) + + def ui_command_show_details(self): + self.shell.log.info(json.dumps(self.target, indent=2)) + + def summary(self): + luns = "LUNs: %s" % len(self.target["luns"]) + id = "TargetID: %s" % self.target["scsi_dev_num"] + info = ",".join([luns, id]) + return info, True + + +class UIVhostLunDevObj(UINode): + def __init__(self, name, parent): + UINode.__init__(self, name, parent) + + +class UIRaidBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "raid_volume", parent) + + def delete(self, name): + self.get_root().bdev_raid_delete(name=name) + + def ui_command_create(self, name, raid_level, base_bdevs, strip_size_kb): + """ + Creates a raid bdev of the provided base_bdevs + + Arguments: + name - raid bdev name + raid_level - raid level, supported values 0 + base_bdevs - base bdevs name, whitespace separated list in quotes + strip_size_kb - strip size of raid bdev in KB, supported values like 8, 16, 32, 64, 128, 256, etc + """ + base_bdevs_array = [] + for u in base_bdevs.strip().split(" "): + base_bdevs_array.append(u) + + strip_size_kb = self.ui_eval_param(strip_size_kb, "number", None) + + ret_name = self.get_root().bdev_raid_create(name=name, + raid_level=raid_level, + base_bdevs=base_bdevs_array, + strip_size_kb=strip_size_kb) + self.shell.log.info(ret_name) + + def ui_command_delete(self, name): + """ + Deletes this raid bdev object + + Arguments: + name - raid bdev name + """ + self.delete(name) diff --git a/src/spdk/scripts/spdkcli/ui_node_iscsi.py b/src/spdk/scripts/spdkcli/ui_node_iscsi.py new file mode 100644 index 000000000..938cb7ab4 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_node_iscsi.py @@ -0,0 +1,639 @@ +from configshell_fb import ExecutionError +from rpc.client import JSONRPCException +from .ui_node import UINode + + +class UIISCSI(UINode): + def __init__(self, parent): + UINode.__init__(self, "iscsi", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + UIISCSIDevices(self) + UIPortalGroups(self) + UIInitiatorGroups(self) + UIISCSIConnections(self) + UIISCSIAuthGroups(self) + UIISCSIGlobalParams(self) + + +class UIISCSIGlobalParams(UINode): + def __init__(self, parent): + UINode.__init__(self, "global_params", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + iscsi_global_params = self.get_root().iscsi_get_options() + if not iscsi_global_params: + return + for param, val in iscsi_global_params.items(): + UIISCSIGlobalParam("%s: %s" % (param, val), self) + + def ui_command_set_auth(self, g=None, d=None, r=None, m=None): + """Set CHAP authentication for discovery service. + + Optional arguments: + g = chap_group: Authentication group ID for discovery session + d = disable_chap: CHAP for discovery session should be disabled + r = require_chap: CHAP for discovery session should be required + m = mutual_chap: CHAP for discovery session should be mutual + """ + chap_group = self.ui_eval_param(g, "number", None) + disable_chap = self.ui_eval_param(d, "bool", None) + require_chap = self.ui_eval_param(r, "bool", None) + mutual_chap = self.ui_eval_param(m, "bool", None) + self.get_root().iscsi_set_discovery_auth( + chap_group=chap_group, disable_chap=disable_chap, + require_chap=require_chap, mutual_chap=mutual_chap) + + +class UIISCSIGlobalParam(UINode): + def __init__(self, param, parent): + UINode.__init__(self, param, parent) + + +class UIISCSIDevices(UINode): + def __init__(self, parent): + UINode.__init__(self, "target_nodes", parent) + self.scsi_devices = list() + self.refresh() + + def refresh(self): + self._children = set([]) + self.target_nodes = list(self.get_root().iscsi_get_target_nodes()) + self.scsi_devices = list(self.get_root().scsi_get_devices()) + for device in self.scsi_devices: + for node in self.target_nodes: + if hasattr(device, "device_name") and node['name'] \ + == device.device_name: + UIISCSIDevice(device, node, self) + + def delete(self, name): + self.get_root().iscsi_delete_target_node(target_node_name=name) + + def ui_command_create(self, name, alias_name, bdev_name_id_pairs, + pg_ig_mappings, queue_depth, g=None, d=None, r=None, + m=None, h=None, t=None): + """Create target node + + Positional args: + name: Target node name (ASCII) + alias_name: Target node alias name (ASCII) + bdev_name_id_pairs: List of bdev_name_id_pairs + pg_ig_mappings: List of pg_ig_mappings + queue_depth: Desired target queue depth + Optional args: + g = chap_group: Authentication group ID for this target node + d = disable_chap: CHAP authentication should be disabled for this target node + r = require_chap: CHAP authentication should be required for this target node + m = mutual_chap: CHAP authentication should be mutual/bidirectional + h = header_digest: Header Digest should be required for this target node + t = data_digest: Data Digest should be required for this target node + """ + luns = [] + print("bdev_name_id_pairs: %s" % bdev_name_id_pairs) + print("pg_ig_mappings: %s" % pg_ig_mappings) + for u in bdev_name_id_pairs.strip().split(" "): + bdev_name, lun_id = u.split(":") + luns.append({"bdev_name": bdev_name, "lun_id": int(lun_id)}) + pg_ig_maps = [] + for u in pg_ig_mappings.strip().split(" "): + pg, ig = u.split(":") + pg_ig_maps.append({"pg_tag": int(pg), "ig_tag": int(ig)}) + queue_depth = self.ui_eval_param(queue_depth, "number", None) + chap_group = self.ui_eval_param(g, "number", None) + disable_chap = self.ui_eval_param(d, "bool", None) + require_chap = self.ui_eval_param(r, "bool", None) + mutual_chap = self.ui_eval_param(m, "bool", None) + header_digest = self.ui_eval_param(h, "bool", None) + data_digest = self.ui_eval_param(t, "bool", None) + self.get_root().iscsi_create_target_node( + name=name, alias_name=alias_name, luns=luns, + pg_ig_maps=pg_ig_maps, queue_depth=queue_depth, + chap_group=chap_group, disable_chap=disable_chap, + require_chap=require_chap, mutual_chap=mutual_chap, + header_digest=header_digest, data_digest=data_digest) + + def ui_command_delete(self, name=None): + """Delete a target node. If name is not specified delete all target nodes. + + Arguments: + name - Target node name. + """ + self.delete(name) + + def ui_command_delete_all(self): + """Delete all target nodes""" + rpc_messages = "" + for device in self.scsi_devices: + try: + self.delete(device.device_name) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def ui_command_add_lun(self, name, bdev_name, lun_id=None): + """Add lun to the target node. + + Required args: + name: Target node name (ASCII) + bdev_name: bdev name + Positional args: + lun_id: LUN ID (integer >= 0) + """ + if lun_id: + lun_id = self.ui_eval_param(lun_id, "number", None) + self.get_root().iscsi_target_node_add_lun( + name=name, bdev_name=bdev_name, lun_id=lun_id) + + def summary(self): + count = 0 + for device in self.scsi_devices: + for node in self.target_nodes: + if hasattr(device, "device_name") and node['name'] \ + == device.device_name: + count = count + 1 + return "Target nodes: %d" % count, None + + +class UIISCSIDevice(UINode): + def __init__(self, device, target, parent): + UINode.__init__(self, device.device_name, parent) + self.device = device + self.target = target + self.refresh() + + def ui_command_set_auth(self, g=None, d=None, r=None, m=None): + """Set CHAP authentication for the target node. + + Optionals args: + g = chap_group: Authentication group ID for this target node + d = disable_chap: CHAP authentication should be disabled for this target node + r = require_chap: CHAP authentication should be required for this target node + m = mutual_chap: CHAP authentication should be mutual/bidirectional + """ + chap_group = self.ui_eval_param(g, "number", None) + disable_chap = self.ui_eval_param(d, "bool", None) + require_chap = self.ui_eval_param(r, "bool", None) + mutual_chap = self.ui_eval_param(m, "bool", None) + self.get_root().iscsi_target_node_set_auth( + name=self.device.device_name, chap_group=chap_group, + disable_chap=disable_chap, + require_chap=require_chap, mutual_chap=mutual_chap) + + def ui_command_iscsi_target_node_add_pg_ig_maps(self, pg_ig_mappings): + """Add PG-IG maps to the target node. + + Args: + pg_ig_maps: List of pg_ig_mappings, e.g. pg_tag:ig_tag pg_tag2:ig_tag2 + """ + pg_ig_maps = [] + for u in pg_ig_mappings.strip().split(" "): + pg, ig = u.split(":") + pg_ig_maps.append({"pg_tag": int(pg), "ig_tag": int(ig)}) + self.get_root().iscsi_target_node_add_pg_ig_maps( + pg_ig_maps=pg_ig_maps, name=self.device.device_name) + + def ui_command_iscsi_target_node_remove_pg_ig_maps(self, pg_ig_mappings): + """Remove PG-IG maps from the target node. + + Args: + pg_ig_maps: List of pg_ig_mappings, e.g. pg_tag:ig_tag pg_tag2:ig_tag2 + """ + pg_ig_maps = [] + for u in pg_ig_mappings.strip().split(" "): + pg, ig = u.split(":") + pg_ig_maps.append({"pg_tag": int(pg), "ig_tag": int(ig)}) + self.get_root().iscsi_target_node_remove_pg_ig_maps( + pg_ig_maps=pg_ig_maps, name=self.device.device_name) + + def refresh(self): + self._children = set([]) + UIISCSILuns(self.target['luns'], self) + UIISCSIPgIgMaps(self.target['pg_ig_maps'], self) + auths = {"disable_chap": self.target["disable_chap"], + "require_chap": self.target["require_chap"], + "mutual_chap": self.target["mutual_chap"], + "chap_group": self.target["chap_group"], + "data_digest": self.target["data_digest"]} + UIISCSIAuth(auths, self) + + def summary(self): + return "Id: %s, QueueDepth: %s" % (self.device.id, + self.target['queue_depth']), None + + +class UIISCSIAuth(UINode): + def __init__(self, auths, parent): + UINode.__init__(self, "auths", parent) + self.auths = auths + self.refresh() + + def summary(self): + return "disable_chap: %s, require_chap: %s, mutual_chap: %s, chap_group: %s" % ( + self.auths['disable_chap'], self.auths['require_chap'], + self.auths['mutual_chap'], self.auths['chap_group']), None + + +class UIISCSILuns(UINode): + def __init__(self, luns, parent): + UINode.__init__(self, "luns", parent) + self.luns = luns + self.refresh() + + def refresh(self): + self._children = set([]) + for lun in self.luns: + UIISCSILun(lun, self) + + def summary(self): + return "Luns: %d" % len(self.luns), None + + +class UIISCSILun(UINode): + def __init__(self, lun, parent): + UINode.__init__(self, "lun %s" % lun['lun_id'], parent) + self.lun = lun + self.refresh() + + def summary(self): + return "%s" % self.lun['bdev_name'], None + + +class UIISCSIPgIgMaps(UINode): + def __init__(self, pg_ig_maps, parent): + UINode.__init__(self, "pg_ig_maps", parent) + self.pg_ig_maps = pg_ig_maps + self.refresh() + + def refresh(self): + self._children = set([]) + for pg_ig in self.pg_ig_maps: + UIISCSIPgIg(pg_ig, self) + + def summary(self): + return "Pg_ig_maps: %d" % len(self.pg_ig_maps), None + + +class UIISCSIPgIg(UINode): + def __init__(self, pg_ig, parent): + UINode.__init__(self, "portal_group%s - initiator_group%s" % + (pg_ig['pg_tag'], pg_ig['ig_tag']), parent) + self.pg_ig = pg_ig + self.refresh() + + +class UIPortalGroups(UINode): + def __init__(self, parent): + UINode.__init__(self, "portal_groups", parent) + self.refresh() + + def delete(self, tag): + self.get_root().iscsi_delete_portal_group(tag=tag) + + def ui_command_create(self, tag, portal_list): + """Add a portal group. + + Args: + portals: List of portals e.g. ip:port ip2:port2 + tag: Portal group tag (unique, integer > 0) + """ + portals = [] + for portal in portal_list.strip().split(" "): + host = portal + cpumask = None + if "@" in portal: + host, cpumask = portal.split("@") + if ":" not in host: + raise ExecutionError("Incorrect format of portal group. Port is missing." + "Use 'help create' to see the command syntax.") + host, port = host.rsplit(":", -1) + portals.append({'host': host, 'port': port}) + if cpumask: + print("WARNING: Specifying a CPU mask for portal groups is no longer supported. Ignoring.") + tag = self.ui_eval_param(tag, "number", None) + self.get_root().construct_portal_group(tag=tag, portals=portals) + + def ui_command_delete(self, tag): + """Delete a portal group with given tag (unique, integer > 0))""" + tag = self.ui_eval_param(tag, "number", None) + self.delete(tag) + + def ui_command_delete_all(self): + """Delete all portal groups""" + rpc_messages = "" + for pg in self.pgs: + try: + self.delete(pg.tag) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def refresh(self): + self._children = set([]) + self.pgs = list(self.get_root().iscsi_get_portal_groups()) + for pg in self.pgs: + try: + UIPortalGroup(pg, self) + except JSONRPCException as e: + self.shell.log.error(e.message) + + def summary(self): + return "Portal groups: %d" % len(self.pgs), None + + +class UIPortalGroup(UINode): + def __init__(self, pg, parent): + UINode.__init__(self, "portal_group%s" % pg.tag, parent) + self.pg = pg + self.refresh() + + def refresh(self): + self._children = set([]) + for portal in self.pg.portals: + UIPortal(portal['host'], portal['port'], self) + + def summary(self): + return "Portals: %d" % len(self.pg.portals), None + + +class UIPortal(UINode): + def __init__(self, host, port, parent): + UINode.__init__(self, "host=%s, port=%s" % ( + host, port), parent) + self.refresh() + + +class UIInitiatorGroups(UINode): + def __init__(self, parent): + UINode.__init__(self, "initiator_groups", parent) + self.refresh() + + def delete(self, tag): + self.get_root().iscsi_delete_initiator_group(tag=tag) + + def ui_command_create(self, tag, initiator_list, netmask_list): + """Add an initiator group. + + Args: + tag: Initiator group tag (unique, integer > 0) + initiators: List of initiator hostnames or IP addresses + separated with whitespaces, e.g. 127.0.0.1 192.168.200.100 + netmasks: List of initiator netmasks separated with whitespaces, + e.g. 255.255.0.0 255.248.0.0 + """ + tag = self.ui_eval_param(tag, "number", None) + self.get_root().construct_initiator_group( + tag=tag, initiators=initiator_list.split(" "), + netmasks=netmask_list.split(" ")) + + def ui_command_delete(self, tag): + """Delete an initiator group. + + Args: + tag: Initiator group tag (unique, integer > 0) + """ + tag = self.ui_eval_param(tag, "number", None) + self.delete(tag) + + def ui_command_delete_all(self): + """Delete all initiator groups""" + rpc_messages = "" + for ig in self.igs: + try: + self.delete(ig.tag) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def ui_command_add_initiator(self, tag, initiators, netmasks): + """Add initiators to an existing initiator group. + + Args: + tag: Initiator group tag (unique, integer > 0) + initiators: List of initiator hostnames or IP addresses, + e.g. 127.0.0.1 192.168.200.100 + netmasks: List of initiator netmasks, + e.g. 255.255.0.0 255.248.0.0 + """ + tag = self.ui_eval_param(tag, "number", None) + self.get_root().iscsi_initiator_group_add_initiators( + tag=tag, initiators=initiators.split(" "), + netmasks=netmasks.split(" ")) + + def ui_command_delete_initiator(self, tag, initiators=None, netmasks=None): + """Delete initiators from an existing initiator group. + + Args: + tag: Initiator group tag (unique, integer > 0) + initiators: List of initiator hostnames or IP addresses, e.g. 127.0.0.1 192.168.200.100 + netmasks: List of initiator netmasks, e.g. 255.255.0.0 255.248.0.0 + """ + tag = self.ui_eval_param(tag, "number", None) + if initiators: + initiators = initiators.split(" ") + if netmasks: + netmasks = netmasks.split(" ") + self.get_root().iscsi_initiator_group_remove_initiators( + tag=tag, initiators=initiators, + netmasks=netmasks) + + def refresh(self): + self._children = set([]) + self.igs = list(self.get_root().iscsi_get_initiator_groups()) + for ig in self.igs: + UIInitiatorGroup(ig, self) + + def summary(self): + return "Initiator groups: %d" % len(self.igs), None + + +class UIInitiatorGroup(UINode): + def __init__(self, ig, parent): + UINode.__init__(self, "initiator_group%s" % ig.tag, parent) + self.ig = ig + self.refresh() + + def refresh(self): + self._children = set([]) + for initiator, netmask in zip(self.ig.initiators, self.ig.netmasks): + UIInitiator(initiator, netmask, self) + + def summary(self): + return "Initiators: %d" % len(self.ig.initiators), None + + +class UIInitiator(UINode): + def __init__(self, initiator, netmask, parent): + UINode.__init__(self, "hostname=%s, netmask=%s" % (initiator, netmask), parent) + self.refresh() + + +class UIISCSIConnections(UINode): + def __init__(self, parent): + UINode.__init__(self, "iscsi_connections", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + self.iscsicons = list(self.get_root().iscsi_get_connections()) + for ic in self.iscsicons: + UIISCSIConnection(ic, self) + + def summary(self): + return "Connections: %d" % len(self.iscsicons), None + + +class UIISCSIConnection(UINode): + def __init__(self, ic, parent): + UINode.__init__(self, "%s" % ic['id'], parent) + self.ic = ic + self.refresh() + + def refresh(self): + self._children = set([]) + for key, val in self.ic.items(): + if key == "id": + continue + UIISCSIConnectionDetails("%s: %s" % (key, val), self) + + +class UIISCSIConnectionDetails(UINode): + def __init__(self, info, parent): + UINode.__init__(self, "%s" % info, parent) + self.refresh() + + +class UIISCSIAuthGroups(UINode): + def __init__(self, parent): + UINode.__init__(self, "auth_groups", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + self.iscsi_auth_groups = list(self.get_root().iscsi_get_auth_groups()) + if self.iscsi_auth_groups is None: + self.iscsi_auth_groups = [] + for ag in self.iscsi_auth_groups: + UIISCSIAuthGroup(ag, self) + + def delete(self, tag): + self.get_root().iscsi_delete_auth_group(tag=tag) + + def delete_secret(self, tag, user): + self.get_root().iscsi_auth_group_remove_secret( + tag=tag, user=user) + + def ui_command_create(self, tag, secrets=None): + """Add authentication group for CHAP authentication. + + Args: + tag: Authentication group tag (unique, integer > 0). + Optional args: + secrets: Array of secrets objects separated by comma sign, + e.g. user:test secret:test muser:mutual_test msecret:mutual_test + """ + tag = self.ui_eval_param(tag, "number", None) + if secrets: + secrets = [dict(u.split(":") for u in a.split(" ")) + for a in secrets.split(",")] + self.get_root().iscsi_create_auth_group(tag=tag, secrets=secrets) + + def ui_command_delete(self, tag): + """Delete an authentication group. + + Args: + tag: Authentication group tag (unique, integer > 0) + """ + tag = self.ui_eval_param(tag, "number", None) + self.delete(tag) + + def ui_command_delete_all(self): + """Delete all authentication groups.""" + rpc_messages = "" + for iscsi_auth_group in self.iscsi_auth_groups: + try: + self.delete(iscsi_auth_group['tag']) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def ui_command_add_secret(self, tag, user, secret, + muser=None, msecret=None): + """Add a secret to an authentication group. + + Args: + tag: Authentication group tag (unique, integer > 0) + user: User name for one-way CHAP authentication + secret: Secret for one-way CHAP authentication + Optional args: + muser: User name for mutual CHAP authentication + msecret: Secret for mutual CHAP authentication + """ + tag = self.ui_eval_param(tag, "number", None) + self.get_root().iscsi_auth_group_add_secret( + tag=tag, user=user, secret=secret, + muser=muser, msecret=msecret) + + def ui_command_delete_secret(self, tag, user): + """Delete a secret from an authentication group. + + Args: + tag: Authentication group tag (unique, integer > 0) + user: User name for one-way CHAP authentication + """ + tag = self.ui_eval_param(tag, "number", None) + self.delete_secret(tag, user) + + def ui_command_delete_secret_all(self, tag): + """Delete all secrets from an authentication group. + + Args: + tag: Authentication group tag (unique, integer > 0) + """ + rpc_messages = "" + tag = self.ui_eval_param(tag, "number", None) + for ag in self.iscsi_auth_groups: + if ag['tag'] == tag: + for secret in ag['secrets']: + try: + self.delete_secret(tag, secret['user']) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Groups: %s" % len(self.iscsi_auth_groups), None + + +class UIISCSIAuthGroup(UINode): + def __init__(self, ag, parent): + UINode.__init__(self, "group" + str(ag['tag']), parent) + self.ag = ag + self.refresh() + + def refresh(self): + self._children = set([]) + for secret in self.ag['secrets']: + UISCSIAuthSecret(secret, self) + + def summary(self): + return "Secrets: %s" % len(self.ag['secrets']), None + + +class UISCSIAuthSecret(UINode): + def __init__(self, secret, parent): + info_list = ["%s=%s" % (key, val) + for key, val in secret.items()] + info_list.sort(reverse=True) + info = ", ".join(info_list) + UINode.__init__(self, info, parent) + self.secret = secret + self.refresh() diff --git a/src/spdk/scripts/spdkcli/ui_node_nvmf.py b/src/spdk/scripts/spdkcli/ui_node_nvmf.py new file mode 100644 index 000000000..1b25298d1 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_node_nvmf.py @@ -0,0 +1,363 @@ +from rpc.client import JSONRPCException +from .ui_node import UINode + + +class UINVMf(UINode): + def __init__(self, parent): + UINode.__init__(self, "nvmf", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + UINVMfSubsystems(self) + UINVMfTransports(self) + + +class UINVMfTransports(UINode): + def __init__(self, parent): + UINode.__init__(self, "transport", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for transport in self.get_root().nvmf_get_transports(): + UINVMfTransport(transport, self) + + def ui_command_create(self, trtype, max_queue_depth=None, max_io_qpairs_per_ctrlr=None, + in_capsule_data_size=None, max_io_size=None, io_unit_size=None, max_aq_depth=None): + """Create a transport with given parameters + + Arguments: + trtype - Example: 'RDMA'. + max_queue_depth - Optional parameter. Integer, max value 65535. + max_io_qpairs_per_ctrlr - Optional parameter. 16 bit Integer, max value 65535. + in_capsule_data_size - Optional parameter. 32 bit Integer, max value 4294967295 + max_io_size - Optional parameter. 32 bit integer, max value 4294967295 + io_unit_size - Optional parameter. 32 bit integer, max value 4294967295 + max_aq_depth - Optional parameter. 32 bit integer, max value 4294967295 + """ + max_queue_depth = self.ui_eval_param(max_queue_depth, "number", None) + max_io_qpairs_per_ctrlr = self.ui_eval_param(max_io_qpairs_per_ctrlr, "number", None) + in_capsule_data_size = self.ui_eval_param(in_capsule_data_size, "number", None) + max_io_size = self.ui_eval_param(max_io_size, "number", None) + io_unit_size = self.ui_eval_param(io_unit_size, "number", None) + max_aq_depth = self.ui_eval_param(max_aq_depth, "number", None) + + self.get_root().create_nvmf_transport(trtype=trtype, + max_queue_depth=max_queue_depth, + max_io_qpairs_per_ctrlr=max_io_qpairs_per_ctrlr, + in_capsule_data_size=in_capsule_data_size, + max_io_size=max_io_size, + io_unit_size=io_unit_size, + max_aq_depth=max_aq_depth) + + def summary(self): + return "Transports: %s" % len(self.children), None + + +class UINVMfTransport(UINode): + def __init__(self, transport, parent): + UINode.__init__(self, transport.trtype, parent) + self.transport = transport + + +class UINVMfSubsystems(UINode): + def __init__(self, parent): + UINode.__init__(self, "subsystem", parent) + self.refresh() + + def refresh(self): + self._children = set([]) + for subsystem in self.get_root().nvmf_get_subsystems(): + UINVMfSubsystem(subsystem, self) + + def delete(self, subsystem_nqn): + self.get_root().nvmf_delete_subsystem(nqn=subsystem_nqn) + + def ui_command_create(self, nqn, serial_number=None, + max_namespaces=None, allow_any_host="false"): + """Create subsystem with given parameteres. + + Arguments: + nqn - Target nqn(ASCII). + serial_number - Example: 'SPDK00000000000001'. + max_namespaces - Optional parameter. Maximum number of namespaces allowed to added during + active connection + allow_any_host - Optional parameter. Allow any host to connect (don't enforce host NQN + whitelist) + """ + allow_any_host = self.ui_eval_param(allow_any_host, "bool", False) + max_namespaces = self.ui_eval_param(max_namespaces, "number", 0) + self.get_root().create_nvmf_subsystem(nqn=nqn, serial_number=serial_number, + allow_any_host=allow_any_host, + max_namespaces=max_namespaces) + + def ui_command_delete(self, subsystem_nqn): + """Delete subsystem with given nqn. + + Arguments: + nqn_subsystem - Name of susbsytem to delete + """ + self.delete(subsystem_nqn) + + def ui_command_delete_all(self): + """Delete all subsystems""" + rpc_messages = "" + for child in self._children: + try: + self.delete(child.subsystem.nqn) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Subsystems: %s" % len(self.children), None + + +class UINVMfSubsystem(UINode): + def __init__(self, subsystem, parent): + UINode.__init__(self, subsystem.nqn, parent) + self.subsystem = subsystem + self.refresh() + + def refresh(self): + self._children = set([]) + UINVMfSubsystemListeners(self.subsystem.listen_addresses, self) + UINVMfSubsystemHosts(self.subsystem.hosts, self) + if hasattr(self.subsystem, 'namespaces'): + UINVMfSubsystemNamespaces(self.subsystem.namespaces, self) + + def refresh_node(self): + for subsystem in self.get_root().nvmf_get_subsystems(): + if subsystem.nqn == self.subsystem.nqn: + self.subsystem = subsystem + self.refresh() + + def ui_command_show_details(self): + self.shell.log.info(json.dumps(vars(self.lvs), indent=2)) + + def ui_command_allow_any_host(self, disable="false"): + """Disable or or enable allow_any_host flag. + + Arguments: + disable - Optional parameter. If false then enable, if true disable + """ + disable = self.ui_eval_param(disable, "bool", None) + self.get_root().nvmf_subsystem_allow_any_host( + nqn=self.subsystem.nqn, disable=disable) + + def summary(self): + sn = None + if hasattr(self.subsystem, 'serial_number'): + sn = "sn=%s" % self.subsystem.serial_number + st = None + if hasattr(self.subsystem, 'subtype'): + st = "st=%s" % self.subsystem.subtype + allow_any_host = None + if self.subsystem.allow_any_host: + allow_any_host = "Allow any host" + info = ", ".join(filter(None, [sn, st, allow_any_host])) + return info, None + + +class UINVMfSubsystemListeners(UINode): + def __init__(self, listen_addresses, parent): + UINode.__init__(self, "listen_addresses", parent) + self.listen_addresses = listen_addresses + self.refresh() + + def refresh(self): + self._children = set([]) + for address in self.listen_addresses: + UINVMfSubsystemListener(address, self) + + def refresh_node(self): + for subsystem in self.get_root().nvmf_get_subsystems(): + if subsystem.nqn == self.parent.subsystem.nqn: + self.listen_addresses = subsystem.listen_addresses + self.refresh() + + def delete(self, trtype, traddr, trsvcid, adrfam=None): + self.get_root().nvmf_subsystem_remove_listener( + nqn=self.parent.subsystem.nqn, trtype=trtype, + traddr=traddr, trsvcid=trsvcid, adrfam=adrfam) + + def ui_command_create(self, trtype, traddr, trsvcid, adrfam): + """Create address listener for subsystem. + + Arguments: + trtype - NVMe-oF transport type: e.g., rdma. + traddr - NVMe-oF transport address: e.g., an ip address. + trsvcid - NVMe-oF transport service id: e.g., a port number. + adrfam - NVMe-oF transport adrfam: e.g., ipv4, ipv6, ib, fc. + """ + self.get_root().nvmf_subsystem_add_listener( + nqn=self.parent.subsystem.nqn, trtype=trtype, traddr=traddr, + trsvcid=trsvcid, adrfam=adrfam) + + def ui_command_delete(self, trtype, traddr, trsvcid, adrfam=None): + """Remove address listener for subsystem. + + Arguments: + trtype - Transport type (RDMA) + traddr - NVMe-oF transport address: e.g., an ip address. + trsvcid - NVMe-oF transport service id: e.g., a port number. + adrfam - Optional argument. Address family ("IPv4", "IPv6", "IB" or "FC"). + """ + self.delete(trtype, traddr, trsvcid, adrfam) + + def ui_command_delete_all(self): + """Remove all address listeners from subsystem.""" + rpc_messages = "" + for la in self.listen_addresses: + try: + self.delete(la['trtype'], la['traddr'], la['trsvcid'], la['adrfam']) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Addresses: %s" % len(self.listen_addresses), None + + +class UINVMfSubsystemListener(UINode): + def __init__(self, address, parent): + UINode.__init__(self, "%s:%s" % (address['traddr'], address['trsvcid']), + parent) + self.address = address + + def summary(self): + return "%s" % self.address['trtype'], True + + +class UINVMfSubsystemHosts(UINode): + def __init__(self, hosts, parent): + UINode.__init__(self, "hosts", parent) + self.hosts = hosts + self.refresh() + + def refresh(self): + self._children = set([]) + for host in self.hosts: + UINVMfSubsystemHost(host, self) + + def refresh_node(self): + for subsystem in self.get_root().nvmf_get_subsystems(): + if subsystem.nqn == self.parent.subsystem.nqn: + self.hosts = subsystem.hosts + self.refresh() + + def delete(self, host): + self.get_root().nvmf_subsystem_remove_host( + nqn=self.parent.subsystem.nqn, host=host) + + def ui_command_create(self, host): + """Add a host NQN to the whitelist of allowed hosts. + + Args: + host: Host NQN to add to the list of allowed host NQNs + """ + self.get_root().nvmf_subsystem_add_host( + nqn=self.parent.subsystem.nqn, host=host) + + def ui_command_delete(self, host): + """Delete host from subsystem. + + Arguments: + host - NQN of host to remove. + """ + self.delete(host) + + def ui_command_delete_all(self): + """Delete host from subsystem""" + rpc_messages = "" + for host in self.hosts: + try: + self.delete(host['nqn']) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Hosts: %s" % len(self.hosts), None + + +class UINVMfSubsystemHost(UINode): + def __init__(self, host, parent): + UINode.__init__(self, "%s" % host['nqn'], parent) + self.host = host + + +class UINVMfSubsystemNamespaces(UINode): + def __init__(self, namespaces, parent): + UINode.__init__(self, "namespaces", parent) + self.namespaces = namespaces + self.refresh() + + def refresh(self): + self._children = set([]) + for namespace in self.namespaces: + UINVMfSubsystemNamespace(namespace, self) + + def refresh_node(self): + for subsystem in self.get_root().nvmf_get_subsystems(): + if subsystem.nqn == self.parent.subsystem.nqn: + self.namespaces = subsystem.namespaces + self.refresh() + + def delete(self, nsid): + self.get_root().nvmf_subsystem_remove_ns( + nqn=self.parent.subsystem.nqn, nsid=nsid) + + def ui_command_create(self, bdev_name, nsid=None, + nguid=None, eui64=None, uuid=None): + """Add a namespace to a subsystem. + + Args: + bdev_name: Name of bdev to expose as a namespace. + Optional args: + nsid: Namespace ID. + nguid: 16-byte namespace globally unique identifier in hexadecimal. + eui64: 8-byte namespace EUI-64 in hexadecimal (e.g. "ABCDEF0123456789"). + uuid: Namespace UUID. + """ + nsid = self.ui_eval_param(nsid, "number", None) + self.get_root().nvmf_subsystem_add_ns( + nqn=self.parent.subsystem.nqn, bdev_name=bdev_name, + nsid=nsid, nguid=nguid, eui64=eui64, uuid=uuid) + + def ui_command_delete(self, nsid): + """Delete namespace from subsystem. + + Arguments: + nsid - Id of namespace to remove. + """ + nsid = self.ui_eval_param(nsid, "number", None) + self.delete(nsid) + + def ui_command_delete_all(self): + """Delete all namespaces from subsystem.""" + rpc_messages = "" + for namespace in self.namespaces: + try: + self.delete(namespace['nsid']) + except JSONRPCException as e: + rpc_messages += e.message + if rpc_messages: + raise JSONRPCException(rpc_messages) + + def summary(self): + return "Namespaces: %s" % len(self.namespaces), None + + +class UINVMfSubsystemNamespace(UINode): + def __init__(self, namespace, parent): + UINode.__init__(self, namespace['bdev_name'], parent) + self.namespace = namespace + + def summary(self): + info = ", ".join([self.namespace['name'], str(self.namespace['nsid'])]) + return info, None diff --git a/src/spdk/scripts/spdkcli/ui_root.py b/src/spdk/scripts/spdkcli/ui_root.py new file mode 100644 index 000000000..cec8eb5f9 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_root.py @@ -0,0 +1,560 @@ +from .ui_node import UINode, UIBdevs, UILvolStores, UIVhosts +from .ui_node_nvmf import UINVMf +from .ui_node_iscsi import UIISCSI +import rpc.client +import rpc +from functools import wraps + + +class UIRoot(UINode): + """ + Root node for CLI menu tree structure. Refreshes running config on startup. + """ + def __init__(self, client, shell): + UINode.__init__(self, "/", shell=shell) + self.current_bdevs = [] + self.current_lvol_stores = [] + self.current_vhost_ctrls = [] + self.current_nvmf_transports = [] + self.current_nvmf_subsystems = [] + self.set_rpc_target(client) + self.verbose = False + self.is_init = self.check_init() + self.methods = [] + + def refresh(self): + self.methods = self.rpc_get_methods(current=True) + if self.is_init is False: + methods = "\n".join(self.methods) + self.shell.log.warning("SPDK Application is not yet initialized.\n" + "Please initialize subsystems with framework_start_init command.\n" + "List of available commands in current state:\n" + "%s" % methods) + else: + # Pass because we'd like to build main tree structure for "ls" + # even if state is uninitialized + pass + + self._children = set([]) + UIBdevs(self) + UILvolStores(self) + if self.has_subsystem("vhost"): + UIVhosts(self) + if self.has_subsystem("nvmf"): + UINVMf(self) + if self.has_subsystem("iscsi"): + UIISCSI(self) + + def set_rpc_target(self, client): + self.client = client + + def print_array(self, a): + return " ".join(a) + + def verbose(f): + # For any configuration calls (create, delete, construct, etc.) + # Check if verbose option is to be used and set appropriately. + # Do not use for "get_*" methods so that output is not + # flooded. + def w(self, **kwargs): + self.client.log_set_level("INFO" if self.verbose else "ERROR") + r = f(self, **kwargs) + self.client.log_set_level("ERROR") + return r + return w + + def is_method_available(f): + # Check if method f is available for given spdk target + def w(self, **kwargs): + if f.__name__ in self.methods: + r = f(self, **kwargs) + return r + # If given method is not avaialble return empty list + # similar to real get_* like rpc + return [] + return w + + def ui_command_framework_start_init(self): + if rpc.framework_start_init(self.client): + self.is_init = True + self.refresh() + + def ui_command_load_config(self, filename): + with open(filename, "r") as fd: + rpc.load_config(self.client, fd) + + def ui_command_load_subsystem_config(self, filename): + with open(filename, "r") as fd: + rpc.load_subsystem_config(self.client, fd) + + def ui_command_save_config(self, filename, indent=2): + with open(filename, "w") as fd: + rpc.save_config(self.client, fd, indent) + + def ui_command_save_subsystem_config(self, filename, subsystem, indent=2): + with open(filename, "w") as fd: + rpc.save_subsystem_config(self.client, fd, indent, subsystem) + + def rpc_get_methods(self, current=False): + return rpc.rpc_get_methods(self.client, current=current) + + def check_init(self): + return "framework_start_init" not in self.rpc_get_methods(current=True) + + def bdev_get_bdevs(self, bdev_type): + if self.is_init: + self.current_bdevs = rpc.bdev.bdev_get_bdevs(self.client) + # Following replace needs to be done in order for some of the bdev + # listings to work: logical volumes, split disk. + # For example logical volumes: listing in menu is "Logical_Volume" + # (cannot have space), but the product name in SPDK is "Logical Volume" + bdev_type = bdev_type.replace("_", " ") + for bdev in [x for x in self.current_bdevs if bdev_type in x["product_name"].lower()]: + test = Bdev(bdev) + yield test + + def bdev_get_iostat(self, **kwargs): + return rpc.bdev.bdev_get_iostat(self.client, **kwargs) + + @verbose + def bdev_split_create(self, **kwargs): + response = rpc.bdev.bdev_split_create(self.client, **kwargs) + return self.print_array(response) + + @verbose + def bdev_split_delete(self, **kwargs): + rpc.bdev.bdev_split_delete(self.client, **kwargs) + + @verbose + def create_malloc_bdev(self, **kwargs): + response = rpc.bdev.bdev_malloc_create(self.client, **kwargs) + return response + + @verbose + def bdev_malloc_delete(self, **kwargs): + rpc.bdev.bdev_malloc_delete(self.client, **kwargs) + + @verbose + def create_iscsi_bdev(self, **kwargs): + response = rpc.bdev.bdev_iscsi_create(self.client, **kwargs) + return response + + @verbose + def bdev_iscsi_delete(self, **kwargs): + rpc.bdev.bdev_iscsi_delete(self.client, **kwargs) + + @verbose + def bdev_aio_create(self, **kwargs): + response = rpc.bdev.bdev_aio_create(self.client, **kwargs) + return response + + @verbose + def bdev_aio_delete(self, **kwargs): + rpc.bdev.bdev_aio_delete(self.client, **kwargs) + + @verbose + def create_lvol_bdev(self, **kwargs): + response = rpc.lvol.bdev_lvol_create(self.client, **kwargs) + return response + + @verbose + def bdev_lvol_delete(self, **kwargs): + response = rpc.lvol.bdev_lvol_delete(self.client, **kwargs) + return response + + @verbose + def create_nvme_bdev(self, **kwargs): + response = rpc.bdev.bdev_nvme_attach_controller(self.client, **kwargs) + return response + + @verbose + def bdev_nvme_detach_controller(self, **kwargs): + rpc.bdev.bdev_nvme_detach_controller(self.client, **kwargs) + + @verbose + def bdev_null_create(self, **kwargs): + response = rpc.bdev.bdev_null_create(self.client, **kwargs) + return response + + @verbose + def bdev_null_delete(self, **kwargs): + rpc.bdev.bdev_null_delete(self.client, **kwargs) + + @verbose + def create_error_bdev(self, **kwargs): + response = rpc.bdev.bdev_error_create(self.client, **kwargs) + + @verbose + def bdev_error_delete(self, **kwargs): + rpc.bdev.bdev_error_delete(self.client, **kwargs) + + @verbose + @is_method_available + def bdev_lvol_get_lvstores(self): + if self.is_init: + self.current_lvol_stores = rpc.lvol.bdev_lvol_get_lvstores(self.client) + for lvs in self.current_lvol_stores: + yield LvolStore(lvs) + + @verbose + def bdev_lvol_create_lvstore(self, **kwargs): + response = rpc.lvol.bdev_lvol_create_lvstore(self.client, **kwargs) + return response + + @verbose + def bdev_lvol_delete_lvstore(self, **kwargs): + rpc.lvol.bdev_lvol_delete_lvstore(self.client, **kwargs) + + @verbose + def bdev_pmem_create_pool(self, **kwargs): + response = rpc.pmem.bdev_pmem_create_pool(self.client, **kwargs) + return response + + @verbose + def bdev_pmem_delete_pool(self, **kwargs): + rpc.pmem.bdev_pmem_delete_pool(self.client, **kwargs) + + @verbose + def bdev_pmem_get_pool_info(self, **kwargs): + response = rpc.pmem.bdev_pmem_get_pool_info(self.client, **kwargs) + return response + + @verbose + def bdev_pmem_create(self, **kwargs): + response = rpc.bdev.bdev_pmem_create(self.client, **kwargs) + return response + + @verbose + def bdev_pmem_delete(self, **kwargs): + response = rpc.bdev.bdev_pmem_delete(self.client, **kwargs) + return response + + @verbose + def create_rbd_bdev(self, **kwargs): + response = rpc.bdev.bdev_rbd_create(self.client, **kwargs) + return response + + @verbose + def bdev_rbd_delete(self, **kwargs): + response = rpc.bdev.bdev_rbd_delete(self.client, **kwargs) + return response + + @verbose + def create_virtio_dev(self, **kwargs): + response = rpc.vhost.bdev_virtio_attach_controller(self.client, **kwargs) + return self.print_array(response) + + @verbose + def bdev_virtio_detach_controller(self, **kwargs): + response = rpc.vhost.bdev_virtio_detach_controller(self.client, **kwargs) + return response + + @verbose + def bdev_raid_create(self, **kwargs): + rpc.bdev.bdev_raid_create(self.client, **kwargs) + + @verbose + def bdev_raid_delete(self, **kwargs): + rpc.bdev.bdev_raid_delete(self.client, **kwargs) + + @verbose + @is_method_available + def bdev_virtio_scsi_get_devices(self): + if self.is_init: + for bdev in rpc.vhost.bdev_virtio_scsi_get_devices(self.client): + test = Bdev(bdev) + yield test + + def list_vhost_ctrls(self): + if self.is_init: + self.current_vhost_ctrls = rpc.vhost.vhost_get_controllers(self.client) + + @verbose + @is_method_available + def vhost_get_controllers(self, ctrlr_type): + if self.is_init: + self.list_vhost_ctrls() + for ctrlr in [x for x in self.current_vhost_ctrls if ctrlr_type in list(x["backend_specific"].keys())]: + yield VhostCtrlr(ctrlr) + + @verbose + def vhost_delete_controller(self, **kwargs): + rpc.vhost.vhost_delete_controller(self.client, **kwargs) + + @verbose + def vhost_create_scsi_controller(self, **kwargs): + rpc.vhost.vhost_create_scsi_controller(self.client, **kwargs) + + @verbose + def vhost_create_blk_controller(self, **kwargs): + rpc.vhost.vhost_create_blk_controller(self.client, **kwargs) + + @verbose + def vhost_scsi_controller_remove_target(self, **kwargs): + rpc.vhost.vhost_scsi_controller_remove_target(self.client, **kwargs) + + @verbose + def vhost_scsi_controller_add_target(self, **kwargs): + rpc.vhost.vhost_scsi_controller_add_target(self.client, **kwargs) + + def vhost_controller_set_coalescing(self, **kwargs): + rpc.vhost.vhost_controller_set_coalescing(self.client, **kwargs) + + @verbose + def create_nvmf_transport(self, **kwargs): + rpc.nvmf.nvmf_create_transport(self.client, **kwargs) + + def list_nvmf_transports(self): + if self.is_init: + self.current_nvmf_transports = rpc.nvmf.nvmf_get_transports(self.client) + + @verbose + @is_method_available + def nvmf_get_transports(self): + if self.is_init: + self.list_nvmf_transports() + for transport in self.current_nvmf_transports: + yield NvmfTransport(transport) + + def list_nvmf_subsystems(self): + if self.is_init: + self.current_nvmf_subsystems = rpc.nvmf.nvmf_get_subsystems(self.client) + + @verbose + @is_method_available + def nvmf_get_subsystems(self): + if self.is_init: + self.list_nvmf_subsystems() + for subsystem in self.current_nvmf_subsystems: + yield NvmfSubsystem(subsystem) + + @verbose + def create_nvmf_subsystem(self, **kwargs): + rpc.nvmf.nvmf_create_subsystem(self.client, **kwargs) + + @verbose + def nvmf_delete_subsystem(self, **kwargs): + rpc.nvmf.nvmf_delete_subsystem(self.client, **kwargs) + + @verbose + def nvmf_subsystem_add_listener(self, **kwargs): + rpc.nvmf.nvmf_subsystem_add_listener(self.client, **kwargs) + + @verbose + def nvmf_subsystem_remove_listener(self, **kwargs): + rpc.nvmf.nvmf_subsystem_remove_listener(self.client, **kwargs) + + @verbose + def nvmf_subsystem_add_host(self, **kwargs): + rpc.nvmf.nvmf_subsystem_add_host(self.client, **kwargs) + + @verbose + def nvmf_subsystem_remove_host(self, **kwargs): + rpc.nvmf.nvmf_subsystem_remove_host(self.client, **kwargs) + + @verbose + def nvmf_subsystem_allow_any_host(self, **kwargs): + rpc.nvmf.nvmf_subsystem_allow_any_host(self.client, **kwargs) + + @verbose + def nvmf_subsystem_add_ns(self, **kwargs): + rpc.nvmf.nvmf_subsystem_add_ns(self.client, **kwargs) + + @verbose + def nvmf_subsystem_remove_ns(self, **kwargs): + rpc.nvmf.nvmf_subsystem_remove_ns(self.client, **kwargs) + + @verbose + def nvmf_subsystem_allow_any_host(self, **kwargs): + rpc.nvmf.nvmf_subsystem_allow_any_host(self.client, **kwargs) + + @verbose + @is_method_available + def scsi_get_devices(self): + if self.is_init: + for device in rpc.iscsi.scsi_get_devices(self.client): + yield ScsiObj(device) + + @verbose + @is_method_available + def iscsi_get_target_nodes(self): + if self.is_init: + for tg in rpc.iscsi.iscsi_get_target_nodes(self.client): + yield tg + + @verbose + def iscsi_create_target_node(self, **kwargs): + rpc.iscsi.iscsi_create_target_node(self.client, **kwargs) + + @verbose + def iscsi_delete_target_node(self, **kwargs): + rpc.iscsi.iscsi_delete_target_node(self.client, **kwargs) + + @verbose + @is_method_available + def iscsi_get_portal_groups(self): + if self.is_init: + for pg in rpc.iscsi.iscsi_get_portal_groups(self.client): + yield ScsiObj(pg) + + @verbose + @is_method_available + def iscsi_get_initiator_groups(self): + if self.is_init: + for ig in rpc.iscsi.iscsi_get_initiator_groups(self.client): + yield ScsiObj(ig) + + @verbose + def construct_portal_group(self, **kwargs): + rpc.iscsi.iscsi_create_portal_group(self.client, **kwargs) + + @verbose + def iscsi_delete_portal_group(self, **kwargs): + rpc.iscsi.iscsi_delete_portal_group(self.client, **kwargs) + + @verbose + def construct_initiator_group(self, **kwargs): + rpc.iscsi.iscsi_create_initiator_group(self.client, **kwargs) + + @verbose + def iscsi_delete_initiator_group(self, **kwargs): + rpc.iscsi.iscsi_delete_initiator_group(self.client, **kwargs) + + @verbose + @is_method_available + def iscsi_get_connections(self, **kwargs): + if self.is_init: + for ic in rpc.iscsi.iscsi_get_connections(self.client, **kwargs): + yield ic + + @verbose + def iscsi_initiator_group_add_initiators(self, **kwargs): + rpc.iscsi.iscsi_initiator_group_add_initiators(self.client, **kwargs) + + @verbose + def iscsi_initiator_group_remove_initiators(self, **kwargs): + rpc.iscsi.iscsi_initiator_group_remove_initiators(self.client, **kwargs) + + @verbose + def iscsi_target_node_add_pg_ig_maps(self, **kwargs): + rpc.iscsi.iscsi_target_node_add_pg_ig_maps(self.client, **kwargs) + + @verbose + def iscsi_target_node_remove_pg_ig_maps(self, **kwargs): + rpc.iscsi.iscsi_target_node_remove_pg_ig_maps(self.client, **kwargs) + + @verbose + def iscsi_auth_group_add_secret(self, **kwargs): + rpc.iscsi.iscsi_auth_group_add_secret(self.client, **kwargs) + + @verbose + def iscsi_auth_group_remove_secret(self, **kwargs): + rpc.iscsi.iscsi_auth_group_remove_secret(self.client, **kwargs) + + @verbose + @is_method_available + def iscsi_get_auth_groups(self, **kwargs): + return rpc.iscsi.iscsi_get_auth_groups(self.client, **kwargs) + + @verbose + def iscsi_create_auth_group(self, **kwargs): + rpc.iscsi.iscsi_create_auth_group(self.client, **kwargs) + + @verbose + def iscsi_delete_auth_group(self, **kwargs): + rpc.iscsi.iscsi_delete_auth_group(self.client, **kwargs) + + @verbose + def iscsi_target_node_set_auth(self, **kwargs): + rpc.iscsi.iscsi_target_node_set_auth(self.client, **kwargs) + + @verbose + def iscsi_target_node_add_lun(self, **kwargs): + rpc.iscsi.iscsi_target_node_add_lun(self.client, **kwargs) + + @verbose + def iscsi_set_discovery_auth(self, **kwargs): + rpc.iscsi.iscsi_set_discovery_auth(self.client, **kwargs) + + @verbose + @is_method_available + def iscsi_get_options(self, **kwargs): + return rpc.iscsi.iscsi_get_options(self.client, **kwargs) + + def has_subsystem(self, subsystem): + for system in rpc.subsystem.framework_get_subsystems(self.client): + if subsystem.lower() == system["subsystem"].lower(): + return True + return False + + +class Bdev(object): + def __init__(self, bdev_info): + """ + All class attributes are set based on what information is received + from bdev_get_bdevs RPC call. + # TODO: Document in docstring parameters which describe bdevs. + # TODO: Possible improvement: JSON schema might be used here in future + """ + for i in list(bdev_info.keys()): + setattr(self, i, bdev_info[i]) + + +class LvolStore(object): + def __init__(self, lvs_info): + """ + All class attributes are set based on what information is received + from bdev_get_bdevs RPC call. + # TODO: Document in docstring parameters which describe bdevs. + # TODO: Possible improvement: JSON schema might be used here in future + """ + for i in list(lvs_info.keys()): + setattr(self, i, lvs_info[i]) + + +class VhostCtrlr(object): + def __init__(self, ctrlr_info): + """ + All class attributes are set based on what information is received + from vhost_get_controllers RPC call. + # TODO: Document in docstring parameters which describe bdevs. + # TODO: Possible improvement: JSON schema might be used here in future + """ + for i in list(ctrlr_info.keys()): + setattr(self, i, ctrlr_info[i]) + + +class NvmfTransport(object): + def __init__(self, transport_info): + """ + All class attributes are set based on what information is received + from get_nvmf_transport RPC call. + # TODO: Document in docstring parameters which describe bdevs. + # TODO: Possible improvement: JSON schema might be used here in future + """ + for i in transport_info.keys(): + setattr(self, i, transport_info[i]) + + +class NvmfSubsystem(object): + def __init__(self, subsystem_info): + """ + All class attributes are set based on what information is received + from get_nvmf_subsystem RPC call. + # TODO: Document in docstring parameters which describe bdevs. + # TODO: Possible improvement: JSON schema might be used here in future + """ + for i in subsystem_info.keys(): + setattr(self, i, subsystem_info[i]) + + +class ScsiObj(object): + def __init__(self, device_info): + """ + All class attributes are set based on what information is received + from iscsi related RPC calls. + # TODO: Document in docstring parameters which describe bdevs. + # TODO: Possible improvement: JSON schema might be used here in future + """ + for i in device_info.keys(): + setattr(self, i, device_info[i]) |