diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 18:24:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 18:24:20 +0000 |
commit | 483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch) | |
tree | e5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/spdk/scripts/spdkcli | |
parent | Initial commit. (diff) | |
download | ceph-483eb2f56657e8e7f419ab1a4fab8dce9ade8609.tar.xz ceph-483eb2f56657e8e7f419ab1a4fab8dce9ade8609.zip |
Adding upstream version 14.2.21.upstream/14.2.21upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/scripts/spdkcli')
-rw-r--r-- | src/spdk/scripts/spdkcli/__init__.py | 1 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_node.py | 929 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_node_iscsi.py | 635 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_node_nvmf.py | 302 | ||||
-rw-r--r-- | src/spdk/scripts/spdkcli/ui_root.py | 482 |
5 files changed, 2349 insertions, 0 deletions
diff --git a/src/spdk/scripts/spdkcli/__init__.py b/src/spdk/scripts/spdkcli/__init__.py new file mode 100644 index 00000000..571d49a8 --- /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 00000000..43f6bdfc --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_node.py @@ -0,0 +1,929 @@ +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 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 msg: + self.shell.log.error(str(msg)) + pass + else: + self.shell.log.debug("Command %s succeeded." % command) + return result + + +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) + + +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().get_lvol_stores(): + UILvsObj(lvs, self) + + 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) + + try: + self.get_root().create_lvol_store(lvs_name=name, bdev_name=bdev_name, cluster_sz=cluster_size) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + 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().delete_lvol_store(lvs_name=name, uuid=uuid) + self.get_root().refresh() + self.refresh() + + 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().get_bdevs(self.name): + UIBdevObj(bdev, self) + + def ui_command_get_bdev_iostat(self, name=None): + try: + ret = self.get_root().get_bdevs_iostat(name=name) + self.shell.log.info(json.dumps(ret, indent=2)) + except JSONRPCException as e: + self.shell.log.error(e.message) + + def summary(self): + return "Bdevs: %d" % len(self.children), None + + +class UIMallocBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "malloc", parent) + + 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) + + try: + 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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().delete_malloc_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UIAIOBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "aio", parent) + + 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) + + try: + ret_name = self.get_root().create_aio_bdev(name=name, + block_size=int(block_size), + filename=filename) + self.shell.log.info(ret_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().delete_aio_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UILvolBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "logical_volume", parent) + + 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) + + try: + 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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().destroy_lvol_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UINvmeBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "nvme", parent) + + 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.") + + try: + 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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + def ui_command_delete(self, name): + """ + Deletes NVMe controller from configuration. + + Arguments: + name - Is a unique identifier of the NVMe controller to be deleted. + """ + try: + self.get_root().delete_nvme_controller(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UINullBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "null", parent) + + 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 + + try: + ret_name = self.get_root().create_null_bdev(num_blocks=num_blocks, + block_size=block_size, + name=name, uuid=uuid) + self.shell.log.info(ret_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().delete_null_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UIErrorBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "error", parent) + + 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. + """ + + try: + self.get_root().create_error_bdev(base_name=base_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().delete_error_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UISplitBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "split_disk", parent) + + def ui_command_split_bdev(self, base_bdev, split_count, split_size_mb=None): + """ + Construct 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) + + try: + ret_name = self.get_root().split_bdev(base_bdev=base_bdev, + split_count=split_count, + split_size_mb=split_size_mb) + self.shell.log.info(ret_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.parent.refresh() + self.refresh() + + def ui_command_destruct_split_bdev(self, base_bdev): + """Destroy split block devices associated with base bdev. + + Args: + base_bdev: name of previously split bdev + """ + + try: + self.get_root().destruct_split_bdev(base_bdev=base_bdev) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.parent.refresh() + self.refresh() + + +class UIPmemBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "pmemblk", parent) + + def ui_command_create_pmem_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) + + try: + self.get_root().create_pmem_pool(pmem_file=pmem_file, + num_blocks=num_blocks, + block_size=block_size) + except JSONRPCException as e: + self.shell.log.error(e.message) + + def ui_command_delete_pmem_pool(self, pmem_file): + try: + self.get_root().delete_pmem_pool(pmem_file=pmem_file) + except JSONRPCException as e: + self.shell.log.error(e.message) + + def ui_command_info_pmem_pool(self, pmem_file): + try: + ret = self.get_root().delete_pmem_pool(pmem_file=pmem_file) + self.shell.log.info(ret) + except JSONRPCException as e: + self.shell.log.error(e.message) + + def ui_command_create(self, pmem_file, name): + try: + ret_name = self.get_root().create_pmem_bdev(pmem_file=pmem_file, + name=name) + self.shell.log.info(ret_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().delete_pmem_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UIRbdBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "rbd", parent) + + def ui_command_create(self, pool_name, rbd_name, block_size, name=None): + block_size = self.ui_eval_param(block_size, "number", None) + + try: + 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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().delete_rbd_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +class UIiSCSIBdev(UIBdev): + def __init__(self, parent): + UIBdev.__init__(self, "iscsi", parent) + + 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. + """ + try: + ret_name = self.get_root().create_iscsi_bdev(name=name, + url=url, + initiator_iqn=initiator_iqn) + self.shell.log.info(ret_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + def ui_command_delete(self, name): + """ + Deletes iSCSI bdev from configuration. + + Arguments: + name - name of the iscsi bdev to be deleted. + """ + try: + self.get_root().delete_iscsi_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + +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) + + try: + 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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + 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. + """ + try: + self.get_root().remove_virtio_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh() + + +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().get_virtio_scsi_devs(): + 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) + + try: + 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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + def ui_command_delete(self, name): + try: + self.get_root().remove_virtio_bdev(name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + +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().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().remove_vhost_controller(ctrlr=name) + self.get_root().refresh() + self.refresh() + + +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().get_vhost_ctrlrs(self.name): + UIVhostBlkCtrlObj(ctrlr, self) + + def ui_command_create(self, name, bdev, cpumask=None, readonly=False): + """ + Construct 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. + """ + try: + self.get_root().create_vhost_blk_controller(ctrlr=name, + dev_name=bdev, + cpumask=cpumask, + readonly=bool(readonly)) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + +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().get_vhost_ctrlrs(self.name): + UIVhostScsiCtrlObj(ctrlr, self) + + def ui_command_create(self, name, cpumask=None): + """ + Construct a Vhost SCSI controller. + + Arguments: + name - Controller name. + cpumask - Optional. Integer to specify mask of CPUs to use. + Default: 1. + """ + try: + self.get_root().create_vhost_scsi_controller(ctrlr=name, + cpumask=cpumask) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.get_root().refresh() + self.refresh() + + +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) + + try: + self.get_root().set_vhost_controller_coalescing(ctrlr=self.ctrlr.ctrlr, + delay_base_us=delay_base_us, + iops_threshold=iops_threshold) + except JSONRPCException as e: + self.shell.log.error(e.message) + + +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. + """ + try: + self.get_root().remove_vhost_scsi_target(ctrlr=self.ctrlr.ctrlr, + scsi_target_num=int(target_num)) + for ctrlr in self.get_root().get_vhost_ctrlrs("scsi"): + if ctrlr.ctrlr == self.ctrlr.ctrlr: + self.ctrlr = ctrlr + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + self.get_root().refresh() + + 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. + """ + try: + self.get_root().add_vhost_scsi_lun(ctrlr=self.ctrlr.ctrlr, + scsi_target_num=int(target_num), + bdev_name=bdev_name) + for ctrlr in self.get_root().get_vhost_ctrlrs("scsi"): + if ctrlr.ctrlr == self.ctrlr.ctrlr: + self.ctrlr = ctrlr + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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) 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 00000000..65592a75 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_node_iscsi.py @@ -0,0 +1,635 @@ +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([]) + for param, val in self.get_root().get_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) + try: + self.get_root().set_iscsi_discovery_auth( + chap_group=chap_group, disable_chap=disable_chap, + require_chap=require_chap, mutual_chap=mutual_chap) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.refresh() + + +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.refresh() + + def refresh(self): + self._children = set([]) + self.target_nodes = list(self.get_root().get_target_nodes()) + self.scsi_devices = list(self.get_root().get_scsi_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 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) + try: + self.get_root().construct_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) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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. + """ + if name is None: + for device in self.devices: + try: + self.get_root().delete_target_node( + target_node_name=device.device_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + else: + try: + self.get_root().delete_target_node(target_node_name=name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.refresh() + + 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) + try: + self.get_root().target_node_add_lun( + name=name, bdev_name=bdev_name, lun_id=lun_id) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.parent.refresh() + + 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) + try: + self.get_root().set_iscsi_target_node_auth( + name=self.device.device_name, chap_group=chap_group, + disable_chap=disable_chap, + require_chap=require_chap, mutual_chap=mutual_chap) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.parent.refresh() + + def ui_command_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)}) + try: + self.get_root().add_pg_ig_maps( + pg_ig_maps=pg_ig_maps, name=self.device.device_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.parent.refresh() + + def ui_command_delete_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)}) + try: + self.get_root().delete_pg_ig_maps( + pg_ig_maps=pg_ig_maps, name=self.device.device_name) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.parent.refresh() + + 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 ui_command_create(self, tag, portal_list): + """Add a portal group. + + Args: + portals: List of portals e.g. ip:port@cpumask ip2:port2 + tag: Portal group tag (unique, integer > 0) + """ + portals = [] + print("portal_list: %s" % portal_list) + for portal in portal_list.strip().split(" "): + host = portal + cpumask = None + if "@" in portal: + host, cpumask = portal.split("@") + host, port = host.rsplit(":", -1) + portals.append({'host': host, 'port': port}) + if cpumask: + portals[-1]['cpumask'] = cpumask + tag = self.ui_eval_param(tag, "number", None) + try: + self.get_root().construct_portal_group(tag=tag, portals=portals) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + def ui_command_delete(self, tag): + """Delete a portal group with given tag (unique, integer > 0))""" + tag = self.ui_eval_param(tag, "number", None) + try: + self.get_root().delete_portal_group(tag=tag) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + def refresh(self): + self._children = set([]) + self.pgs = list(self.get_root().get_portal_groups()) + for pg in self.pgs: + UIPortalGroup(pg, self) + + 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'], portal['cpumask'], self) + + def summary(self): + return "Portals: %d" % len(self.pg.portals), None + + +class UIPortal(UINode): + def __init__(self, host, port, cpumask, parent): + UINode.__init__(self, "host=%s, port=%s, cpumask=%s" % ( + host, port, cpumask), parent) + self.refresh() + + +class UIInitiatorGroups(UINode): + def __init__(self, parent): + UINode.__init__(self, "initiator_groups", parent) + self.refresh() + + 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) + try: + self.get_root().construct_initiator_group( + tag=tag, initiators=initiator_list.split(" "), + netmasks=netmask_list.split(" ")) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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) + try: + self.get_root().delete_initiator_group(tag=tag) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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) + try: + self.get_root().add_initiators_to_initiator_group( + tag=tag, initiators=initiators.split(" "), + netmasks=netmasks.split(" ")) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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(" ") + try: + self.get_root().delete_initiators_from_initiator_group( + tag=tag, initiators=initiators, + netmasks=netmasks) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + def refresh(self): + self._children = set([]) + self.igs = list(self.get_root().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().get_iscsi_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.iteritems(): + 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().get_iscsi_auth_groups()) + if self.iscsi_auth_groups is None: + self.iscsi_auth_groups = [] + for ag in self.iscsi_auth_groups: + UIISCSIAuthGroup(ag, self) + + 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(",")] + try: + self.get_root().add_iscsi_auth_group(tag=tag, secrets=secrets) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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) + try: + self.get_root().delete_iscsi_auth_group(tag=tag) + except JSONRPCException as e: + self.shell.log.error(e.message) + + self.refresh() + + 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) + try: + self.get_root().add_secret_to_iscsi_auth_group( + tag=tag, user=user, secret=secret, + muser=muser, msecret=msecret) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.refresh() + + 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) + try: + self.get_root().delete_secret_from_iscsi_auth_group( + tag=tag, user=user) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.refresh() + + 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 = ", ".join("%s=%s" % (key, val) + for key, val in secret.items()) + 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 00000000..71b69367 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_node_nvmf.py @@ -0,0 +1,302 @@ +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) + + +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().get_nvmf_subsystems(): + UINVMfSubsystem(subsystem, self) + + 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) + try: + self.get_root().create_nvmf_subsystem(nqn=nqn, serial_number=serial_number, + allow_any_host=allow_any_host, + max_namespaces=max_namespaces) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.refresh() + + def ui_command_delete(self, subsystem_nqn): + """Delete subsystem with given nqn. + + Arguments: + nqn_subsystem - Name of susbsytem to delete + """ + try: + self.get_root().delete_nvmf_subsystem(nqn=subsystem_nqn) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.refresh() + + 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().get_nvmf_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) + try: + self.get_root().nvmf_subsystem_allow_any_host( + nqn=self.subsystem.nqn, disable=disable) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + 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().get_nvmf_subsystems(): + if subsystem.nqn == self.parent.subsystem.nqn: + self.listen_addresses = subsystem.listen_addresses + self.refresh() + + 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. + """ + try: + self.get_root().nvmf_subsystem_add_listener( + nqn=self.parent.subsystem.nqn, trtype=trtype, traddr=traddr, + trsvcid=trsvcid, adrfam=adrfam) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + 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"). + """ + try: + self.get_root().nvmf_subsystem_remove_listener( + nqn=self.parent.subsystem.nqn, trtype=trtype, + traddr=traddr, trsvcid=trsvcid, adrfam=adrfam) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + 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().get_nvmf_subsystems(): + if subsystem.nqn == self.parent.subsystem.nqn: + self.hosts = subsystem.hosts + self.refresh() + + 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 + """ + try: + self.get_root().nvmf_subsystem_add_host( + nqn=self.parent.subsystem.nqn, host=host) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + def ui_command_delete(self, host): + """Delete host from subsystem. + + Arguments: + host - NQN of host to remove. + """ + try: + self.get_root().nvmf_subsystem_remove_host( + nqn=self.parent.subsystem.nqn, host=host) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + 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().get_nvmf_subsystems(): + if subsystem.nqn == self.parent.subsystem.nqn: + self.namespaces = subsystem.namespaces + self.refresh() + + 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) + try: + self.get_root().nvmf_subsystem_add_ns( + nqn=self.parent.subsystem.nqn, bdev_name=bdev_name, + nsid=nsid, nguid=nguid, eui64=eui64, uuid=uuid) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + 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) + try: + self.get_root().nvmf_subsystem_remove_ns( + nqn=self.parent.subsystem.nqn, nsid=nsid) + except JSONRPCException as e: + self.shell.log.error(e.message) + self.get_root().refresh() + self.refresh_node() + + 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([str(self.namespace['uuid']), 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 00000000..9854d373 --- /dev/null +++ b/src/spdk/scripts/spdkcli/ui_root.py @@ -0,0 +1,482 @@ +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, s, shell): + UINode.__init__(self, "/", shell=shell) + self.current_bdevs = [] + self.current_lvol_stores = [] + self.current_vhost_ctrls = [] + self.current_nvmf_subsystems = [] + self.set_rpc_target(s) + self.verbose = False + self.is_init = self.check_init() + + def refresh(self): + if self.is_init is False: + methods = self.get_rpc_methods(current=True) + methods = "\n".join(methods) + self.shell.log.warning("SPDK Application is not yet initialized.\n" + "Please initialize subsystems with start_subsystem_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) + UIVhosts(self) + UINVMf(self) + UIISCSI(self) + + def set_rpc_target(self, s): + self.client = rpc.client.JSONRPCClient(s) + + 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.verbose = self.verbose + r = f(self, **kwargs) + self.client.verbose = False + return r + return w + + def ui_command_start_subsystem_init(self): + if rpc.start_subsystem_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 get_rpc_methods(self, current=False): + return rpc.get_rpc_methods(self.client, current=current) + + def check_init(self): + return "start_subsystem_init" not in self.get_rpc_methods(current=True) + + def get_bdevs(self, bdev_type): + if self.is_init: + self.current_bdevs = rpc.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 get_bdevs_iostat(self, **kwargs): + return rpc.bdev.get_bdevs_iostat(self.client, **kwargs) + + @verbose + def split_bdev(self, **kwargs): + response = rpc.bdev.construct_split_vbdev(self.client, **kwargs) + return self.print_array(response) + + @verbose + def destruct_split_bdev(self, **kwargs): + rpc.bdev.destruct_split_vbdev(self.client, **kwargs) + + @verbose + def delete_bdev(self, name): + rpc.bdev.delete_bdev(self.client, bdev_name=name) + + @verbose + def create_malloc_bdev(self, **kwargs): + response = rpc.bdev.construct_malloc_bdev(self.client, **kwargs) + return response + + @verbose + def delete_malloc_bdev(self, **kwargs): + rpc.bdev.delete_malloc_bdev(self.client, **kwargs) + + @verbose + def create_iscsi_bdev(self, **kwargs): + response = rpc.bdev.construct_iscsi_bdev(self.client, **kwargs) + return response + + @verbose + def delete_iscsi_bdev(self, **kwargs): + rpc.bdev.delete_iscsi_bdev(self.client, **kwargs) + + @verbose + def create_aio_bdev(self, **kwargs): + response = rpc.bdev.construct_aio_bdev(self.client, **kwargs) + return response + + @verbose + def delete_aio_bdev(self, **kwargs): + rpc.bdev.delete_aio_bdev(self.client, **kwargs) + + @verbose + def create_lvol_bdev(self, **kwargs): + response = rpc.lvol.construct_lvol_bdev(self.client, **kwargs) + return response + + @verbose + def destroy_lvol_bdev(self, **kwargs): + response = rpc.lvol.destroy_lvol_bdev(self.client, **kwargs) + return response + + @verbose + def create_nvme_bdev(self, **kwargs): + response = rpc.bdev.construct_nvme_bdev(self.client, **kwargs) + return response + + @verbose + def delete_nvme_controller(self, **kwargs): + rpc.bdev.delete_nvme_controller(self.client, **kwargs) + + @verbose + def create_null_bdev(self, **kwargs): + response = rpc.bdev.construct_null_bdev(self.client, **kwargs) + return response + + @verbose + def delete_null_bdev(self, **kwargs): + rpc.bdev.delete_null_bdev(self.client, **kwargs) + + @verbose + def create_error_bdev(self, **kwargs): + response = rpc.bdev.construct_error_bdev(self.client, **kwargs) + + @verbose + def delete_error_bdev(self, **kwargs): + rpc.bdev.delete_error_bdev(self.client, **kwargs) + + def get_lvol_stores(self): + if self.is_init: + self.current_lvol_stores = rpc.lvol.get_lvol_stores(self.client) + for lvs in self.current_lvol_stores: + yield LvolStore(lvs) + + @verbose + def create_lvol_store(self, **kwargs): + response = rpc.lvol.construct_lvol_store(self.client, **kwargs) + return response + + @verbose + def delete_lvol_store(self, **kwargs): + rpc.lvol.destroy_lvol_store(self.client, **kwargs) + + @verbose + def create_pmem_pool(self, **kwargs): + response = rpc.pmem.create_pmem_pool(self.client, **kwargs) + return response + + @verbose + def delete_pmem_pool(self, **kwargs): + rpc.pmem.delete_pmem_pool(self.client, **kwargs) + + @verbose + def create_pmem_bdev(self, **kwargs): + response = rpc.bdev.construct_pmem_bdev(self.client, **kwargs) + return response + + @verbose + def delete_pmem_bdev(self, **kwargs): + response = rpc.bdev.delete_pmem_bdev(self.client, **kwargs) + return response + + @verbose + def create_rbd_bdev(self, **kwargs): + response = rpc.bdev.construct_rbd_bdev(self.client, **kwargs) + return response + + @verbose + def delete_rbd_bdev(self, **kwargs): + response = rpc.bdev.delete_rbd_bdev(self.client, **kwargs) + return response + + @verbose + def create_virtio_dev(self, **kwargs): + response = rpc.vhost.construct_virtio_dev(self.client, **kwargs) + return self.print_array(response) + + @verbose + def remove_virtio_bdev(self, **kwargs): + response = rpc.vhost.remove_virtio_bdev(self.client, **kwargs) + return response + + def get_virtio_scsi_devs(self): + if self.is_init: + for bdev in rpc.vhost.get_virtio_scsi_devs(self.client): + test = Bdev(bdev) + yield test + + def list_vhost_ctrls(self): + if self.is_init: + self.current_vhost_ctrls = rpc.vhost.get_vhost_controllers(self.client) + + def get_vhost_ctrlrs(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 remove_vhost_controller(self, **kwargs): + rpc.vhost.remove_vhost_controller(self.client, **kwargs) + + @verbose + def create_vhost_scsi_controller(self, **kwargs): + rpc.vhost.construct_vhost_scsi_controller(self.client, **kwargs) + + @verbose + def create_vhost_blk_controller(self, **kwargs): + rpc.vhost.construct_vhost_blk_controller(self.client, **kwargs) + + @verbose + def remove_vhost_scsi_target(self, **kwargs): + rpc.vhost.remove_vhost_scsi_target(self.client, **kwargs) + + @verbose + def add_vhost_scsi_lun(self, **kwargs): + rpc.vhost.add_vhost_scsi_lun(self.client, **kwargs) + + def set_vhost_controller_coalescing(self, **kwargs): + rpc.vhost.set_vhost_controller_coalescing(self.client, **kwargs) + + def list_nvmf_subsystems(self): + if self.is_init: + self.current_nvmf_subsystems = rpc.nvmf.get_nvmf_subsystems(self.client) + + def get_nvmf_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_subsystem_create(self.client, **kwargs) + + @verbose + def delete_nvmf_subsystem(self, **kwargs): + rpc.nvmf.delete_nvmf_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) + + def get_scsi_devices(self): + if self.is_init: + for device in rpc.iscsi.get_scsi_devices(self.client): + yield ScsiObj(device) + + def get_target_nodes(self): + if self.is_init: + for tg in rpc.iscsi.get_target_nodes(self.client): + yield tg + + @verbose + def construct_target_node(self, **kwargs): + rpc.iscsi.construct_target_node(self.client, **kwargs) + + @verbose + def delete_target_node(self, **kwargs): + rpc.iscsi.delete_target_node(self.client, **kwargs) + + def get_portal_groups(self): + if self.is_init: + for pg in rpc.iscsi.get_portal_groups(self.client): + yield ScsiObj(pg) + + def get_initiator_groups(self): + if self.is_init: + for ig in rpc.iscsi.get_initiator_groups(self.client): + yield ScsiObj(ig) + + @verbose + def construct_portal_group(self, **kwargs): + rpc.iscsi.add_portal_group(self.client, **kwargs) + + @verbose + def delete_portal_group(self, **kwargs): + rpc.iscsi.delete_portal_group(self.client, **kwargs) + + @verbose + def construct_initiator_group(self, **kwargs): + rpc.iscsi.add_initiator_group(self.client, **kwargs) + + @verbose + def delete_initiator_group(self, **kwargs): + rpc.iscsi.delete_initiator_group(self.client, **kwargs) + + @verbose + def get_iscsi_connections(self, **kwargs): + if self.is_init: + for ic in rpc.iscsi.get_iscsi_connections(self.client, **kwargs): + yield ic + + @verbose + def add_initiators_to_initiator_group(self, **kwargs): + rpc.iscsi.add_initiators_to_initiator_group(self.client, **kwargs) + + @verbose + def delete_initiators_from_initiator_group(self, **kwargs): + rpc.iscsi.delete_initiators_from_initiator_group(self.client, **kwargs) + + @verbose + def add_pg_ig_maps(self, **kwargs): + rpc.iscsi.add_pg_ig_maps(self.client, **kwargs) + + @verbose + def delete_pg_ig_maps(self, **kwargs): + rpc.iscsi.delete_pg_ig_maps(self.client, **kwargs) + + @verbose + def add_secret_to_iscsi_auth_group(self, **kwargs): + rpc.iscsi.add_secret_to_iscsi_auth_group(self.client, **kwargs) + + @verbose + def delete_secret_from_iscsi_auth_group(self, **kwargs): + rpc.iscsi.delete_secret_from_iscsi_auth_group(self.client, **kwargs) + + @verbose + def get_iscsi_auth_groups(self, **kwargs): + return rpc.iscsi.get_iscsi_auth_groups(self.client, **kwargs) + + @verbose + def add_iscsi_auth_group(self, **kwargs): + rpc.iscsi.add_iscsi_auth_group(self.client, **kwargs) + + @verbose + def delete_iscsi_auth_group(self, **kwargs): + rpc.iscsi.delete_iscsi_auth_group(self.client, **kwargs) + + @verbose + def set_iscsi_target_node_auth(self, **kwargs): + rpc.iscsi.set_iscsi_target_node_auth(self.client, **kwargs) + + @verbose + def target_node_add_lun(self, **kwargs): + rpc.iscsi.target_node_add_lun(self.client, **kwargs) + + @verbose + def set_iscsi_discovery_auth(self, **kwargs): + rpc.iscsi.set_iscsi_discovery_auth(self.client, **kwargs) + + @verbose + def get_iscsi_global_params(self, **kwargs): + return rpc.iscsi.get_iscsi_global_params(self.client, **kwargs) + + +class Bdev(object): + def __init__(self, bdev_info): + """ + All class attributes are set based on what information is received + from 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 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 get_vhost_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 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]) |