summaryrefslogtreecommitdiffstats
path: root/src/spdk/scripts/spdkcli
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/scripts/spdkcli
parentInitial commit. (diff)
downloadceph-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-xsrc/spdk/scripts/spdkcli.py84
-rw-r--r--src/spdk/scripts/spdkcli/__init__.py1
-rw-r--r--src/spdk/scripts/spdkcli/ui_node.py861
-rw-r--r--src/spdk/scripts/spdkcli/ui_node_iscsi.py639
-rw-r--r--src/spdk/scripts/spdkcli/ui_node_nvmf.py363
-rw-r--r--src/spdk/scripts/spdkcli/ui_root.py560
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])