Repository: stratis-storage/stratis-cli (language: python)

Issue:
Update changelog



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 656e9e4a5..441ef87cf 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,11 +6,11 @@ on:
   push:
     branches:
       - master
-      - develop-2.2.1
+      - develop-2.3.0
   pull_request:
     branches:
       - master
-      - develop-2.2.1
+      - develop-2.3.0
 
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
@@ -21,29 +21,29 @@ jobs:
       matrix:
         include:
           # MANDATORY CHECKS USING CURRENT DEVELOPMENT INTERPRETER
-          - python-version: 3.7.9
+          - python-version: 3.8.6
             dependencies: >
               pylint==2.4.4
               dbus-client-gen==0.4
               dbus-python-client-gen==0.7
               justbytes==0.11
               python-dateutil==2.8.0
-              wcwidth==0.1.9
+              wcwidth==0.2.4
               psutil==5.6.7
-              semantic_version==2.6.0
+              semantic_version==2.8.4
             task: PYTHONPATH=./src make -f Makefile lint
-          - python-version: 3.7.9
-            dependencies: black==19.10b0 isort==4.3.21
+          - python-version: 3.8.6
+            dependencies: black==20.8b1 isort==4.3.21
             task: make -f Makefile fmt-travis
-          - python-version: 3.7.9
+          - python-version: 3.8.6
             dependencies: >
               dbus-client-gen==0.4
               dbus-python-client-gen==0.7
               justbytes==0.11
               python-dateutil==2.8.0
-              wcwidth==0.1.9
+              wcwidth==0.2.4
               psutil==5.6.7
-              semantic_version==2.6.0
+              semantic_version==2.8.4
             task: PYTHONPATH=./src make -f Makefile test-travis
           # MANDATORY CHECKS USING LOWEST SUPPORTED INTERPRETER
           - python-version: 3.6.8
@@ -57,10 +57,10 @@ jobs:
               semantic_version==2.6.0
             task: PYTHONPATH=./src make -f Makefile lint
           # VERIFICATION OF TEST INFRASTRUCTURE
-          - python-version: 3.7.9
-            dependencies: yamllint==1.23.0
+          - python-version: 3.8.6
+            dependencies: yamllint==1.26.0
             task: make -f Makefile yamllint
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-18.04
     steps:
       - uses: actions/checkout@v2
       - uses: actions/setup-python@v2
diff --git a/CHANGES.txt b/CHANGES.txt
index 08702e759..50e746c85 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,78 @@
+stratis-cli 2.4.0
+=================
+Required stratisd version: 2.4.0
+
+Recommended Python interpreter: 3.8.6
+Lowest supported Python interpreter: 3.6.8
+Python linter: pylint (2.4.4)
+Python auto-formatter: black (20.8b1)
+Python import sorter: isort (4.3.21)
+YAML linter: yamllint (1.26.0)
+
+- Add support for creating an encrypted pool bound with Clevis or a keyring,
+  and binding and unbinding with either encryption facility:
+  https://github.com/stratis-storage/stratis-cli/issues/733
+  https://github.com/stratis-storage/stratis-cli/pull/749
+  https://github.com/stratis-storage/stratis-cli/pull/748
+  https://github.com/stratis-storage/stratis-cli/pull/743
+  https://github.com/stratis-storage/stratis-cli/pull/736
+  https://github.com/stratis-storage/stratis-cli/pull/735
+
+- Print pool UUID in pool list command:
+  https://github.com/stratis-storage/stratis-cli/pull/695
+
+- Modify report subcommand so that it defaults to 'engine_state_report';
+  guarantee stability of 'engine_state_report' name:
+  https://github.com/stratis-storage/stratis-cli/issues/700
+  https://github.com/stratis-storage/stratis-cli/pull/717
+  https://github.com/stratis-storage/stratis-cli/pull/703
+  https://github.com/stratis-storage/stratis-cli/pull/702
+
+- Make the hyphenated format for UUIDs the default, add a flag to allow
+  reverting to unhyphenated format:
+  https://github.com/stratis-storage/stratis-cli/issues/698
+  https://github.com/stratis-storage/stratis-cli/pull/705
+
+- Expand relative paths to absolute paths in init-cache command,
+  add assertions to all D-Bus commands that take device paths to verify
+  that the device paths are absolute:
+  https://github.com/stratis-storage/stratis-cli/pull/750
+  https://github.com/stratis-storage/stratis-cli/pull/746
+
+- Add an explanation for an exception chain containing a GetManagedObjects()
+  failure, which may occur with a multi-threaded daemon implementation:
+  https://github.com/stratis-storage/stratis-cli/pull/713
+
+- Remove the blackbox tests:
+  https://github.com/stratis-storage/stratis-cli/pull/707
+
+- Update the bash completion script for new commands:
+  https://github.com/stratis-storage/stratis-cli/pull/714
+
+- Tidies and Maintenance:
+  https://github.com/stratis-storage/stratis-cli/pull/742
+  https://github.com/stratis-storage/stratis-cli/pull/740
+  https://github.com/stratis-storage/stratis-cli/pull/739
+  https://github.com/stratis-storage/stratis-cli/pull/737
+  https://github.com/stratis-storage/stratis-cli/pull/732
+  https://github.com/stratis-storage/stratis-cli/pull/729
+  https://github.com/stratis-storage/stratis-cli/pull/728
+  https://github.com/stratis-storage/stratis-cli/pull/726
+  https://github.com/stratis-storage/stratis-cli/pull/723
+  https://github.com/stratis-storage/stratis-cli/pull/719
+  https://github.com/stratis-storage/stratis-cli/pull/718
+  https://github.com/stratis-storage/stratis-cli/pull/716
+  https://github.com/stratis-storage/stratis-cli/pull/712
+  https://github.com/stratis-storage/stratis-cli/pull/709
+  https://github.com/stratis-storage/stratis-cli/pull/708
+  https://github.com/stratis-storage/stratis-cli/pull/706
+  https://github.com/stratis-storage/stratis-cli/pull/704
+  https://github.com/stratis-storage/stratis-cli/pull/699
+  https://github.com/stratis-storage/stratis-cli/pull/697
+  https://github.com/stratis-storage/stratis-cli/pull/696
+  https://github.com/stratis-storage/stratis-cli/pull/694
+
+
 stratis-cli 2.3.0
 =================
 Required stratisd version: 2.3.0
@@ -20,6 +95,7 @@ YAML linter: yamllint (1.23.0)
   https://github.com/stratis-storage/stratis-cli/pull/681
 
 - Tidies and Maintenance:
+  https://github.com/stratis-storage/stratis-cli/pull/692
   https://github.com/stratis-storage/stratis-cli/pull/691
   https://github.com/stratis-storage/stratis-cli/pull/688
   https://github.com/stratis-storage/stratis-cli/pull/678
diff --git a/Makefile b/Makefile
index d56978601..64a13367d 100644
--- a/Makefile
+++ b/Makefile
@@ -7,9 +7,6 @@ lint:
 	./check.py developer_tools
 	./check.py bin/stratis
 	./check.py src/stratis_cli
-	./check.py tests/blackbox/stratis_cli_cert.py
-	./check.py tests/blackbox/stratisd_cert.py
-	./check.py tests/blackbox/testlib
 	./check.py tests/whitebox
 
 .PHONY: fmt
@@ -20,7 +17,7 @@ fmt:
 .PHONY: fmt-travis
 fmt-travis:
 	isort --recursive --diff --check-only check.py setup.py bin/stratis src tests
-	black ./bin/stratis . --check
+	black ./bin/stratis ./developer_tools/update_introspection_data . --check
 
 PYREVERSE_OPTS = --output=pdf
 .PHONY: view
@@ -49,7 +46,7 @@ api-docs:
 	sphinx-build-3 -b html api api/_build/html
 
 dbus-tests:
-	python3 -m unittest discover ${UNITTEST_OPTS} --top-level-directory ./tests/whitebox --start-directory ./tests/whitebox/integration > /dev/null
+	python3 -m unittest discover ${UNITTEST_OPTS} --top-level-directory ./tests/whitebox --start-directory ./tests/whitebox/integration
 
 unittest-tests:
 	python3 -m unittest discover ${UNITTEST_OPTS} --start-directory ./tests/whitebox/unittest
@@ -57,7 +54,7 @@ unittest-tests:
 .PHONY: coverage-no-html
 coverage-no-html:
 	python3 -m coverage --version
-	python3 -m coverage run --timid --branch -m unittest discover --quiet --top-level-directory ./tests/whitebox --start-directory ./tests/whitebox/integration > /dev/null
+	python3 -m coverage run --timid --branch -m unittest discover --quiet --top-level-directory ./tests/whitebox --start-directory ./tests/whitebox/integration >& /dev/null
 	python3 -m coverage run --timid --branch -a -m unittest discover --quiet --start-directory ./tests/whitebox/unittest
 	python3 -m coverage run --timid --branch -a -m unittest --quiet tests.whitebox.monkey_patching.test_keyboard_interrupt.KeyboardInterruptTestCase
 	python3 -m coverage run --timid --branch -a -m unittest --quiet tests.whitebox.monkey_patching.test_stratisd_version.StratisdVersionTestCase
diff --git a/README.rst b/README.rst
index 93e67ce60..363c39002 100644
--- a/README.rst
+++ b/README.rst
@@ -19,16 +19,21 @@ processing and displaying the results.
 
 Installing
 ----------
+You can install ``stratis-cli`` directly from the ``stratis-cli`` project
+repo.
 
-To install, check out the source, and use the included setup script, as::
+``stratis-cli`` has a number of dependencies that may not already be
+installed. You may choose to allow the setup script to install any missing
+dependencies from PyPi, or you may prefer to install the dependencies using
+your distribution's package manager. All ``stratis-cli``'s direct
+dependencies are listed in ``stratis-cli``'s setup.py file, in the
+``install_requires`` field. If you choose to install the dependencies
+using your installation's package manager, you should do so before you
+run the setup.py script.
 
-   > python setup.py install
-
-You will also need to obtain the following related Stratis repos:
-dbus-client-gen, dbus-python-client-gen, into-dbus-python,
-dbus-signature-pyparsing.
+Finally, run the setup.py script as::
 
-Finally, ensure you have the pyparsing package installed.
+   > python setup.py install
 
 Running
 -------
diff --git a/check.py b/check.py
index 6822a5105..51243da7b 100755
--- a/check.py
+++ b/check.py
@@ -33,21 +33,6 @@
         "--ignore=_introspect.py",
         "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'",
     ],
-    "tests/blackbox/stratisd_cert.py": [
-        "--reports=no",
-        "--disable=I",
-        "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'",
-    ],
-    "tests/blackbox/stratis_cli_cert.py": [
-        "--reports=no",
-        "--disable=I",
-        "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'",
-    ],
-    "tests/blackbox/testlib": [
-        "--reports=no",
-        "--disable=I",
-        "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'",
-    ],
     "tests/whitebox": [
         "--reports=no",
         "--disable=I",
diff --git a/developer_tools/update_introspection_data b/developer_tools/update_introspection_data
index 77d33d000..283b3c026 100755
--- a/developer_tools/update_introspection_data
+++ b/developer_tools/update_introspection_data
@@ -22,6 +22,7 @@ import xml.etree.ElementTree as ET
 
 # isort: THIRDPARTY
 import dbus
+from semantic_version import Version
 
 # isort: FIRSTPARTY
 from dbus_python_client_gen import make_class
@@ -46,6 +47,9 @@ SPECS = {
 <arg name="return_code" type="q" direction="out"/>
 <arg name="return_string" type="s" direction="out"/>
 </method>
+<property name="Version" type="s" access="read">
+<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
+</property>
 </interface>
 """,
     "org.storage.stratis2.pool.r1": """
@@ -73,16 +77,16 @@ Introspectable = make_class(
 Manager = make_class("Manager", ET.fromstring(SPECS[_MANAGER_IFACE]), _TIMEOUT)
 Pool = make_class("Pool", ET.fromstring(SPECS[_POOL_IFACE]), _TIMEOUT)
 
-# EDIT these fields to get the interfaces and revisions desired
-TOP_OBJECT_INTERFACES = [
-    "org.freedesktop.DBus.ObjectManager",
-    "org.storage.stratis2.FetchProperties.r3",
-    "org.storage.stratis2.Manager.r3",
-    "org.storage.stratis2.Report.r1",
+OBJECT_MANAGER_INTERFACE = "org.freedesktop.DBus.ObjectManager"
+
+TOP_OBJECT_INTERFACE_PREFIXES = [
+    "org.storage.stratis2.FetchProperties",
+    "org.storage.stratis2.Manager",
+    "org.storage.stratis2.Report",
 ]
-POOL_OBJECT_INTERFACES = ["org.storage.stratis2.pool.r3"]
-BLOCKDEV_OBJECT_INTERFACES = ["org.storage.stratis2.blockdev.r2"]
-FILESYSTEM_OBJECT_INTERFACES = ["org.storage.stratis2.filesystem"]
+POOL_OBJECT_INTERFACE_PREFIXES = ["org.storage.stratis2.pool"]
+BLOCKDEV_OBJECT_INTERFACE_PREFIXES = ["org.storage.stratis2.blockdev"]
+FILESYSTEM_OBJECT_INTERFACE_PREFIXES = ["org.storage.stratis2.filesystem"]
 
 
 def _add_data(proxy, interfaces, table):
@@ -93,28 +97,33 @@ def _add_data(proxy, interfaces, table):
     :param proxy: dbus Proxy object
     :param list interfaces: list of interesting interface names
     :param dict table: table from interface names to introspection data as text
+    :raises: RuntimeError if some interface not found
     """
     string_data = Introspectable.Methods.Introspect(proxy, {})
     xml_data = ET.fromstring(string_data)
-    for interface in xml_data:
-        interface_name = interface.attrib["name"]
-        if interface_name in interfaces:
-            table[interface_name] = ET.tostring(interface).decode("utf-8").rstrip(" \n")
 
-    if not (frozenset(interfaces) <= frozenset(table.keys())):
-        exit(
-            "not %s <= %s"
-            % (sorted(frozenset(interfaces)), sorted(frozenset(table.keys())),)
-        )
+    for interface_name in interfaces:
+        try:
+            interface = next(
+                interface
+                for interface in xml_data
+                if interface.attrib["name"] == interface_name
+            )
+            table[interface_name] = ET.tostring(interface).decode("utf-8").rstrip(" \n")
+        except StopIteration as err:
+            raise RuntimeError(
+                "interface %s not found in introspection data" % interface_name
+            ) from err
 
 
-def main():
+def setup_minimal_object_set(bus):
     """
-    The main method.
-    """
-    specs = {}
-    bus = dbus.SystemBus()
+    Set up the minimal set of objects to be introspected on.
 
+    :param bus: the bus
+    :returns: a dict of proxy objects
+    :rtype: dict of str * dbus proxy object
+    """
     proxy = bus.get_object(_SERVICE, "/org/storage/stratis2", introspect=False)
 
     (
@@ -139,7 +148,10 @@ def main():
     blockdev_proxy = bus.get_object(_SERVICE, dev_object_paths[0], introspect=False)
 
     ((_, (filesystems)), return_code, return_msg,) = Pool.Methods.CreateFilesystems(
-        pool_proxy, {"specs": ["fs_name"],},
+        pool_proxy,
+        {
+            "specs": ["fs_name"],
+        },
     )
 
     if return_code != 0:
@@ -150,14 +162,67 @@ def main():
         _SERVICE, filesystem_object_path, introspect=False
     )
 
-    _add_data(proxy, TOP_OBJECT_INTERFACES, specs)
-    _add_data(pool_proxy, POOL_OBJECT_INTERFACES, specs)
-    _add_data(blockdev_proxy, BLOCKDEV_OBJECT_INTERFACES, specs)
-    _add_data(filesystem_proxy, FILESYSTEM_OBJECT_INTERFACES, specs)
+    return {
+        "top_proxy": proxy,
+        "pool_proxy": pool_proxy,
+        "filesystem_proxy": filesystem_proxy,
+        "blockdev_proxy": blockdev_proxy,
+    }
+
+
+def make_introspection_spec(proxies):
+    """
+    Make the introspection specification using proxy objects.
+
+    :param proxies:a dictionary of distinguishing names to proxy objects
+    :type proxies: dict of str * proxy object
+    :returns: a table with interface name keys and introspection data values
+    :rtype: dict of str * str
+    """
+    specs = {}
+
+    revision = (
+        "r%d" % Version(Manager.Properties.Version.Get(proxies["top_proxy"])).minor
+    )
+
+    def get_current_interfaces(interface_prefixes):
+        return ["%s.%s" % (prefix, revision) for prefix in interface_prefixes]
 
+    _add_data(
+        proxies["top_proxy"],
+        [OBJECT_MANAGER_INTERFACE]
+        + get_current_interfaces(TOP_OBJECT_INTERFACE_PREFIXES),
+        specs,
+    )
+    _add_data(
+        proxies["pool_proxy"],
+        get_current_interfaces(POOL_OBJECT_INTERFACE_PREFIXES),
+        specs,
+    )
+
+    _add_data(
+        proxies["blockdev_proxy"],
+        get_current_interfaces(BLOCKDEV_OBJECT_INTERFACE_PREFIXES),
+        specs,
+    )
+    _add_data(
+        proxies["filesystem_proxy"],
+        get_current_interfaces(FILESYSTEM_OBJECT_INTERFACE_PREFIXES),
+        specs,
+    )
+
+    return specs
+
+
+def print_spec(specs):
+    """
+    Print spec formatted according to stratis-cli and black's requirments.
+
+    :param specs: the specification to print
+    :type specs: dict of str * str
+    """
     print("SPECS = {")
 
-    # format the lines in a way that will satisfy black
     print(
         ",\n".join(
             '    "%s": """\n%s\n"""' % (key, value)
@@ -168,5 +233,18 @@ def main():
     print("}")
 
 
+def main():
+    """
+    The main method.
+    """
+    bus = dbus.SystemBus()
+
+    proxies = setup_minimal_object_set(bus)
+
+    specs = make_introspection_spec(proxies)
+
+    print_spec(specs)
+
+
 if __name__ == "__main__":
     main()
diff --git a/docs/stratis.txt b/docs/stratis.txt
index 5c2d4d4a1..2c5522259 100644
--- a/docs/stratis.txt
+++ b/docs/stratis.txt
@@ -31,10 +31,12 @@ GLOBAL OPTIONS
 	Show help on command.
 --propagate::
 	(For debugging.) Allow exceptions raised during execution to propagate.
+--unhyphenated-uuids::
+	(For listing.) Print pool and filesystem UUIDs without hyphens for list commands.
 
 COMMANDS
 --------
-pool create [--redundancy <redundancy>] [--key-desc <key_desc>] <pool_name> <blockdev> [<blockdev>..]::
+pool create [--redundancy <redundancy>] [--key-desc <key_desc>] [--clevis <(nbde|tang|tpm2)> [--tang-url <tang_url>] [<(--thumbprint <thp> | --trust-url)>] <pool_name> <blockdev> [<blockdev>..]::
      Create a pool from one or more block devices, with the given pool name.
 pool list::
      List all pools on the system.
@@ -56,19 +58,19 @@ pool unlock <(keyring | clevis)>::
      Unlock all devices that are part of an encrypted pool registered with stratisd
      but that have not yet been opened. The available unlock methods are
      *keyring* or *clevis*.
-pool bind <(nbde|tang)> <pool name> <key description> <url> <(--thumbprint <thp> | --trust-url)>::
+pool bind <(nbde|tang)> <pool name> <url> <(--thumbprint <thp> | --trust-url)>::
      Bind the devices in the specified pool to a supplementary encryption
      mechanism that uses NBDE (Network-Bound Disc Encryption). *tang* is
-     an alias for *nbde*. The description of the key in the kernel keyring
-     used to encrypt the devices must be supplied.
-pool bind tpm2 <pool name> <key description>::
+     an alias for *nbde*.
+pool bind tpm2 <pool name>::
      Bind the devices in the specified pool to a supplementary encryption
-     mechanism that uses TPM 2.0 (Trusted Platform Module). The description of
-     the key in the kernel keyring used to encrypt the devices must be supplied.
-pool unbind <pool name>::
-     Unbind the devices in the specified pool from an existing supplementary
-     encryption mechanism. Unbinding leaves the primary encryption mechanism,
-     which uses a key in the kernel keyring, unaffected.
+     mechanism that uses TPM 2.0 (Trusted Platform Module).
+pool bind keyring <pool name> <keydesc>::
+     Bind the devices in the specified pool to a supplementary encryption
+     mechanism using a key in the kernel keyring.
+pool unbind <(clevis|keyring)> <pool name> ::
+     Unbind the devices in the specified pool from the specified encryption
+     mechanism.
 filesystem create <pool_name> <fs_name> [<fs_name>..]::
 	   Create one or more filesystems from the specified pool.
            NOTE: There is a temporary restriction on the number of filesystems
@@ -96,9 +98,10 @@ key unset <key_desc>::
      Unset a key in the kernel keyring so it is no longer available for encryption
      operations.
 report <report_name>::
-        Get a report from the daemon regarding its internal state. This command is
-        not supported as the report names and returned JSON schemas are unstable
-        and may be changed at any time.
+        Get a report from the daemon regarding its internal state.
+        The engine_state_report name will be supported in future releases.
+        Any other report name should be considered unstable and may be removed
+        in a future release. The JSON schema of any report must always be considered unstable.
 daemon redundancy::
        List the redundancy levels that the Stratis service supports.
 daemon version::
@@ -128,6 +131,11 @@ OPTIONS
         server's URL should be trusted and the server's credentials accepted
         without verification, or to supply a previously provided thumbprint for
         verification.
+--tang-url <url>::
+        If creating a pool encrypted via NBDE using a tang server, specifies
+        the URL of the server.
+--clevis <(nbde | tang | tpm2)> ::
+        The clevis method that should be used to encrypt the created pool.
 
 ENVIRONMENT VARIABLES
 ---------------------
@@ -223,7 +231,7 @@ stratis filesystem create mypool data1
 ====
 .Binding a pool's devices to use an NBDE policy for decryption
 ====
-stratis pool bind nbde --trust-url mypool someKeyDescription someTangServerUrl
+stratis pool bind nbde --trust-url mypool someTangServerUrl
 ====
 
 SEE ALSO
diff --git a/shell-completion/bash/stratis b/shell-completion/bash/stratis
index 9cc15334c..62099d851 100755
--- a/shell-completion/bash/stratis
+++ b/shell-completion/bash/stratis
@@ -12,9 +12,13 @@ _stratis()
 	third="${COMP_WORDS[3]}"
 	opts="-h --help"
 	global_opts="--version --propagate ${opts}"
+	listing_opts="--unhyphenated-uuids"
 	root_subcommands="daemon pool blockdev filesystem key report"
-	pool_subcommands="create list add-data add-cache rename destroy init-cache unlock"
-	pool_opts="--redundancy --key-desc"
+	pool_subcommands="add-cache add-data bind create destroy init-cache list rename unbind unlock"
+	pool_create_opts="--redundancy --key-desc"
+	pool_bind_opts="--thumbprint --trust-url"
+	pool_bind_subcommands="nbde tang tpm2"
+	pool_unlock_subcommands="clevis keyring"
 	fs_subcommands="create snapshot list rename destroy"
 	blockdev_subcommands="list"
 	daemon_subcommands="redundancy version"
@@ -29,9 +33,20 @@ _stratis()
 		return 0
 	elif [[ ${cur} == -* ]] ; then
         if [[ ${COMP_CWORD} -eq 1 ]]; then
-            COMPREPLY=( $(compgen -W "${global_opts}" -- ${cur}) )
+            COMPREPLY=( $(compgen -W "${global_opts} ${opts}" -- ${cur}) )
+	elif [[ ${second} == "list" ]]; then
+	    COMPREPLY=( $(compgen -W "${opts} ${listing_opts}" -- ${cur}) )
         elif [[ ${first} == "pool" ]]; then
-        	COMPREPLY=( $(compgen -W "${pool_opts} ${opts}" -- ${cur}) )
+			if [[ ${second} == "create" ]]; then
+				COMPREPLY=( $(compgen -W "${pool_create_opts} ${opts}" -- ${cur}) )
+			elif [[ ${second} == "bind" ]] && [[ ${third} != "tpm2" ]]; then
+				COMPREPLY=( $(compgen -W "${pool_bind_opts} ${opts}" -- ${cur}) )
+			else
+				COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+			fi
+			
+			return 0
+        	
         elif [[ ${first} == "key" ]]; then
         	case ${second} in
         		set|reset)
@@ -102,10 +117,17 @@ _stratis()
 				;;
 			pool)
 				case ${prev} in
+					unlock)
+						COMPREPLY=( $(compgen -W "${pool_unlock_subcommands}" -- ${cur}) )
+						return 0
+						;;
+					bind)
+						COMPREPLY=( $(compgen -W "${pool_bind_subcommands}" -- ${cur}) )
+						;;
 					-h|--help|create|list|unlock)
 						return 0
 						;;
-					rename|destroy|add-data|add-cache|init-cache)
+					rename|destroy|add-data|add-cache|init-cache|unbind)
 						COMPREPLY=( $(compgen -W  "$(stratis pool list | awk '{if (NR!=1) {print $1}}')" -- ${cur}) )
 						return 0
 						;;
@@ -144,13 +166,17 @@ _stratis()
 				;;
 			pool)
 				case ${second} in
-					-h|--help|list|rename|destroy)
+					-h|--help|list|rename|destroy|unbind|unlock)
 						return 0
 						;;
 					create|add-data|add-cache|init-cache)
 						COMPREPLY=( $(compgen -W  "${blockdevs}" -- ${cur}) )
 						return 0
 						;;
+					bind)
+						COMPREPLY=( $(compgen -W  "$(stratis pool list | awk '{if (NR!=1) {print $1}}')" -- ${cur}) )
+						return 0
+						;;
 				esac
 				;;
 			key)
@@ -191,7 +217,7 @@ _stratis()
 				;;
 			pool)
 				case ${second} in
-					-h|--help|list|rename|destroy)
+					-h|--help|list|rename|destroy|bind|unbind|unlock)
 						return 0
 						;;
 					create|add-cache|add-data|init-cache)
diff --git a/src/stratis_cli/_actions/__init__.py b/src/stratis_cli/_actions/__init__.py
index 29a92f532..450a22c93 100644
--- a/src/stratis_cli/_actions/__init__.py
+++ b/src/stratis_cli/_actions/__init__.py
@@ -18,6 +18,7 @@
 from ._constants import BLOCKDEV_INTERFACE, FILESYSTEM_INTERFACE, POOL_INTERFACE
 from ._logical import LogicalActions
 from ._physical import PhysicalActions
+from ._pool import PoolActions
 from ._stratis import StratisActions
 from ._stratisd_version import check_stratisd_version
 from ._top import TopActions
diff --git a/src/stratis_cli/_actions/_constants.py b/src/stratis_cli/_actions/_constants.py
index 4d31daa5f..0873db7c1 100644
--- a/src/stratis_cli/_actions/_constants.py
+++ b/src/stratis_cli/_actions/_constants.py
@@ -22,12 +22,15 @@
 
 SECTOR_SIZE = 512
 
-FETCH_PROPERTIES_INTERFACE = "org.storage.stratis2.FetchProperties.r3"
-FILESYSTEM_INTERFACE = "org.storage.stratis2.filesystem"
-POOL_INTERFACE = "org.storage.stratis2.pool.r3"
-BLOCKDEV_INTERFACE = "org.storage.stratis2.blockdev.r2"
-REPORT_INTERFACE = "org.storage.stratis2.Report.r1"
-
 MAXIMUM_STRATISD_VERSION = "3.0.0"
-MINIMUM_STRATISD_VERSION = "2.3.0"
+MINIMUM_STRATISD_VERSION = "2.4.0"
 assert Version(MINIMUM_STRATISD_VERSION) < Version(MAXIMUM_STRATISD_VERSION)
+
+REVISION = "r%s" % Version(MINIMUM_STRATISD_VERSION).minor
+
+BLOCKDEV_INTERFACE = "org.storage.stratis2.blockdev.%s" % REVISION
+FETCH_PROPERTIES_INTERFACE = "org.storage.stratis2.FetchProperties.%s" % REVISION
+FILESYSTEM_INTERFACE = "org.storage.stratis2.filesystem.%s" % REVISION
+MANAGER_INTERFACE = "org.storage.stratis2.Manager.%s" % REVISION
+POOL_INTERFACE = "org.storage.stratis2.pool.%s" % REVISION
+REPORT_INTERFACE = "org.storage.stratis2.Report.%s" % REVISION
diff --git a/src/stratis_cli/_actions/_data.py b/src/stratis_cli/_actions/_data.py
index fa3d99180..dcf61f2f6 100644
--- a/src/stratis_cli/_actions/_data.py
+++ b/src/stratis_cli/_actions/_data.py
@@ -15,9 +15,9 @@
 XML interface specifications.
 """
 # isort: STDLIB
+import os
 import sys
 import xml.etree.ElementTree as ET
-from os import environ
 
 # isort: FIRSTPARTY
 from dbus_client_gen import (
@@ -32,19 +32,18 @@
     BLOCKDEV_INTERFACE,
     FETCH_PROPERTIES_INTERFACE,
     FILESYSTEM_INTERFACE,
+    MANAGER_INTERFACE,
     POOL_INTERFACE,
     REPORT_INTERFACE,
 )
+from ._environment import get_timeout
 from ._introspect import SPECS
-from ._utils import get_timeout
 
 assert hasattr(sys.modules.get("stratis_cli"), "run"), (
     "This module is being loaded too eagerly. Make sure that loading it is "
     "deferred until after the stratis_cli module has been fully loaded."
 )
 
-_MANAGER_INTERFACE = "org.storage.stratis2.Manager.r3"
-
 DBUS_TIMEOUT_SECONDS = 120
 
 
@@ -52,7 +51,7 @@
     # pylint: disable=invalid-name
 
     timeout = get_timeout(
-        environ.get("STRATIS_DBUS_TIMEOUT", DBUS_TIMEOUT_SECONDS * 1000)
+        os.environ.get("STRATIS_DBUS_TIMEOUT", DBUS_TIMEOUT_SECONDS * 1000)
     )
 
     fetch_properties_spec = ET.fromstring(SPECS[FETCH_PROPERTIES_INTERFACE])
@@ -75,7 +74,7 @@
     MODev = managed_object_class("MODev", blockdev_spec)
     devs = mo_query_builder(blockdev_spec)
 
-    Manager = make_class("Manager", ET.fromstring(SPECS[_MANAGER_INTERFACE]), timeout)
+    Manager = make_class("Manager", ET.fromstring(SPECS[MANAGER_INTERFACE]), timeout)
 
     ObjectManager = make_class(
         "ObjectManager",
@@ -107,3 +106,43 @@
     raise StratisCliGenerationError(
         "Failed to generate some class needed for examining D-Bus data"
     ) from err
+
+
+def _add_abs_path_assertion(klass, method_name, key):
+    """
+    Set method_name of method_klass to a new method which checks that the
+    device paths values at key are absolute paths.
+
+    :param klass: the klass to which this metthod belongs
+    :param str method_name: the name of the method
+    :param str key: the key at which the paths can be found in the arguments
+    """
+    method_class = getattr(klass, "Methods")
+    orig_method = getattr(method_class, method_name)
+
+    def new_method(proxy, args):
+        """
+        New CreatePool method
+        """
+        rel_paths = [path for path in args[key] if not os.path.isabs(path)]
+        assert (
+            rel_paths == []
+        ), "Precondition violated: paths %s should be absolute" % ", ".join(rel_paths)
+        return orig_method(proxy, args)
+
+    setattr(method_class, method_name, new_method)
+
+
+try:
+    _add_abs_path_assertion(Manager, "CreatePool", "devices")
+    _add_abs_path_assertion(Pool, "InitCache", "devices")
+    _add_abs_path_assertion(Pool, "AddCacheDevs", "devices")
+    _add_abs_path_assertion(Pool, "AddDataDevs", "devices")
+
+except AttributeError as err:  # pragma: no cover
+    # This can only happen if the expected method is missing from the XML spec
+    # or code generation has a bug, we will never test for these conditions.
+    raise StratisCliGenerationError(
+        "Malformed class definition; could not access a class or method in "
+        "the generated class definition"
+    ) from err
diff --git a/src/stratis_cli/_actions/_environment.py b/src/stratis_cli/_actions/_environment.py
new file mode 100644
index 000000000..90481bcfd
--- /dev/null
+++ b/src/stratis_cli/_actions/_environment.py
@@ -0,0 +1,61 @@
+# Copyright 2021 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Environment functions.
+"""
+
+from .._errors import StratisCliEnvironmentError
+
+
+def get_timeout(value):
+    """
+    Turn an input str or int into a float timeout value.
+
+    :param value: the input str or int
+    :type value: str or int
+    :raises StratisCliEnvironmentError:
+    :returns: float
+    """
+
+    maximum_dbus_timeout_ms = 1073741823
+
+    # Ensure the input str is not a float
+    if isinstance(value, float):
+        raise StratisCliEnvironmentError(
+            "The timeout value provided is a float; it should be an integer."
+        )
+
+    try:
+        timeout_int = int(value)
+
+    except ValueError as err:
+        raise StratisCliEnvironmentError(
+            "The timeout value provided is not an integer."
+        ) from err
+
+    # Ensure the integer is not too small
+    if timeout_int < -1:
+        raise StratisCliEnvironmentError(
+            "The timeout value provided is smaller than the smallest acceptable value, -1."
+        )
+
+    # Ensure the integer is not too large
+    if timeout_int > maximum_dbus_timeout_ms:
+        raise StratisCliEnvironmentError(
+            "The timeout value provided exceeds the largest acceptable value, %s."
+            % maximum_dbus_timeout_ms
+        )
+
+    # Convert from milliseconds to seconds
+    return timeout_int / 1000
diff --git a/src/stratis_cli/_actions/_formatting.py b/src/stratis_cli/_actions/_formatting.py
index 44b9a3544..525159433 100644
--- a/src/stratis_cli/_actions/_formatting.py
+++ b/src/stratis_cli/_actions/_formatting.py
@@ -17,9 +17,10 @@
 
 # isort: STDLIB
 import sys
+import uuid
 
 from .._errors import StratisCliEnginePropertyError, StratisCliPropertyNotFoundError
-from ._utils import fetch_property
+from ._utils import unpack_property
 
 # If the wcwidth package is not available the wcswidth function will not
 # be available. In that case, use the standard function len where wcswidth
@@ -61,7 +62,7 @@ def get_property(props, name, to_repr, default):
     :rtype: object
     """
     try:
-        return to_repr(fetch_property(props, name))
+        return to_repr(unpack_property(props, name))
     # An exception should only be raised if the property can not be obtained.
     # This requires either running against an interface that does not support
     # the property or the engine encountering an error getting the property,
@@ -162,3 +163,10 @@ def print_table(column_headings, row_entries, alignment, file=sys.stdout):
     for row, row_widths in zip(row_entries, cell_widths):
         _print_row(file, row, row_widths, column_widths, alignment)
         print(file=file)
+
+
+def to_hyphenated(mo_uuid):
+    """
+    Convert uuid string to hyphenated format
+    """
+    return str(uuid.UUID(mo_uuid))
diff --git a/src/stratis_cli/_actions/_introspect.py b/src/stratis_cli/_actions/_introspect.py
index 101eca7f5..188a17609 100644
--- a/src/stratis_cli/_actions/_introspect.py
+++ b/src/stratis_cli/_actions/_introspect.py
@@ -2,198 +2,215 @@
     "org.freedesktop.DBus.ObjectManager": """
 <interface name="org.freedesktop.DBus.ObjectManager">
     <method name="GetManagedObjects">
-      <arg direction="out" name="objpath_interfaces_and_properties" type="a{oa{sa{sv}}}" />
+      <arg name="objpath_interfaces_and_properties" type="a{oa{sa{sv}}}" direction="out" />
     </method>
   </interface>
 """,
-    "org.storage.stratis2.FetchProperties.r3": """
-<interface name="org.storage.stratis2.FetchProperties.r3">
+    "org.storage.stratis2.FetchProperties.r4": """
+<interface name="org.storage.stratis2.FetchProperties.r4">
     <method name="GetAllProperties">
-      <arg direction="out" name="results" type="a{s(bv)}" />
+      <arg name="results" type="a{s(bv)}" direction="out" />
     </method>
     <method name="GetProperties">
-      <arg direction="in" name="properties" type="as" />
-      <arg direction="out" name="results" type="a{s(bv)}" />
+      <arg name="properties" type="as" direction="in" />
+      <arg name="results" type="a{s(bv)}" direction="out" />
     </method>
   </interface>
 """,
-    "org.storage.stratis2.Manager.r3": """
-<interface name="org.storage.stratis2.Manager.r3">
+    "org.storage.stratis2.Manager.r4": """
+<interface name="org.storage.stratis2.Manager.r4">
     <method name="ConfigureSimulator">
-      <arg direction="in" name="denominator" type="u" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="denominator" type="u" direction="in" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="CreatePool">
-      <arg direction="in" name="name" type="s" />
-      <arg direction="in" name="redundancy" type="(bq)" />
-      <arg direction="in" name="devices" type="as" />
-      <arg direction="in" name="key_desc" type="(bs)" />
-      <arg direction="out" name="result" type="(b(oao))" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="name" type="s" direction="in" />
+      <arg name="redundancy" type="(bq)" direction="in" />
+      <arg name="devices" type="as" direction="in" />
+      <arg name="key_desc" type="(bs)" direction="in" />
+      <arg name="clevis_info" type="(b(ss))" direction="in" />
+      <arg name="result" type="(b(oao))" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="DestroyPool">
-      <arg direction="in" name="pool" type="o" />
-      <arg direction="out" name="result" type="(bs)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="pool" type="o" direction="in" />
+      <arg name="result" type="(bs)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
+    </method>
+    <method name="EngineStateReport">
+      <arg name="result" type="s" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="SetKey">
-      <arg direction="in" name="key_desc" type="s" />
-      <arg direction="in" name="key_fd" type="h" />
-      <arg direction="in" name="interactive" type="b" />
-      <arg direction="out" name="result" type="(bb)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="key_desc" type="s" direction="in" />
+      <arg name="key_fd" type="h" direction="in" />
+      <arg name="interactive" type="b" direction="in" />
+      <arg name="result" type="(bb)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="UnlockPool">
-      <arg direction="in" name="pool_uuid" type="s" />
-      <arg direction="in" name="unlock_method" type="s" />
-      <arg direction="out" name="result" type="(bas)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="pool_uuid" type="s" direction="in" />
+      <arg name="unlock_method" type="s" direction="in" />
+      <arg name="result" type="(bas)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="UnsetKey">
-      <arg direction="in" name="key_desc" type="s" />
-      <arg direction="out" name="result" type="b" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="key_desc" type="s" direction="in" />
+      <arg name="result" type="b" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
-    <property access="read" name="Version" type="s">
+    <property name="Version" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
   </interface>
 """,
-    "org.storage.stratis2.Report.r1": """
-<interface name="org.storage.stratis2.Report.r1">
+    "org.storage.stratis2.Report.r4": """
+<interface name="org.storage.stratis2.Report.r4">
     <method name="GetReport">
-      <arg direction="in" name="name" type="s" />
-      <arg direction="out" name="result" type="s" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="name" type="s" direction="in" />
+      <arg name="result" type="s" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
   </interface>
 """,
-    "org.storage.stratis2.blockdev.r2": """
-<interface name="org.storage.stratis2.blockdev.r2">
+    "org.storage.stratis2.blockdev.r4": """
+<interface name="org.storage.stratis2.blockdev.r4">
     <method name="SetUserInfo">
-      <arg direction="in" name="id" type="(bs)" />
-      <arg direction="out" name="changed" type="(bs)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="id" type="(bs)" direction="in" />
+      <arg name="changed" type="(bs)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
-    <property access="read" name="Devnode" type="s">
+    <property name="Devnode" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="HardwareInfo" type="(bs)">
+    <property name="HardwareInfo" type="(bs)" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="InitializationTime" type="t">
+    <property name="InitializationTime" type="t" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="PhysicalPath" type="s">
+    <property name="PhysicalPath" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="Pool" type="o">
+    <property name="Pool" type="o" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="Tier" type="q">
+    <property name="Tier" type="q" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false" />
     </property>
-    <property access="read" name="UserInfo" type="(bs)">
+    <property name="UserInfo" type="(bs)" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false" />
     </property>
-    <property access="read" name="Uuid" type="s">
+    <property name="Uuid" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
   </interface>
 """,
-    "org.storage.stratis2.filesystem": """
-<interface name="org.storage.stratis2.filesystem">
+    "org.storage.stratis2.filesystem.r4": """
+<interface name="org.storage.stratis2.filesystem.r4">
     <method name="SetName">
-      <arg direction="in" name="name" type="s" />
-      <arg direction="out" name="result" type="(bs)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="name" type="s" direction="in" />
+      <arg name="result" type="(bs)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
-    <property access="read" name="Created" type="s">
+    <property name="Created" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="Devnode" type="s">
-      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
+    <property name="Devnode" type="s" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="invalidates" />
     </property>
-    <property access="read" name="Name" type="s" />
-    <property access="read" name="Pool" type="o">
+    <property name="Name" type="s" access="read" />
+    <property name="Pool" type="o" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="Uuid" type="s">
+    <property name="Uuid" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
   </interface>
 """,
-    "org.storage.stratis2.pool.r3": """
-<interface name="org.storage.stratis2.pool.r3">
+    "org.storage.stratis2.pool.r4": """
+<interface name="org.storage.stratis2.pool.r4">
     <method name="AddCacheDevs">
-      <arg direction="in" name="devices" type="as" />
-      <arg direction="out" name="results" type="(bao)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="devices" type="as" direction="in" />
+      <arg name="results" type="(bao)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="AddDataDevs">
-      <arg direction="in" name="devices" type="as" />
-      <arg direction="out" name="results" type="(bao)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="devices" type="as" direction="in" />
+      <arg name="results" type="(bao)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="Bind">
-      <arg direction="in" name="pin" type="s" />
-      <arg direction="in" name="json" type="s" />
-      <arg direction="out" name="results" type="b" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="pin" type="s" direction="in" />
+      <arg name="json" type="s" direction="in" />
+      <arg name="results" type="b" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
+    </method>
+    <method name="BindKeyring">
+      <arg name="key_desc" type="s" direction="in" />
+      <arg name="results" type="b" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="CreateFilesystems">
-      <arg direction="in" name="specs" type="as" />
-      <arg direction="out" name="results" type="(ba(os))" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="specs" type="as" direction="in" />
+      <arg name="results" type="(ba(os))" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="DestroyFilesystems">
-      <arg direction="in" name="filesystems" type="ao" />
-      <arg direction="out" name="results" type="(bas)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="filesystems" type="ao" direction="in" />
+      <arg name="results" type="(bas)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="InitCache">
-      <arg direction="in" name="devices" type="as" />
-      <arg direction="out" name="results" type="(bao)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="devices" type="as" direction="in" />
+      <arg name="results" type="(bao)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="SetName">
-      <arg direction="in" name="name" type="s" />
-      <arg direction="out" name="result" type="(bs)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="name" type="s" direction="in" />
+      <arg name="result" type="(bs)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="SnapshotFilesystem">
-      <arg direction="in" name="origin" type="o" />
-      <arg direction="in" name="snapshot_name" type="s" />
-      <arg direction="out" name="result" type="(bo)" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="origin" type="o" direction="in" />
+      <arg name="snapshot_name" type="s" direction="in" />
+      <arg name="result" type="(bo)" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
     <method name="Unbind">
-      <arg direction="out" name="results" type="b" />
-      <arg direction="out" name="return_code" type="q" />
-      <arg direction="out" name="return_string" type="s" />
+      <arg name="results" type="b" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
+    </method>
+    <method name="UnbindKeyring">
+      <arg name="results" type="b" direction="out" />
+      <arg name="return_code" type="q" direction="out" />
+      <arg name="return_string" type="s" direction="out" />
     </method>
-    <property access="read" name="Encrypted" type="b">
+    <property name="Encrypted" type="b" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
-    <property access="read" name="Name" type="s" />
-    <property access="read" name="Uuid" type="s">
+    <property name="Name" type="s" access="read" />
+    <property name="Uuid" type="s" access="read">
       <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
     </property>
   </interface>
diff --git a/src/stratis_cli/_actions/_logical.py b/src/stratis_cli/_actions/_logical.py
index ba095e468..73e36fc3a 100644
--- a/src/stratis_cli/_actions/_logical.py
+++ b/src/stratis_cli/_actions/_logical.py
@@ -28,7 +28,7 @@
 from .._stratisd_constants import StratisdErrors
 from ._connection import get_object
 from ._constants import TOP_OBJECT
-from ._formatting import TABLE_FAILURE_STRING, get_property, print_table
+from ._formatting import TABLE_FAILURE_STRING, get_property, print_table, to_hyphenated
 
 
 class LogicalActions:
@@ -158,6 +158,10 @@ def filesystem_used(props):
                 props, "Used", lambda x: str(Range(x)), TABLE_FAILURE_STRING
             )
 
+        format_uuid = (
+            (lambda mo_uuid: mo_uuid) if namespace.unhyphenated_uuids else to_hyphenated
+        )
+
         tables = [
             (
                 path_to_name[mofilesystem.Pool()],
@@ -167,7 +171,7 @@ def filesystem_used(props):
                 .astimezone()
                 .strftime("%b %d %Y %H:%M"),
                 mofilesystem.Devnode(),
-                mofilesystem.Uuid(),
+                format_uuid(mofilesystem.Uuid()),
             )
             for props, mofilesystem in filesystems_with_props
         ]
diff --git a/src/stratis_cli/_actions/_pool.py b/src/stratis_cli/_actions/_pool.py
new file mode 100644
index 000000000..278835dfc
--- /dev/null
+++ b/src/stratis_cli/_actions/_pool.py
@@ -0,0 +1,659 @@
+# Copyright 2021 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Pool actions.
+"""
+
+# isort: STDLIB
+import os
+from collections import defaultdict
+
+# isort: THIRDPARTY
+from justbytes import Range
+
+from .._errors import (
+    StratisCliAggregateError,
+    StratisCliEngineError,
+    StratisCliIncoherenceError,
+    StratisCliInUseOtherTierError,
+    StratisCliInUseSameTierError,
+    StratisCliNameConflictError,
+    StratisCliNoChangeError,
+    StratisCliPartialChangeError,
+    StratisCliPartialFailureError,
+)
+from .._stratisd_constants import BlockDevTiers, EncryptionMethod, StratisdErrors
+from ._connection import get_object
+from ._constants import TOP_OBJECT
+from ._formatting import TABLE_FAILURE_STRING, get_property, print_table, to_hyphenated
+from ._utils import fetch_property, get_clevis_info
+
+
+def _generate_pools_to_blockdevs(managed_objects, to_be_added, tier):
+    """
+    Generate a map of pools to which block devices they own
+    :param managed_objects: the result of a GetManagedObjects call
+    :type managed_objects: dict of str * dict
+    :param to_be_added: the blockdevs to be added
+    :type to_be_added: frozenset of str
+    :param tier: tier to search for blockdevs to be added
+    :type tier: _stratisd_constants.BlockDevTiers
+    :returns: a map of pool names to sets of strings containing blockdevs they own
+    :rtype: dict of str * frozenset of str
+    """
+    # pylint: disable=import-outside-toplevel
+    from ._data import MODev
+    from ._data import MOPool
+    from ._data import devs
+    from ._data import pools
+
+    pool_map = dict(
+        (path, str(MOPool(info).Name()))
+        for (path, info) in pools().search(managed_objects)
+    )
+
+    pools_to_blockdevs = defaultdict(list)
+    for modev in (
+        # pylint: disable=bad-continuation
+        modev
+        for modev in (
+            MODev(info)
+            for (_, info) in devs(props={"Tier": tier}).search(managed_objects)
+        )
+        if str(modev.Devnode()) in to_be_added
+    ):
+        pools_to_blockdevs[pool_map[modev.Pool()]].append(str(modev.Devnode()))
+
+    return dict(
+        (pool, frozenset(blockdevs)) for pool, blockdevs in pools_to_blockdevs.items()
+    )
+
+
+def _check_opposite_tier(managed_objects, to_be_added, other_tier):
+    """
+    Check whether specified blockdevs are already in the other tier.
+
+    :param managed_objects: the result of a GetManagedObjects call
+    :type managed_objects: dict of str * dict
+    :param to_be_added: the blockdevs to be added
+    :type to_be_added: frozenset of str
+    :param other_tier: the other tier, not the one requested
+    :type other_tier: _stratisd_constants.BlockDevTiers
+    :raises StratisCliInUseOtherTierError: if blockdevs are used by other tier
+    """
+    pools_to_blockdevs = _generate_pools_to_blockdevs(
+        managed_objects, to_be_added, other_tier
+    )
+
+    if pools_to_blockdevs != {}:
+        raise StratisCliInUseOtherTierError(
+            pools_to_blockdevs,
+            BlockDevTiers.Data
+            if other_tier == BlockDevTiers.Cache
+            else BlockDevTiers.Cache,
+        )
+
+
+def _check_same_tier(pool_name, managed_objects, to_be_added, this_tier):
+    """
+    Check whether specified blockdevs are already in the tier to which they
+    are to be added.
+
+    :param managed_objects: the result of a GetManagedObjects call
+    :type managed_objects: dict of str * dict
+    :param to_be_added: the blockdevs to be added
+    :type to_be_added: frozenset of str
+    :param this_tier: the tier requested
+    :type this_tier: _stratisd_constants.BlockDevTiers
+    :raises StratisCliPartialChangeError: if blockdevs are used by this tier
+    :raises StratisCliInUseSameTierError: if blockdevs are used by this tier in another pool
+    """
+    pools_to_blockdevs = _generate_pools_to_blockdevs(
+        managed_objects, to_be_added, this_tier
+    )
+
+    owned_by_current_pool = frozenset(pools_to_blockdevs.get(pool_name, []))
+    owned_by_other_pools = dict(
+        (pool, devnodes)
+        for pool, devnodes in pools_to_blockdevs.items()
+        if pool_name != pool
+    )
+
+    if owned_by_current_pool != frozenset():
+        raise StratisCliPartialChangeError(
+            "add to cache" if this_tier == BlockDevTiers.Cache else "add to data",
+            to_be_added.difference(owned_by_current_pool),
+            to_be_added.intersection(owned_by_current_pool),
+        )
+    if owned_by_other_pools != {}:
+        raise StratisCliInUseSameTierError(owned_by_other_pools, this_tier)
+
+
+def _fetch_locked_pools_property(proxy):
+    """
+    Fetch the LockedPools property from stratisd.
+    :param proxy: proxy to the top object in stratisd
+    :return: list of pool UUIDs as strings
+    :rtype: list of str
+    :raises StratisCliPropertyNotFoundError:
+    :raises StratisCliEnginePropertyError:
+    """
+    return fetch_property(proxy, "LockedPools")
+
+
+class PoolActions:
+    """
+    Pool actions.
+    """
+
+    @staticmethod
+    def create_pool(namespace):
+        """
+        Create a stratis pool.
+
+        :raises StratisCliEngineError:
+        :raises StratisCliIncoherenceError:
+        :raises StratisCliNameConflictError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import Manager
+        from ._data import ObjectManager
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+        pool_name = namespace.pool_name
+        names = pools(props={"Name": pool_name}).search(managed_objects)
+        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
+        if list(names) != []:
+            raise StratisCliNameConflictError("pool", pool_name)
+
+        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Cache)
+
+        _check_same_tier(pool_name, managed_objects, blockdevs, BlockDevTiers.Data)
+
+        clevis_info = get_clevis_info(namespace)
+
+        ((changed, (_, _)), return_code, message) = Manager.Methods.CreatePool(
+            proxy,
+            {
+                "name": pool_name,
+                "redundancy": (True, 0),
+                "devices": blockdevs,
+                "key_desc": (
+                    (True, namespace.key_desc)
+                    if namespace.key_desc is not None
+                    else (False, "")
+                ),
+                # pylint: disable=bad-continuation
+                "clevis_info": (False, ("", ""))
+                if clevis_info is None
+                else (True, clevis_info),
+            },
+        )
+
+        if return_code != StratisdErrors.OK:  # pragma: no cover
+            raise StratisCliEngineError(return_code, message)
+
+        if not changed:  # pragma: no cover
+            raise StratisCliIncoherenceError(
+                (
+                    "Expected to create the specified pool %s but stratisd "
+                    "reports that it did not actually create the pool"
+                )
+                % pool_name
+            )
+
+    @staticmethod
+    def init_cache(namespace):  # pylint: disable=too-many-locals
+        """
+        Initialize the cache of an existing stratis pool.
+
+        :raises StratisCliEngineError:
+        :raises StratisCliIncoherenceError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import MODev
+        from ._data import ObjectManager
+        from ._data import Pool
+        from ._data import devs
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+        pool_name = namespace.pool_name
+        (pool_object_path, _) = next(
+            pools(props={"Name": pool_name})
+            .require_unique_match(True)
+            .search(managed_objects)
+        )
+        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
+
+        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Data)
+
+        _check_same_tier(pool_name, managed_objects, blockdevs, BlockDevTiers.Cache)
+
+        ((changed, devs_added), return_code, message) = Pool.Methods.InitCache(
+            get_object(pool_object_path), {"devices": blockdevs}
+        )
+
+        if return_code != StratisdErrors.OK:
+            raise StratisCliEngineError(return_code, message)
+
+        if not changed or len(devs_added) < len(blockdevs):  # pragma: no cover
+            devnodes_added = [
+                MODev(info).Devnode()
+                for (object_path, info) in devs(
+                    props={"Pool": pool_object_path}
+                ).search(ObjectManager.Methods.GetManagedObjects(proxy, {}))
+                if object_path in devs_added
+            ]
+            raise StratisCliIncoherenceError(
+                (
+                    "Expected to add the specified blockdevs as cache "
+                    "to pool %s but stratisd reports that it did not actually "
+                    "add some or all of the blockdevs requested; devices "
+                    "added: (%s), devices requested: (%s)"
+                )
+                % (namespace.pool_name, ", ".join(devnodes_added), ", ".join(blockdevs))
+            )
+
+    @staticmethod
+    def list_pools(namespace):
+        """
+        List all stratis pools.
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import FetchProperties
+        from ._data import MOPool
+        from ._data import ObjectManager
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+        pools_with_props = [
+            (
+                FetchProperties.Methods.GetAllProperties(get_object(objpath), {}),
+                MOPool(info),
+            )
+            for objpath, info in pools().search(managed_objects)
+        ]
+
+        def physical_size_triple(props):
+            """
+            Calculate the triple to display for total physical size.
+
+            The format is total/used/free where the display value for each
+            member of the tuple are chosen automatically according to justbytes'
+            configuration.
+
+            :param props: a dictionary of property values obtained
+            :type props: dict of str * object
+            :returns: a string to display in the resulting list output
+            :rtype: str
+            """
+            total_physical_size = get_property(props, "TotalPhysicalSize", Range, None)
+
+            total_physical_used = get_property(props, "TotalPhysicalUsed", Range, None)
+
+            total_physical_free = (
+                None
+                if total_physical_size is None or total_physical_used is None
+                else total_physical_size - total_physical_used
+            )
+
+            return "%s / %s / %s" % (
+                TABLE_FAILURE_STRING
+                if total_physical_size is None
+                else total_physical_size,
+                TABLE_FAILURE_STRING
+                if total_physical_used is None
+                else total_physical_used,
+                TABLE_FAILURE_STRING
+                if total_physical_free is None
+                else total_physical_free,
+            )
+
+        def properties_string(mopool, props_map):
+            """
+            Make a string encoding some important properties of the pool
+
+            :param mopool: an object representing all the properties of the pool
+            :type mopool: MOPool
+            :param props_map: a map of properties returned by GetAllProperties
+            :type props_map: dict of str * any
+            """
+
+            def gen_string(has_property, code):
+                """
+                Generate the display string for a boolean property
+
+                :param has_property: whether the property is true or false
+                :type has_property: bool or NoneType
+                :param str code: the code to generate the string for
+                :returns: the generated string
+                :rtype: str
+                """
+                if has_property == True:  # pylint: disable=singleton-comparison
+                    prefix = " "
+                elif has_property == False:  # pylint: disable=singleton-comparison
+                    prefix = "~"
+                # This is only going to occur if the engine experiences an
+                # error while calculating a property or if our code has a bug.
+                else:  # pragma: no cover
+                    prefix = "?"
+                return prefix + code
+
+            prop_list = []
+            prop_list.append(
+                gen_string(get_property(props_map, "HasCache", lambda x: x, None), "Ca")
+            )
+            prop_list.append(gen_string(mopool.Encrypted(), "Cr"))
+            return ",".join(prop_list)
+
+        format_uuid = (
+            (lambda mo_uuid: mo_uuid) if namespace.unhyphenated_uuids else to_hyphenated
+        )
+
+        tables = [
+            (
+                mopool.Name(),
+                physical_size_triple(props),
+                properties_string(mopool, props),
+                format_uuid(mopool.Uuid()),
+            )
+            for props, mopool in pools_with_props
+        ]
+
+        print_table(
+            ["Name", "Total Physical", "Properties", "UUID"],
+            sorted(tables, key=lambda entry: entry[0]),
+            ["<", ">", ">", ">"],
+        )
+
+    @staticmethod
+    def destroy_pool(namespace):
+        """
+        Destroy a stratis pool.
+
+        If no pool exists, the method succeeds.
+
+        :raises StratisCliEngineError:
+        :raises StratisCliIncoherenceError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import Manager
+        from ._data import ObjectManager
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+        (pool_object_path, _) = next(
+            pools(props={"Name": namespace.pool_name})
+            .require_unique_match(True)
+            .search(managed_objects)
+        )
+
+        ((changed, _), return_code, message) = Manager.Methods.DestroyPool(
+            proxy, {"pool": pool_object_path}
+        )
+
+        # This branch can be covered, since the engine will return an error
+        # if the pool can not be destroyed because it has filesystems.
+        if return_code != StratisdErrors.OK:
+            raise StratisCliEngineError(return_code, message)
+
+        if not changed:  # pragma: no cover
+            raise StratisCliIncoherenceError(
+                (
+                    "Expected to destroy the specified pool %s but "
+                    "stratisd reports that it did not actually "
+                    "destroy the pool requested"
+                )
+                % namespace.pool_name
+            )
+
+    @staticmethod
+    def rename_pool(namespace):
+        """
+        Rename a pool.
+
+        :raises StratisCliEngineError:
+        :raises StratisCliNoChangeError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import ObjectManager
+        from ._data import Pool
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+        (pool_object_path, _) = next(
+            pools(props={"Name": namespace.current})
+            .require_unique_match(True)
+            .search(managed_objects)
+        )
+
+        ((changed, _), return_code, message) = Pool.Methods.SetName(
+            get_object(pool_object_path), {"name": namespace.new}
+        )
+
+        if return_code != StratisdErrors.OK:  # pragma: no cover
+            raise StratisCliEngineError(return_code, message)
+
+        if not changed:
+            raise StratisCliNoChangeError("rename", namespace.new)
+
+    @staticmethod
+    def add_data_devices(namespace):  # pylint: disable=too-many-locals
+        """
+        Add specified data devices to a pool.
+
+        :raises StratisCliEngineError:
+        :raises StratisCliIncoherenceError:
+        :raises StratisCliInUseOtherTierError:
+        :raises StratisCliInUseSameTierError:
+        :raises StratisCliPartialChangeError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import MODev
+        from ._data import ObjectManager
+        from ._data import Pool
+        from ._data import devs
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+
+        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
+
+        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Cache)
+
+        _check_same_tier(
+            namespace.pool_name, managed_objects, blockdevs, BlockDevTiers.Data
+        )
+
+        (pool_object_path, _) = next(
+            pools(props={"Name": namespace.pool_name})
+            .require_unique_match(True)
+            .search(managed_objects)
+        )
+
+        ((added, devs_added), return_code, message) = Pool.Methods.AddDataDevs(
+            get_object(pool_object_path), {"devices": list(blockdevs)}
+        )
+        if return_code != StratisdErrors.OK:  # pragma: no cover
+            raise StratisCliEngineError(return_code, message)
+
+        if not added or len(devs_added) < len(blockdevs):  # pragma: no cover
+            devnodes_added = [
+                MODev(info).Devnode()
+                for (object_path, info) in devs(
+                    props={"Pool": pool_object_path}
+                ).search(ObjectManager.Methods.GetManagedObjects(proxy, {}))
+                if object_path in devs_added
+            ]
+            raise StratisCliIncoherenceError(
+                (
+                    "Expected to add the specified blockdevs to the data tier "
+                    "in pool %s but stratisd reports that it did not actually "
+                    "add some or all of the blockdevs requested; devices "
+                    "added: (%s), devices requested: (%s)"
+                )
+                % (namespace.pool_name, ", ".join(devnodes_added), ", ".join(blockdevs))
+            )
+
+    @staticmethod
+    def add_cache_devices(namespace):  # pylint: disable=too-many-locals
+        """
+        Add specified cache devices to a pool.
+
+        :raises StratisCliEngineError:
+        :raises StratisCliIncoherenceError:
+        :raises StratisCliInUseOtherTierError:
+        :raises StratisCliInUseSameTierError:
+        :raises StratisCliPartialChangeError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import MODev
+        from ._data import ObjectManager
+        from ._data import Pool
+        from ._data import devs
+        from ._data import pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+
+        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
+
+        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Data)
+
+        _check_same_tier(
+            namespace.pool_name, managed_objects, blockdevs, BlockDevTiers.Cache
+        )
+
+        (pool_object_path, _) = next(
+            pools(props={"Name": namespace.pool_name})
+            .require_unique_match(True)
+            .search(managed_objects)
+        )
+
+        ((added, devs_added), return_code, message) = Pool.Methods.AddCacheDevs(
+            get_object(pool_object_path), {"devices": list(blockdevs)}
+        )
+        if return_code != StratisdErrors.OK:
+            raise StratisCliEngineError(return_code, message)
+
+        if not added or len(devs_added) < len(blockdevs):  # pragma: no cover
+            devnodes_added = [
+                MODev(info).Devnode()
+                for (object_path, info) in devs(
+                    props={"Pool": pool_object_path}
+                ).search(ObjectManager.Methods.GetManagedObjects(proxy, {}))
+                if object_path in devs_added
+            ]
+            raise StratisCliIncoherenceError(
+                (
+                    "Expected to add the specified blockdevs to the cache tier "
+                    "in pool %s but stratisd reports that it did not actually "
+                    "add some or all of the blockdevs requested; devices "
+                    "added: (%s), devices requested: (%s)"
+                )
+                % (namespace.pool_name, ", ".join(devnodes_added), ", ".join(blockdevs))
+            )
+
+    @staticmethod
+    def unbind(namespace):
+        """
+        Unbind all devices in an encrypted pool.
+
+        :raises StratisCliNoChangeError:
+        :raises StratisCliEngineError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import ObjectManager, Pool, pools
+
+        proxy = get_object(TOP_OBJECT)
+        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
+        pool_name = namespace.pool_name
+        (pool_object_path, _) = next(
+            pools(props={"Name": pool_name})
+            .require_unique_match(True)
+            .search(managed_objects)
+        )
+
+        unbind_method = (
+            Pool.Methods.Unbind
+            if namespace.method == str(EncryptionMethod.CLEVIS)
+            else Pool.Methods.UnbindKeyring
+        )
+
+        (changed, return_code, return_msg) = unbind_method(
+            get_object(pool_object_path), {}
+        )
+
+        if return_code != StratisdErrors.OK:
+            raise StratisCliEngineError(return_code, return_msg)
+
+        if not changed:
+            raise StratisCliNoChangeError("unbind", pool_name)
+
+    @staticmethod
+    def unlock_pools(namespace):
+        """
+        Unlock all of the encrypted pools that have been detected by the daemon
+        but are still locked.
+        :raises StratisCliIncoherenceError:
+        :raises StratisCliNoChangeError:
+        :raises StratisCliAggregateError:
+        """
+        # pylint: disable=import-outside-toplevel
+        from ._data import Manager
+
+        proxy = get_object(TOP_OBJECT)
+
+        locked_pools = _fetch_locked_pools_property(proxy)
+        if locked_pools == {}:  # pragma: no cover
+            raise StratisCliNoChangeError("unlock", "pools")
+
+        # This block is not covered as the sim engine does not simulate the
+        # management of unlocked devices, so locked_pools is always empty.
+        errors = []  # pragma: no cover
+        for uuid in locked_pools:  # pragma: no cover
+            (
+                (is_some, unlocked_devices),
+                return_code,
+                message,
+            ) = Manager.Methods.UnlockPool(
+                proxy, {"pool_uuid": uuid, "unlock_method": namespace.unlock_method}
+            )
+
+            if return_code != StratisdErrors.OK:
+                errors.append(
+                    StratisCliPartialFailureError(
+                        "unlock", "pool with UUID %s" % uuid, error_message=message
+                    )
+                )
+
+            if is_some and unlocked_devices == []:
+                raise StratisCliIncoherenceError(
+                    (
+                        "stratisd reported that some existing devices are locked but "
+                        "no new devices were unlocked during this operation"
+                    )
+                )
+
+        if errors != []:  # pragma: no cover
+            raise StratisCliAggregateError("unlock", "pool", errors)
diff --git a/src/stratis_cli/_actions/_top.py b/src/stratis_cli/_actions/_top.py
index 7446b0b17..5cd922b85 100644
--- a/src/stratis_cli/_actions/_top.py
+++ b/src/stratis_cli/_actions/_top.py
@@ -18,132 +18,30 @@
 # isort: STDLIB
 import json
 import os
-from collections import defaultdict
 from getpass import getpass
 
-# isort: THIRDPARTY
-from justbytes import Range
-
 from .._errors import (
-    StratisCliAggregateError,
     StratisCliEngineError,
     StratisCliIncoherenceError,
-    StratisCliInUseOtherTierError,
-    StratisCliInUseSameTierError,
     StratisCliKeyfileNotFoundError,
     StratisCliNameConflictError,
     StratisCliNoChangeError,
-    StratisCliPartialChangeError,
-    StratisCliPartialFailureError,
     StratisCliResourceNotFoundError,
 )
-from .._stratisd_constants import CLEVIS_TANG_TRUST_URL, BlockDevTiers, StratisdErrors
+from .._stratisd_constants import (
+    CLEVIS_KEY_TANG_TRUST_URL,
+    CLEVIS_KEY_THP,
+    CLEVIS_KEY_URL,
+    CLEVIS_PIN_TANG,
+    CLEVIS_PIN_TPM2,
+    StratisdErrors,
+)
 from ._connection import get_object
 from ._constants import TOP_OBJECT
-from ._formatting import TABLE_FAILURE_STRING, get_property, print_table
+from ._formatting import print_table
 from ._utils import fetch_property
 
 
-def _generate_pools_to_blockdevs(managed_objects, to_be_added, tier):
-    """
-    Generate a map of pools to which block devices they own
-    :param managed_objects: the result of a GetManagedObjects call
-    :type managed_objects: dict of str * dict
-    :param to_be_added: the blockdevs to be added
-    :type to_be_added: frozenset of str
-    :param tier: tier to search for blockdevs to be added
-    :type tier: _stratisd_constants.BlockDevTiers
-    :returns: a map of pool names to sets of strings containing blockdevs they own
-    :rtype: dict of str * frozenset of str
-    """
-    # pylint: disable=import-outside-toplevel
-    from ._data import MODev
-    from ._data import MOPool
-    from ._data import devs
-    from ._data import pools
-
-    pool_map = dict(
-        (path, str(MOPool(info).Name()))
-        for (path, info) in pools().search(managed_objects)
-    )
-
-    pools_to_blockdevs = defaultdict(list)
-    for modev in (
-        # pylint: disable=bad-continuation
-        modev
-        for modev in (
-            MODev(info)
-            for (_, info) in devs(props={"Tier": tier}).search(managed_objects)
-        )
-        if str(modev.Devnode()) in to_be_added
-    ):
-        pools_to_blockdevs[pool_map[modev.Pool()]].append(str(modev.Devnode()))
-
-    return dict(
-        (pool, frozenset(blockdevs)) for pool, blockdevs in pools_to_blockdevs.items()
-    )
-
-
-def _check_opposite_tier(managed_objects, to_be_added, other_tier):
-    """
-    Check whether specified blockdevs are already in the other tier.
-
-    :param managed_objects: the result of a GetManagedObjects call
-    :type managed_objects: dict of str * dict
-    :param to_be_added: the blockdevs to be added
-    :type to_be_added: frozenset of str
-    :param other_tier: the other tier, not the one requested
-    :type other_tier: _stratisd_constants.BlockDevTiers
-    :raises StratisCliInUseOtherTierError: if blockdevs are used by other tier
-    """
-    pools_to_blockdevs = _generate_pools_to_blockdevs(
-        managed_objects, to_be_added, other_tier
-    )
-
-    if pools_to_blockdevs != {}:
-        raise StratisCliInUseOtherTierError(
-            pools_to_blockdevs,
-            BlockDevTiers.Data
-            if other_tier == BlockDevTiers.Cache
-            else BlockDevTiers.Cache,
-        )
-
-
-def _check_same_tier(pool_name, managed_objects, to_be_added, this_tier):
-    """
-    Check whether specified blockdevs are already in the tier to which they
-    are to be added.
-
-    :param managed_objects: the result of a GetManagedObjects call
-    :type managed_objects: dict of str * dict
-    :param to_be_added: the blockdevs to be added
-    :type to_be_added: frozenset of str
-    :param this_tier: the tier requested
-    :type this_tier: _stratisd_constants.BlockDevTiers
-    :raises StratisCliPartialChangeError: if blockdevs are used by this tier
-    :raises StratisCliInUseSameTierError: if blockdevs are used by this tier in another pool
-    """
-    pools_to_blockdevs = _generate_pools_to_blockdevs(
-        managed_objects, to_be_added, this_tier
-    )
-
-    owned_by_current_pool = frozenset(pools_to_blockdevs.get(pool_name, []))
-    owned_by_other_pools = dict(
-        (pool, devnodes)
-        for pool, devnodes in pools_to_blockdevs.items()
-        if pool_name != pool
-    )
-
-    if owned_by_current_pool != frozenset():
-        raise StratisCliPartialChangeError(
-            "add to cache" if this_tier == BlockDevTiers.Cache else "add to data",
-            to_be_added.difference(owned_by_current_pool),
-            to_be_added.intersection(owned_by_current_pool),
-        )
-    if owned_by_other_pools != {}:
-        raise StratisCliInUseSameTierError(owned_by_other_pools, this_tier)
-
-
 def _fetch_keylist_property(proxy):
     """
     Fetch the KeyList property from stratisd.
@@ -153,37 +51,7 @@ def _fetch_keylist_property(proxy):
     :raises StratisCliPropertyNotFoundError:
     :raises StratisCliEnginePropertyError:
     """
-    return _fetch_property(proxy, "KeyList")
-
-
-def _fetch_locked_pools_property(proxy):
-    """
-    Fetch the LockedPools property from stratisd.
-    :param proxy: proxy to the top object in stratisd
-    :return: list of pool UUIDs as strings
-    :rtype: list of str
-    :raises StratisCliPropertyNotFoundError:
-    :raises StratisCliEnginePropertyError:
-    """
-    return _fetch_property(proxy, "LockedPools")
-
-
-def _fetch_property(proxy, property_name):
-    """
-    Fetch a property from stratisd.
-    :param proxy: proxy to the top object in stratisd
-    :param str property_name: name of the property to fetch
-    :return: value associated with the requested property name
-    :raises StratisCliPropertyNotFoundError:
-    :raises StratisCliEnginePropertyError:
-    """
-    # pylint: disable=import-outside-toplevel
-    from ._data import FetchProperties
-
-    properties = FetchProperties.Methods.GetProperties(
-        proxy, {"properties": [property_name]}
-    )
-    return fetch_property(properties, property_name)
+    return fetch_property(proxy, "KeyList")
 
 
 def _add_update_key(proxy, key_desc, capture_key, *, keyfile_path):
@@ -221,7 +89,8 @@ def _add_update_key(proxy, key_desc, capture_key, *, keyfile_path):
         fd_is_pipe = False
 
     add_ret = Manager.Methods.SetKey(
-        proxy, {"key_desc": key_desc, "key_fd": file_desc, "interactive": False},
+        proxy,
+        {"key_desc": key_desc, "key_fd": file_desc, "interactive": False},
     )
 
     if fd_is_pipe:  # pragma: no cover
@@ -238,428 +107,36 @@ class TopActions:
     """
 
     @staticmethod
-    def create_pool(namespace):
-        """
-        Create a stratis pool.
-
-        :raises StratisCliEngineError:
-        :raises StratisCliIncoherenceError:
-        :raises StratisCliNameConflictError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import Manager
-        from ._data import ObjectManager
-        from ._data import pools
-
-        proxy = get_object(TOP_OBJECT)
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-        pool_name = namespace.pool_name
-        names = pools(props={"Name": pool_name}).search(managed_objects)
-        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
-        if list(names) != []:
-            raise StratisCliNameConflictError("pool", pool_name)
-
-        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Cache)
-
-        _check_same_tier(pool_name, managed_objects, blockdevs, BlockDevTiers.Data)
-
-        ((changed, (_, _)), return_code, message) = Manager.Methods.CreatePool(
-            proxy,
-            {
-                "name": pool_name,
-                "redundancy": (True, 0),
-                "devices": blockdevs,
-                "key_desc": (
-                    (True, namespace.key_desc)
-                    if namespace.key_desc is not None
-                    else (False, "")
-                ),
-            },
-        )
-
-        if return_code != StratisdErrors.OK:  # pragma: no cover
-            raise StratisCliEngineError(return_code, message)
-
-        if not changed:  # pragma: no cover
-            raise StratisCliIncoherenceError(
-                (
-                    "Expected to create the specified pool %s but stratisd "
-                    "reports that it did not actually create the pool"
-                )
-                % pool_name
-            )
-
-    @staticmethod
-    def init_cache(namespace):  # pylint: disable=too-many-locals
+    def get_report(namespace):
         """
-        Initialize the cache of an existing stratis pool.
+        Get the requested report from stratisd.
 
         :raises StratisCliEngineError:
-        :raises StratisCliIncoherenceError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import MODev
-        from ._data import ObjectManager
-        from ._data import Pool
-        from ._data import devs
-        from ._data import pools
-
-        proxy = get_object(TOP_OBJECT)
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-        pool_name = namespace.pool_name
-        (pool_object_path, _) = next(
-            pools(props={"Name": pool_name})
-            .require_unique_match(True)
-            .search(managed_objects)
-        )
-        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
-
-        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Data)
-
-        _check_same_tier(pool_name, managed_objects, blockdevs, BlockDevTiers.Cache)
-
-        ((changed, devs_added), return_code, message) = Pool.Methods.InitCache(
-            get_object(pool_object_path), {"devices": namespace.blockdevs}
-        )
-
-        if return_code != StratisdErrors.OK:
-            raise StratisCliEngineError(return_code, message)
-
-        if not changed or len(devs_added) < len(blockdevs):  # pragma: no cover
-            devnodes_added = [
-                MODev(info).Devnode()
-                for (object_path, info) in devs(
-                    props={"Pool": pool_object_path}
-                ).search(ObjectManager.Methods.GetManagedObjects(proxy, {}))
-                if object_path in devs_added
-            ]
-            raise StratisCliIncoherenceError(
-                (
-                    "Expected to add the specified blockdevs as cache "
-                    "to pool %s but stratisd reports that it did not actually "
-                    "add some or all of the blockdevs requested; devices "
-                    "added: (%s), devices requested: (%s)"
-                )
-                % (namespace.pool_name, ", ".join(devnodes_added), ", ".join(blockdevs))
-            )
-
-    @staticmethod
-    def list_pools(_):
-        """
-        List all stratis pools.
         """
         # pylint: disable=import-outside-toplevel
-        from ._data import FetchProperties
-        from ._data import MOPool
-        from ._data import ObjectManager
-        from ._data import pools
+        if namespace.report_name == "engine_state_report":
 
-        proxy = get_object(TOP_OBJECT)
-
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-        pools_with_props = [
-            (
-                FetchProperties.Methods.GetAllProperties(get_object(objpath), {}),
-                MOPool(info),
-            )
-            for objpath, info in pools().search(managed_objects)
-        ]
-
-        def physical_size_triple(props):
-            """
-            Calculate the triple to display for total physical size.
-
-            The format is total/used/free where the display value for each
-            member of the tuple are chosen automatically according to justbytes'
-            configuration.
-
-            :param props: a dictionary of property values obtained
-            :type props: dict of str * object
-            :returns: a string to display in the resulting list output
-            :rtype: str
-            """
-            total_physical_size = get_property(props, "TotalPhysicalSize", Range, None)
-
-            total_physical_used = get_property(props, "TotalPhysicalUsed", Range, None)
-
-            total_physical_free = (
-                None
-                if total_physical_size is None or total_physical_used is None
-                else total_physical_size - total_physical_used
-            )
-
-            return "%s / %s / %s" % (
-                TABLE_FAILURE_STRING
-                if total_physical_size is None
-                else total_physical_size,
-                TABLE_FAILURE_STRING
-                if total_physical_used is None
-                else total_physical_used,
-                TABLE_FAILURE_STRING
-                if total_physical_free is None
-                else total_physical_free,
-            )
+            from ._data import Manager
 
-        def properties_string(mopool, props_map):
-            """
-            Make a string encoding some important properties of the pool
-
-            :param mopool: an object representing all the properties of the pool
-            :type mopool: MOPool
-            :param props_map: a map of properties returned by GetAllProperties
-            :type props_map: dict of str * any
-            """
-
-            def gen_string(has_property, code):
-                """
-                Generate the display string for a boolean property
-
-                :param has_property: whether the property is true or false
-                :type has_property: bool or NoneType
-                :param str code: the code to generate the string for
-                :returns: the generated string
-                :rtype: str
-                """
-                if has_property == True:  # pylint: disable=singleton-comparison
-                    prefix = " "
-                elif has_property == False:  # pylint: disable=singleton-comparison
-                    prefix = "~"
-                # This is only going to occur if the engine experiences an
-                # error while calculating a property or if our code has a bug.
-                else:  # pragma: no cover
-                    prefix = "?"
-                return prefix + code
-
-            prop_list = []
-            prop_list.append(
-                gen_string(get_property(props_map, "HasCache", lambda x: x, None), "Ca")
-            )
-            prop_list.append(gen_string(mopool.Encrypted(), "Cr"))
-            return ",".join(prop_list)
-
-        tables = [
-            (
-                mopool.Name(),
-                physical_size_triple(props),
-                properties_string(mopool, props),
+            (report, return_code, message) = Manager.Methods.EngineStateReport(
+                get_object(TOP_OBJECT), {}
             )
-            for props, mopool in pools_with_props
-        ]
-
-        print_table(
-            ["Name", "Total Physical", "Properties"],
-            sorted(tables, key=lambda entry: entry[0]),
-            ["<", ">", ">"],
-        )
-
-    @staticmethod
-    def destroy_pool(namespace):
-        """
-        Destroy a stratis pool.
-
-        If no pool exists, the method succeeds.
-
-        :raises StratisCliEngineError:
-        :raises StratisCliIncoherenceError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import Manager
-        from ._data import ObjectManager
-        from ._data import pools
-
-        proxy = get_object(TOP_OBJECT)
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-        (pool_object_path, _) = next(
-            pools(props={"Name": namespace.pool_name})
-            .require_unique_match(True)
-            .search(managed_objects)
-        )
 
-        ((changed, _), return_code, message) = Manager.Methods.DestroyPool(
-            proxy, {"pool": pool_object_path}
-        )
+        else:
 
-        # This branch can be covered, since the engine will return an error
-        # if the pool can not be destroyed because it has filesystems.
-        if return_code != StratisdErrors.OK:
-            raise StratisCliEngineError(return_code, message)
+            from ._data import Report
 
-        if not changed:  # pragma: no cover
-            raise StratisCliIncoherenceError(
-                (
-                    "Expected to destroy the specified pool %s but "
-                    "stratisd reports that it did not actually "
-                    "destroy the pool requested"
-                )
-                % namespace.pool_name
+            (report, return_code, message) = Report.Methods.GetReport(
+                get_object(TOP_OBJECT), {"name": namespace.report_name}
             )
 
-    @staticmethod
-    def rename_pool(namespace):
-        """
-        Rename a pool.
-
-        :raises StratisCliEngineError:
-        :raises StratisCliNoChangeError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import ObjectManager
-        from ._data import Pool
-        from ._data import pools
-
-        proxy = get_object(TOP_OBJECT)
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-        (pool_object_path, _) = next(
-            pools(props={"Name": namespace.current})
-            .require_unique_match(True)
-            .search(managed_objects)
-        )
-
-        ((changed, _), return_code, message) = Pool.Methods.SetName(
-            get_object(pool_object_path), {"name": namespace.new}
-        )
-
+        # The only reason that stratisd has for returning an error code is
+        # if the report name is unrecognizes. However, the parser restricts
+        # the list # of names to only the ones that stratisd recognizes, so
+        # this branch can only be taken due to an unexpected bug in stratisd.
         if return_code != StratisdErrors.OK:  # pragma: no cover
             raise StratisCliEngineError(return_code, message)
 
-        if not changed:
-            raise StratisCliNoChangeError("rename", namespace.new)
-
-    @staticmethod
-    def add_data_devices(namespace):  # pylint: disable=too-many-locals
-        """
-        Add specified data devices to a pool.
-
-        :raises StratisCliEngineError:
-        :raises StratisCliIncoherenceError:
-        :raises StratisCliInUseOtherTierError:
-        :raises StratisCliInUseSameTierError:
-        :raises StratisCliPartialChangeError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import MODev
-        from ._data import ObjectManager
-        from ._data import Pool
-        from ._data import devs
-        from ._data import pools
-
-        proxy = get_object(TOP_OBJECT)
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-
-        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
-
-        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Cache)
-
-        _check_same_tier(
-            namespace.pool_name, managed_objects, blockdevs, BlockDevTiers.Data
-        )
-
-        (pool_object_path, _) = next(
-            pools(props={"Name": namespace.pool_name})
-            .require_unique_match(True)
-            .search(managed_objects)
-        )
-
-        ((added, devs_added), return_code, message) = Pool.Methods.AddDataDevs(
-            get_object(pool_object_path), {"devices": list(blockdevs)}
-        )
-        if return_code != StratisdErrors.OK:  # pragma: no cover
-            raise StratisCliEngineError(return_code, message)
-
-        if not added or len(devs_added) < len(blockdevs):  # pragma: no cover
-            devnodes_added = [
-                MODev(info).Devnode()
-                for (object_path, info) in devs(
-                    props={"Pool": pool_object_path}
-                ).search(ObjectManager.Methods.GetManagedObjects(proxy, {}))
-                if object_path in devs_added
-            ]
-            raise StratisCliIncoherenceError(
-                (
-                    "Expected to add the specified blockdevs to the data tier "
-                    "in pool %s but stratisd reports that it did not actually "
-                    "add some or all of the blockdevs requested; devices "
-                    "added: (%s), devices requested: (%s)"
-                )
-                % (namespace.pool_name, ", ".join(devnodes_added), ", ".join(blockdevs))
-            )
-
-    @staticmethod
-    def add_cache_devices(namespace):  # pylint: disable=too-many-locals
-        """
-        Add specified cache devices to a pool.
-
-        :raises StratisCliEngineError:
-        :raises StratisCliIncoherenceError:
-        :raises StratisCliInUseOtherTierError:
-        :raises StratisCliInUseSameTierError:
-        :raises StratisCliPartialChangeError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import MODev
-        from ._data import ObjectManager
-        from ._data import Pool
-        from ._data import devs
-        from ._data import pools
-
-        proxy = get_object(TOP_OBJECT)
-        managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})
-
-        blockdevs = frozenset([os.path.abspath(p) for p in namespace.blockdevs])
-
-        _check_opposite_tier(managed_objects, blockdevs, BlockDevTiers.Data)
-
-        _check_same_tier(
-            namespace.pool_name, managed_objects, blockdevs, BlockDevTiers.Cache
-        )
-
-        (pool_object_path, _) = next(
-            pools(props={"Name": namespace.pool_name})
-            .require_unique_match(True)
-            .search(managed_objects)
-        )
-
-        ((added, devs_added), return_code, message) = Pool.Methods.AddCacheDevs(
-            get_object(pool_object_path), {"devices": list(blockdevs)}
-        )
-        if return_code != StratisdErrors.OK:
-            raise StratisCliEngineError(return_code, message)
-
-        if not added or len(devs_added) < len(blockdevs):  # pragma: no cover
-            devnodes_added = [
-                MODev(info).Devnode()
-                for (object_path, info) in devs(
-                    props={"Pool": pool_object_path}
-                ).search(ObjectManager.Methods.GetManagedObjects(proxy, {}))
-                if object_path in devs_added
-            ]
-            raise StratisCliIncoherenceError(
-                (
-                    "Expected to add the specified blockdevs to the cache tier "
-                    "in pool %s but stratisd reports that it did not actually "
-                    "add some or all of the blockdevs requested; devices "
-                    "added: (%s), devices requested: (%s)"
-                )
-                % (namespace.pool_name, ", ".join(devnodes_added), ", ".join(blockdevs))
-            )
-
-    @staticmethod
-    def get_report(namespace):
-        """
-        Get the requested report from stratisd.
-
-        :raises StratisCliEngineError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import Report
-
-        (report, return_code, message) = Report.Methods.GetReport(
-            get_object(TOP_OBJECT), {"name": namespace.report_name}
-        )
-
-        if return_code != StratisdErrors.OK:
-            raise StratisCliEngineError(return_code, message)
-
         json_report = json.loads(report)
         print(json.dumps(json_report, indent=4, sort_keys=True))
 
@@ -803,7 +280,7 @@ def list_keys(_):
         )
 
     @staticmethod
-    def _bind(namespace, clevis_pin, clevis_config):
+    def _bind_clevis(namespace, clevis_pin, clevis_config):
         """
         Generic bind method. For further information about Clevis, and
         discussion of the pin and the configuration, consult Clevis
@@ -825,7 +302,10 @@ def _bind(namespace, clevis_pin, clevis_config):
         )
         (changed, return_code, return_msg) = Pool.Methods.Bind(
             get_object(pool_object_path),
-            {"pin": clevis_pin, "json": json.dumps(clevis_config),},
+            {
+                "pin": clevis_pin,
+                "json": json.dumps(clevis_config),
+            },
         )
 
         if return_code != StratisdErrors.OK:
@@ -842,13 +322,14 @@ def bind_tang(namespace):
         :raises StratisCliNoChangeError:
         :raises StratisCliEngineError:
         """
-        clevis_config = {"url": namespace.url}
-        if namespace.thumbprint is None:
-            clevis_config[CLEVIS_TANG_TRUST_URL] = True
+        clevis_config = {CLEVIS_KEY_URL: namespace.url}
+        if namespace.trust_url:
+            clevis_config[CLEVIS_KEY_TANG_TRUST_URL] = True
         else:
-            clevis_config["thp"] = namespace.thumbprint
+            assert namespace.thumbprint is not None
+            clevis_config[CLEVIS_KEY_THP] = namespace.thumbprint
 
-        TopActions._bind(namespace, "tang", clevis_config)
+        TopActions._bind_clevis(namespace, CLEVIS_PIN_TANG, clevis_config)
 
     @staticmethod
     def bind_tpm(namespace):
@@ -859,15 +340,12 @@ def bind_tpm(namespace):
         :raises StratisCliEngineError:
         """
 
-        TopActions._bind(namespace, "tpm2", {})
+        TopActions._bind_clevis(namespace, CLEVIS_PIN_TPM2, {})
 
     @staticmethod
-    def unbind(namespace):
+    def bind_keyring(namespace):
         """
-        Unbind all devices in an encrypted pool.
-
-        :raises StratisCliNoChangeError:
-        :raises StratisCliEngineError:
+        Bind all devices in an encrypted pool using the kernel keyring.
         """
         # pylint: disable=import-outside-toplevel
         from ._data import ObjectManager, Pool, pools
@@ -880,60 +358,15 @@ def unbind(namespace):
             .require_unique_match(True)
             .search(managed_objects)
         )
-        (changed, return_code, return_msg) = Pool.Methods.Unbind(
-            get_object(pool_object_path), {}
+        (changed, return_code, return_msg) = Pool.Methods.BindKeyring(
+            get_object(pool_object_path),
+            {
+                "key_desc": namespace.keydesc,
+            },
         )
 
         if return_code != StratisdErrors.OK:
             raise StratisCliEngineError(return_code, return_msg)
 
         if not changed:
-            raise StratisCliNoChangeError("unbind", pool_name)
-
-    @staticmethod
-    def unlock_pools(namespace):
-        """
-        Unlock all of the encrypted pools that have been detected by the daemon
-        but are still locked.
-        :raises StratisCliIncoherenceError:
-        :raises StratisCliNoChangeError:
-        :raises StratisCliAggregateError:
-        """
-        # pylint: disable=import-outside-toplevel
-        from ._data import Manager
-
-        proxy = get_object(TOP_OBJECT)
-
-        locked_pools = _fetch_locked_pools_property(proxy)
-        if locked_pools == {}:  # pragma: no cover
-            raise StratisCliNoChangeError("unlock", "pools")
-
-        # This block is not covered as the sim engine does not simulate the
-        # management of unlocked devices, so locked_pools is always empty.
-        errors = []  # pragma: no cover
-        for uuid in locked_pools:  # pragma: no cover
-            (
-                (is_some, unlocked_devices),
-                return_code,
-                message,
-            ) = Manager.Methods.UnlockPool(
-                proxy, {"pool_uuid": uuid, "unlock_method": namespace.unlock_method}
-            )
-
-            if return_code != StratisdErrors.OK:
-                errors.append(
-                    StratisCliPartialFailureError(
-                        "unlock", "pool with UUID %s" % uuid, error_message=message
-                    )
-                )
-
-            if is_some and unlocked_devices == []:
-                raise StratisCliIncoherenceError(
-                    (
-                        "stratisd reported that some existing devices are locked but "
-                        "no new devices were unlocked during this operation"
-                    )
-                )
-
-        if errors != []:  # pragma: no cover
-            raise StratisCliAggregateError("unlock", "pool", errors)
+            raise StratisCliNoChangeError("bind", pool_name)
diff --git a/src/stratis_cli/_actions/_utils.py b/src/stratis_cli/_actions/_utils.py
index bfb688bdf..bcfe05039 100644
--- a/src/stratis_cli/_actions/_utils.py
+++ b/src/stratis_cli/_actions/_utils.py
@@ -16,57 +16,25 @@
 Miscellaneous functions.
 """
 
+# isort: STDLIB
+import json
+
 from .._errors import (
     StratisCliEnginePropertyError,
-    StratisCliEnvironmentError,
+    StratisCliMissingClevisTangURLError,
+    StratisCliMissingClevisThumbprintError,
     StratisCliPropertyNotFoundError,
 )
+from .._stratisd_constants import (
+    CLEVIS_KEY_TANG_TRUST_URL,
+    CLEVIS_KEY_THP,
+    CLEVIS_KEY_URL,
+    CLEVIS_PIN_TANG,
+    CLEVIS_PIN_TPM2,
+)
 
 
-def get_timeout(value):
-    """
-    Turn an input str or int into a float timeout value.
-
-    :param value: the input str or int
-    :type value: str or int
-    :raises StratisCliEnvironmentError:
-    :returns: float
-    """
-
-    maximum_dbus_timeout_ms = 1073741823
-
-    # Ensure the input str is not a float
-    if isinstance(value, float):
-        raise StratisCliEnvironmentError(
-            "The timeout value provided is a float; it should be an integer."
-        )
-
-    try:
-        timeout_int = int(value)
-
-    except ValueError:
-        raise StratisCliEnvironmentError(
-            "The timeout value provided is not an integer."
-        )
-
-    # Ensure the integer is not too small
-    if timeout_int < -1:
-        raise StratisCliEnvironmentError(
-            "The timeout value provided is smaller than the smallest acceptable value, -1."
-        )
-
-    # Ensure the integer is not too large
-    if timeout_int > maximum_dbus_timeout_ms:
-        raise StratisCliEnvironmentError(
-            "The timeout value provided exceeds the largest acceptable value, %s."
-            % maximum_dbus_timeout_ms
-        )
-
-    # Convert from milliseconds to seconds
-    return timeout_int / 1000
-
-
-def fetch_property(props, name):
+def unpack_property(props, name):
     """
     Get a property fetched through FetchProperties interface
 
@@ -90,5 +58,65 @@ def fetch_property(props, name):
         if not success:
             raise StratisCliEnginePropertyError(name, variant)  # pragma: no cover
         return variant
-    except KeyError:  # pragma: no cover
-        raise StratisCliPropertyNotFoundError(name)
+    except KeyError as err:  # pragma: no cover
+        raise StratisCliPropertyNotFoundError(name) from err
+
+
+def fetch_property(proxy, property_name):
+    """
+    Fetch a property from stratisd.
+    :param proxy: proxy to the top object in stratisd
+    :param str property_name: name of the property to fetch
+    :return: value associated with the requested property name
+    :raises StratisCliPropertyNotFoundError:
+    :raises StratisCliEnginePropertyError:
+    """
+    # pylint: disable=import-outside-toplevel
+    from ._data import FetchProperties
+
+    properties = FetchProperties.Methods.GetProperties(
+        proxy, {"properties": [property_name]}
+    )
+    return unpack_property(properties, property_name)
+
+
+def get_clevis_info(namespace):
+    """
+    Get clevis info, if any, from the namespace.
+
+    :param namespace: namespace set up by the parser
+
+    :returns: clevis info or None
+    :rtype: pair of str or NoneType
+    """
+    clevis_info = None
+    if namespace.clevis is not None:
+        if namespace.clevis in ("nbde", "tang"):
+            if namespace.tang_url is None:
+                raise StratisCliMissingClevisTangURLError()
+
+            if not namespace.trust_url and namespace.thumbprint is None:
+                raise StratisCliMissingClevisThumbprintError()
+
+            clevis_config = {CLEVIS_KEY_URL: namespace.tang_url}
+            if namespace.trust_url:
+                clevis_config[CLEVIS_KEY_TANG_TRUST_URL] = True
+            else:
+                assert namespace.thumbprint is not None
+                clevis_config[CLEVIS_KEY_THP] = namespace.thumbprint
+
+            clevis_info = (CLEVIS_PIN_TANG, clevis_config)
+
+        elif namespace.clevis == "tpm2":
+            clevis_info = (CLEVIS_PIN_TPM2, {})
+
+        else:
+            raise AssertionError(
+                "unexpected value %s for clevis option" % namespace.clevis
+            )  # pragma: no cover
+
+    return (
+        clevis_info
+        if clevis_info is None
+        else (clevis_info[0], json.dumps(clevis_info[1]))
+    )
diff --git a/src/stratis_cli/_error_reporting.py b/src/stratis_cli/_error_reporting.py
index 4e54c06b1..5e51b36f6 100644
--- a/src/stratis_cli/_error_reporting.py
+++ b/src/stratis_cli/_error_reporting.py
@@ -28,7 +28,7 @@
     DbusClientMissingSearchPropertiesError,
     DbusClientUniqueResultError,
 )
-from dbus_python_client_gen import DPClientInvocationError
+from dbus_python_client_gen import DPClientInvocationError, DPClientMethodCallContext
 
 from ._actions import BLOCKDEV_INTERFACE, FILESYSTEM_INTERFACE, POOL_INTERFACE
 from ._errors import (
@@ -37,6 +37,7 @@
     StratisCliEngineError,
     StratisCliEnginePropertyError,
     StratisCliIncoherenceError,
+    StratisCliParserError,
     StratisCliStratisdVersionError,
     StratisCliUnknownInterfaceError,
     StratisCliUserError,
@@ -151,7 +152,10 @@ def _interpret_errors_0(error):
     return None  # pragma: no cover
 
 
-def _interpret_errors_1(errors):  # pylint: disable=too-many-return-statements
+def _interpret_errors_1(
+    # pylint: disable=bad-continuation
+    errors,
+):  # pylint: disable=too-many-return-statements, too-many-branches
     """
     Interpret the subchain of errors after the first error.
 
@@ -194,6 +198,10 @@ def _interpret_errors_1(errors):  # pylint: disable=too-many-return-statements
     if isinstance(error, StratisCliEnginePropertyError):  # pragma: no cover
         return str(error)
 
+    if isinstance(error, StratisCliParserError):
+        fmt_str = "You entered an invalid command: %s"
+        return fmt_str % error
+
     if isinstance(error, StratisCliUserError):
         fmt_str = "It appears that you issued an unintended command: %s"
         return fmt_str % error
@@ -263,10 +271,11 @@ def _interpret_errors_2(errors):  # pragma: no cover
 
     if len(errors) > 1:
         next_error = errors[1]
-        # We do not test this error, as the only known way to cause it is
-        # manipulation of selinux configuration, which is too laborious to
-        # bother with at this time.
         if isinstance(next_error, dbus.exceptions.DBusException):
+
+            # We do not test this error, as the only known way to cause it is
+            # manipulation of selinux configuration, which is too laborious to
+            # bother with at this time.
             if next_error.get_dbus_name() == "org.freedesktop.DBus.Error.Disconnected":
                 return (
                     "The D-Bus connection was disconnected during a "
@@ -274,6 +283,30 @@ def _interpret_errors_2(errors):  # pragma: no cover
                     "prohibit that particular D-Bus interaction."
                 )
 
+            # We do not test this error, as the only known way to cause it is
+            # to spam the daemon with a succession of mutating commands from
+            # separate processes. The circumstances that cause this exception
+            # to be raised are a call to GetManagedObjects() that is initiated
+            # while a call that removes a filesystem or pool is in progress.
+            # In that case, the GetManagedObjects() call may process object
+            # paths that have not yet been removed and may encounter an error
+            # when calculating the object properties since the engine no longer
+            # has any record of the filesystem or pool.
+            if next_error.get_dbus_name() == "org.freedesktop.DBus.Error.Failed":
+                context = error.context
+                if (
+                    # pylint: disable=bad-continuation
+                    error.interface_name == "org.freedesktop.DBus.ObjectManager"
+                    and isinstance(context, DPClientMethodCallContext)
+                    and context.method_name == "GetManagedObjects"
+                ):
+                    return (
+                        "A D-Bus method failed during execution of the "
+                        "selected command. Most likely, the failure was due "
+                        "to a transient inconsistency in the D-Bus interface "
+                        "and the command will succeed if run again."
+                    )
+
     return None
 
 
diff --git a/src/stratis_cli/_errors.py b/src/stratis_cli/_errors.py
index 3394ccc5c..160e82f23 100644
--- a/src/stratis_cli/_errors.py
+++ b/src/stratis_cli/_errors.py
@@ -76,9 +76,10 @@ class StratisCliPropertyNotFoundError(StratisCliRuntimeError):
     """
 
     def __init__(self, prop_name):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param str prop_name: the property that did not exist
+        :param str prop_name: the property that did not exist
         """
         # pylint: disable=super-init-not-called
         self.prop_name = prop_name
@@ -99,15 +100,16 @@ class StratisCliPartialChangeError(StratisCliUserError):
     """
 
     def __init__(self, command, changed_resources, unchanged_resources):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param str command: the command run that caused the error
-            :param changed_resources: the target resources that would change
-            :type changed_resources: frozenset of str
-            :param unchanged_resources: the target resources that would not change
-            :type unchanged_resources: frozenset of str
+        :param str command: the command run that caused the error
+        :param changed_resources: the target resources that would change
+        :type changed_resources: frozenset of str
+        :param unchanged_resources: the target resources that would not change
+        :type unchanged_resources: frozenset of str
 
-            Precondition: unchanged_resources != frozenset()
+        Precondition: unchanged_resources != frozenset()
         """
         # pylint: disable=super-init-not-called
         self.command = command
@@ -144,10 +146,11 @@ class StratisCliNoChangeError(StratisCliPartialChangeError):
     """
 
     def __init__(self, command, resource):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param str command: the executed command
-            :param str resource: the target resource
+        :param str command: the executed command
+        :param str resource: the target resource
         """
         StratisCliPartialChangeError.__init__(
             self, command, frozenset(), frozenset([resource])
@@ -174,6 +177,39 @@ def __str__(self):
         return "A %s named %s already exists" % (self.object_type, self.name)
 
 
+class StratisCliParserError(StratisCliRuntimeError):
+    """
+    Raised when user has passed arguments that the parser should reject
+    but can not due to its configuration limitations.
+    """
+
+
+class StratisCliMissingClevisTangURLError(StratisCliParserError):
+    """
+    Raised during pool creation if --clevis=[tang|nbde] specified but no
+    URL supplied.
+    """
+
+    def __str__(self):
+        return (
+            "Specified binding with Clevis Tang server, but URL was not "
+            "specified. Use --tang-url option to specify tang URL."
+        )
+
+
+class StratisCliMissingClevisThumbprintError(StratisCliParserError):
+    """
+    Raised during pool creation if --clevis=[tang|nbde] specified but neither
+    a thumbprint nor instructions to trust the URL were supplied.
+    """
+
+    def __str__(self):
+        return (
+            "Specified binding with Clevis Tang server, but neither "
+            "--thumbprint nor --trust-url option was specified."
+        )
+
+
 class StratisCliIncoherenceError(StratisCliRuntimeError):
     """
     Raised if there was a disagreement about state between the CLI
@@ -199,15 +235,16 @@ class StratisCliInUseOtherTierError(StratisCliInUseError):
     """
 
     def __init__(self, pools_to_blockdevs, added_as):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param pools_to_blockdevs: pools mapped to the blockdevs they own
-            :type pools_to_blockdevs: dict of str * frozenset of str
-            :param added_as: what tier the devices were to be added to
-            :type added_as: _stratisd_constants.BlockDevTiers
+        :param pools_to_blockdevs: pools mapped to the blockdevs they own
+        :type pools_to_blockdevs: dict of str * frozenset of str
+        :param added_as: what tier the devices were to be added to
+        :type added_as: _stratisd_constants.BlockDevTiers
 
-            Precondition: pools_to_blockdevs != {}
-            Precondition: all frozenset of str in pools_to_blockdevs have at least one item
+        Precondition: pools_to_blockdevs != {}
+        Precondition: all frozenset of str in pools_to_blockdevs have at least one item
         """
         # pylint: disable=super-init-not-called
         self.pools_to_blockdevs = pools_to_blockdevs
@@ -262,15 +299,16 @@ class StratisCliInUseSameTierError(StratisCliInUseError):
     """
 
     def __init__(self, pools_to_blockdevs, added_as):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param pools_to_blockdevs: pools mapped to the blockdevs they own
-            :type pools_to_blockdevs: dict of str * frozenset of str
-            :param added_as: what tier the devices were to be added to
-            :type added_as: _stratisd_constants.BlockDevTiers
+        :param pools_to_blockdevs: pools mapped to the blockdevs they own
+        :type pools_to_blockdevs: dict of str * frozenset of str
+        :param added_as: what tier the devices were to be added to
+        :type added_as: _stratisd_constants.BlockDevTiers
 
-            Precondition: pools_to_blockdevs != {}
-            Precondition: all frozenset of str in pools_to_blockdevs have at least one item
+        Precondition: pools_to_blockdevs != {}
+        Precondition: all frozenset of str in pools_to_blockdevs have at least one item
         """
         # pylint: disable=super-init-not-called
         self.pools_to_blockdevs = pools_to_blockdevs
@@ -326,9 +364,10 @@ class StratisCliUnknownInterfaceError(StratisCliRuntimeError):
     """
 
     def __init__(self, interface_name):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param str interface_name: the unexpected interface name
+        :param str interface_name: the unexpected interface name
         """
         # pylint: disable=super-init-not-called
         self._interface_name = interface_name
@@ -347,11 +386,12 @@ class StratisCliEnginePropertyError(StratisCliRuntimeError):
     """
 
     def __init__(self, prop_name, message):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param str prop_name: the property that could not be obtained
-            :param str message: the error message returned by the engine in
-                                place of the property
+        :param str prop_name: the property that could not be obtained
+        :param str message: the error message returned by the engine in
+                            place of the property
         """
         # pylint: disable=super-init-not-called
         self.prop_name = prop_name
@@ -371,10 +411,11 @@ class StratisCliEngineError(StratisCliRuntimeError):
     """
 
     def __init__(self, rc, message):
-        """ Initializer.
+        """
+        Initializer.
 
-            :param rc int: the error code returned by the engine
-            :param str message: whatever message accompanied the error code
+        :param rc int: the error code returned by the engine
+        :param str message: whatever message accompanied the error code
         """
         # pylint: disable=super-init-not-called
         self.error_code = rc
diff --git a/src/stratis_cli/_parser/_bind.py b/src/stratis_cli/_parser/_bind.py
index 21cbdd93c..aaeffcf8e 100644
--- a/src/stratis_cli/_parser/_bind.py
+++ b/src/stratis_cli/_parser/_bind.py
@@ -24,13 +24,6 @@
             help="Bind using NBDE via a tang server",
             args=[
                 ("pool_name", dict(action="store", help="Pool name")),
-                (
-                    "key_description",
-                    dict(
-                        action="store",
-                        help="Description of key in kernel keyring used by this pool",
-                    ),
-                ),
                 ("url", dict(action="store", help="URL of tang server")),
             ],
             mut_ex_args=[
@@ -66,15 +59,19 @@
             help="Bind using TPM2",
             args=[
                 ("pool_name", dict(action="store", help="Pool name")),
-                (
-                    "key_description",
-                    dict(
-                        action="store",
-                        help="Description of key in kernel keyring used by this pool",
-                    ),
-                ),
             ],
             func=TopActions.bind_tpm,
         ),
     ),
+    (
+        "keyring",
+        dict(
+            help="Bind using the kernel keyring",
+            args=[
+                ("pool_name", dict(action="store", help="Pool name")),
+                ("keydesc", dict(action="store", help="key description")),
+            ],
+            func=TopActions.bind_keyring,
+        ),
+    ),
 ]
diff --git a/src/stratis_cli/_parser/_parser.py b/src/stratis_cli/_parser/_parser.py
index 591d9df65..fcdc6395e 100644
--- a/src/stratis_cli/_parser/_parser.py
+++ b/src/stratis_cli/_parser/_parser.py
@@ -21,6 +21,7 @@
 from .._actions import (
     LogicalActions,
     PhysicalActions,
+    PoolActions,
     StratisActions,
     TopActions,
     check_stratisd_version,
@@ -113,7 +114,7 @@ def wrapped_func(*args):
         dict(
             help="Perform General Pool Actions",
             subcmds=POOL_SUBCMDS,
-            func=TopActions.list_pools,
+            func=PoolActions.list_pools,
         ),
     ),
     (
@@ -142,7 +143,11 @@ def wrapped_func(*args):
                 (
                     "report_name",
                     dict(
-                        default=None, type=str, help=("Name of the report to display")
+                        default="engine_state_report",
+                        type=str,
+                        help=("Name of the report to display"),
+                        nargs="?",
+                        choices=["engine_state_report", "errored_pool_report"],
                     ),
                 )
             ],
@@ -160,7 +165,11 @@ def wrapped_func(*args):
 ]
 
 GEN_ARGS = [
-    ("--propagate", dict(action="store_true", help="Allow exceptions to propagate"))
+    ("--propagate", dict(action="store_true", help="Allow exceptions to propagate")),
+    (
+        "--unhyphenated-uuids",
+        dict(action="store_true", help=("Display UUIDs in unhyphenated format")),
+    ),
 ]
 
 
diff --git a/src/stratis_cli/_parser/_pool.py b/src/stratis_cli/_parser/_pool.py
index 1937bed6b..8a17f4067 100644
--- a/src/stratis_cli/_parser/_pool.py
+++ b/src/stratis_cli/_parser/_pool.py
@@ -15,7 +15,7 @@
 Definition of pool actions to display in the CLI.
 """
 
-from .._actions import TopActions
+from .._actions import PoolActions
 from .._stratisd_constants import EncryptionMethod
 from ._bind import BIND_SUBCMDS
 
@@ -51,8 +51,61 @@
                         dest="key_desc",
                     ),
                 ),
+                (
+                    "--clevis",
+                    dict(
+                        default=None,
+                        type=str,
+                        help=("Specification for binding with Clevis."),
+                        dest="clevis",
+                        choices=["nbde", "tang", "tpm2"],
+                    ),
+                ),
+                (
+                    "--tang-url",
+                    dict(
+                        default=None,
+                        type=str,
+                        help=(
+                            "URL of Clevis tang server (ignored if "
+                            "--clevis=[tang|nbde] not set)"
+                        ),
+                        dest="tang_url",
+                    ),
+                ),
             ],
-            func=TopActions.create_pool,
+            mut_ex_args=[
+                (
+                    False,
+                    [
+                        (
+                            "--trust-url",
+                            dict(
+                                action="store_true",
+                                help=(
+                                    "Omit verification of tang server "
+                                    "credentials (ignored if "
+                                    "--clevis=[tang|nbde] not set)"
+                                ),
+                                dest="trust_url",
+                            ),
+                        ),
+                        (
+                            "--thumbprint",
+                            dict(
+                                action="store",
+                                help=(
+                                    "Thumbprint of tang server at specified "
+                                    "URL (ignored if --clevis=[tang|nbde] not "
+                                    "set)"
+                                ),
+                                dest="thumbprint",
+                            ),
+                        ),
+                    ],
+                )
+            ],
+            func=PoolActions.create_pool,
         ),
     ),
     (
@@ -75,7 +128,7 @@
                     ),
                 ),
             ],
-            func=TopActions.init_cache,
+            func=PoolActions.init_cache,
         ),
     ),
     (
@@ -83,7 +136,7 @@
         dict(
             help="List pools",
             description="Lists Stratis pools that exist on the system",
-            func=TopActions.list_pools,
+            func=PoolActions.list_pools,
         ),
     ),
     (
@@ -91,7 +144,7 @@
         dict(
             help="Destroy a pool",
             args=[("pool_name", dict(action="store", help="pool name"))],
-            func=TopActions.destroy_pool,
+            func=PoolActions.destroy_pool,
         ),
     ),
     (
@@ -102,7 +155,7 @@
                 ("current", dict(action="store", help="Current pool name")),
                 ("new", dict(action="store", help="New pool name")),
             ],
-            func=TopActions.rename_pool,
+            func=PoolActions.rename_pool,
         ),
     ),
     (
@@ -120,7 +173,7 @@
                     ),
                 ),
             ],
-            func=TopActions.add_data_devices,
+            func=PoolActions.add_data_devices,
         ),
     ),
     (
@@ -138,7 +191,7 @@
                     ),
                 ),
             ],
-            func=TopActions.add_cache_devices,
+            func=PoolActions.add_cache_devices,
         ),
     ),
     (
@@ -152,8 +205,18 @@
         "unbind",
         dict(
             help="Unbind the given pool, removing support for encryption with Clevis",
-            args=[("pool_name", dict(action="store", help="Pool name"))],
-            func=TopActions.unbind,
+            args=[
+                (
+                    "method",
+                    dict(
+                        action="store",
+                        choices=[str(x) for x in list(EncryptionMethod)],
+                        help="Encryption method to unbind",
+                    ),
+                ),
+                ("pool_name", dict(action="store", help="Pool name")),
+            ],
+            func=PoolActions.unbind,
         ),
     ),
     (
@@ -171,7 +234,7 @@
                     ),
                 )
             ],
-            func=TopActions.unlock_pools,
+            func=PoolActions.unlock_pools,
         ),
     ),
 ]
diff --git a/src/stratis_cli/_stratisd_constants.py b/src/stratis_cli/_stratisd_constants.py
index c5cdc063f..da4f1c893 100644
--- a/src/stratis_cli/_stratisd_constants.py
+++ b/src/stratis_cli/_stratisd_constants.py
@@ -109,4 +109,8 @@ def __str__(self):
         return self.value
 
 
-CLEVIS_TANG_TRUST_URL = "stratis:tang:trust_url"
+CLEVIS_KEY_TANG_TRUST_URL = "stratis:tang:trust_url"
+CLEVIS_PIN_TANG = "tang"
+CLEVIS_PIN_TPM2 = "tpm2"
+CLEVIS_KEY_THP = "thp"
+CLEVIS_KEY_URL = "url"


Test patch:
diff --git a/tests/blackbox/README.md b/tests/blackbox/README.md
deleted file mode 100644
index da3c6f0de..000000000
--- a/tests/blackbox/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-The intention for the tests in this directory are for verifying that a
-released, packaged, and installed instance of Stratis is functional.
-
-Run the tests as root and supply 3 or more block devices on the
-command line that are blank for test use.
-
-The `stratisd` test may be run without the installation of the `stratis`
-command line interface.
-
-```bash
-# python3 stratisd_cert.py -v --disk /dev/vdb --disk /dev/vdc --disk /dev/vdd
-```
-
-The stratis-cli test assumes that the `stratisd` daemon is installed,
-although the test will start the daemon if needed.
-
-```bash
-# python3 stratis_cli_cert.py -v --disk /dev/vdb --disk /dev/vdc --disk /dev/vdd
-```
diff --git a/tests/blackbox/stratis_cli_cert.py b/tests/blackbox/stratis_cli_cert.py
deleted file mode 100644
index cb17007f5..000000000
--- a/tests/blackbox/stratis_cli_cert.py
+++ /dev/null
@@ -1,704 +0,0 @@
-# Copyright 2019 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Tests of the stratis CLI.
-"""
-
-# isort: STDLIB
-import argparse
-import os
-import sys
-import time
-import unittest
-
-# isort: THIRDPARTY
-from testlib.dbus import fs_n, p_n
-from testlib.infra import KernelKey, clean_up
-from testlib.utils import (
-    RandomKeyTmpFile,
-    exec_command,
-    exec_test_command,
-    process_exists,
-)
-
-_STRATIS_CLI = os.getenv("STRATIS_CLI", "/usr/bin/stratis")
-_ROOT = 0
-_NON_ROOT = 1
-
-
-def _raise_error_exception(return_code, msg):
-    """
-    Check result of a CLI call in a context where it is in error
-    if the call fails.
-    :param int return_code: the return code from the D-Bus call
-    :param str msg: the message returned on the D-Bus
-    """
-    if return_code != 0:
-        raise RuntimeError(
-            "Expected return code of 0; actual return code: %s, error_msg: %s"
-            % (return_code, msg)
-        )
-
-
-def make_test_pool(pool_disks, key_desc=None):
-    """
-    Create a test pool that will later get destroyed
-    :param list pool_disks: List of disks with which the pool will be created
-    :param key_desc: Key description associated with the key with which to encrypt
-                     the block devices in the pool
-    :type key_desc: str or NoneType
-    :return: Name of the created pool
-    """
-    pool_name = p_n()
-
-    args = [_STRATIS_CLI, "pool", "create"]
-    if key_desc is not None:
-        args += ["--key-desc", key_desc]
-    args += [pool_name] + pool_disks
-
-    (return_code, _, stderr) = exec_test_command(args)
-
-    _raise_error_exception(return_code, stderr)
-    return pool_name
-
-
-def make_test_filesystem(pool_name):
-    """
-    Create a test filesystem that will later get destroyed
-    :param pool_name: Name of a test pool
-    :return: Name of the created filesystem
-    """
-    filesystem_name = fs_n()
-    (return_code, _, stderr) = exec_test_command(
-        [_STRATIS_CLI, "filesystem", "create", pool_name, filesystem_name]
-    )
-
-    _raise_error_exception(return_code, stderr)
-    return filesystem_name
-
-
-class StratisCertify(unittest.TestCase):  # pylint: disable=too-many-public-methods
-    """
-    Unit tests for Stratis
-    """
-
-    def setUp(self):
-        """
-        Setup for an individual test.
-        * Register a cleanup action, to be run if the test fails.
-        * Ensure that stratisd is running via systemd.
-        * Use the running stratisd instance to destroy any existing
-        Stratis filesystems, pools, etc.
-        * Call "udevadm settle" so udev database can be updated with changes
-        to Stratis devices.
-        :return: None
-        """
-        self.addCleanup(clean_up)
-
-        if process_exists("stratisd") is None:
-            exec_command(["systemctl", "start", "stratisd"])
-            time.sleep(20)
-
-        clean_up()
-
-        time.sleep(1)
-        exec_command(["udevadm", "settle"])
-
-    def _test_permissions(self, command_line, permissions, exp_stdout_empty):
-        """
-        Test running CLI commands with and without root permissions.
-        :param list command_line: The arguments needed to execute the Stratis command being tested
-        :type command_line: List of str
-        :param bool permissions: True if the Stratis command needs root permissions to succeed,
-                                    otherwise False.
-        :param bool exp_stdout_empty: True if stdout is expected to be empty
-                                        when Stratis command succeeds.
-        """
-
-        os.seteuid(_NON_ROOT)
-        try:
-            if permissions:
-                self.unittest_command(command_line, 1, False, True)
-            else:
-                self.unittest_command(command_line, 0, True, exp_stdout_empty)
-        except Exception as err:
-            os.seteuid(_ROOT)
-            raise err
-
-        os.seteuid(_ROOT)
-
-        if permissions:
-            self.unittest_command(command_line, 0, True, exp_stdout_empty)
-
-    def unittest_command(  # pylint: disable=bad-continuation
-        self, args, exp_exit_code, exp_stderr_is_empty, exp_stdout_is_empty
-    ):
-        """
-        Execute a test command and make assertions about the exit code, stderr, and stdout
-        :param list args: The arguments needed to execute the Stratis command being tested
-        :type args: List of str
-        :param exp_exit_code: The expected exit code, 0, 1, or 2
-        :param bool exp_stderr_is_empty: True if stderr is expected to be empty, otherwise False
-        :param bool exp_stdout_is_empty: True if stdout is expected to be empty, otherwise False
-        :return: None
-        """
-        exit_code, stdout, stderr = exec_test_command(args)
-
-        self.assertEqual(
-            exit_code,
-            exp_exit_code,
-            msg=os.linesep.join(["", "stdout: %s" % stdout, "stderr: %s" % stderr]),
-        )
-
-        if exp_stderr_is_empty:
-            self.assertEqual(stderr, "")
-        else:
-            self.assertNotEqual(stderr, "")
-
-        if exp_stdout_is_empty:
-            self.assertEqual(stdout, "")
-        else:
-            self.assertNotEqual(stdout, "")
-
-    def test_stratisd_version(self):
-        """
-        Test getting the daemon version.
-        """
-        self.unittest_command([_STRATIS_CLI, "daemon", "version"], 0, True, False)
-
-    def test_stratisd_version_permissions(self):
-        """
-        Test that getting the daemon version succeeds with dropped permissions.
-        """
-        self._test_permissions([_STRATIS_CLI, "daemon", "version"], False, False)
-
-    def test_stratisd_redundancy(self):
-        """
-        Test listing the redundancy levels that the Stratis service supports.
-        """
-        self.unittest_command([_STRATIS_CLI, "daemon", "redundancy"], 0, True, False)
-
-    def test_stratisd_redundancy_permissions(self):
-        """
-        Test listing the redundancy levels succeeds with dropped permissions.
-        """
-        self._test_permissions([_STRATIS_CLI, "daemon", "redundancy"], False, False)
-
-    def test_engine_state_report(self):
-        """
-        Test getting the engine_state_report.
-        """
-        self.unittest_command(
-            [_STRATIS_CLI, "report", "engine_state_report"], 0, True, False
-        )
-
-    def test_engine_state_report_permissions(self):
-        """
-        Test getting the engine_state_report succeeds with dropped permissions.
-        """
-        self._test_permissions(
-            [_STRATIS_CLI, "report", "engine_state_report"], False, False
-        )
-
-    def test_invalid_report(self):
-        """
-        Test getting an invalid report.
-        """
-        self.unittest_command([_STRATIS_CLI, "report", "invalid_name"], 1, False, True)
-
-    def test_pool_list_empty(self):
-        """
-        Test listing a non-existent pool.
-        """
-        self.unittest_command([_STRATIS_CLI, "pool", "list"], 0, True, False)
-
-    def test_filesystem_list_empty(self):
-        """
-        Test listing an non-existent filesystem.
-        """
-        self.unittest_command([_STRATIS_CLI, "filesystem", "list"], 0, True, False)
-
-    def test_key_set_unset(self):
-        """
-        Test setting and unsetting a key.
-        """
-        with RandomKeyTmpFile() as fname:
-            self.unittest_command(
-                [_STRATIS_CLI, "key", "set", "testkey1", "--keyfile-path", fname],
-                0,
-                True,
-                True,
-            )
-
-        self.unittest_command([_STRATIS_CLI, "key", "unset", "testkey1"], 0, True, True)
-
-    def test_key_set_reset_unset(self):
-        """
-        Test setting, resetting, and unsetting a key.
-        """
-        with RandomKeyTmpFile() as first_fname, RandomKeyTmpFile() as second_fname:
-            self.unittest_command(
-                [_STRATIS_CLI, "key", "set", "testkey2", "--keyfile-path", first_fname],
-                0,
-                True,
-                True,
-            )
-            self.unittest_command(
-                [
-                    _STRATIS_CLI,
-                    "key",
-                    "reset",
-                    "testkey2",
-                    "--keyfile-path",
-                    second_fname,
-                ],
-                0,
-                True,
-                True,
-            )
-
-        self.unittest_command([_STRATIS_CLI, "key", "unset", "testkey2"], 0, True, True)
-
-    def test_key_set_reset_unset_permissions(self):
-        """
-        Test setting, resetting, and unsetting a key fails with dropped permissions.
-        """
-        with RandomKeyTmpFile() as first_fname, RandomKeyTmpFile() as second_fname:
-            self._test_permissions(
-                [_STRATIS_CLI, "key", "set", "testkey2", "--keyfile-path", first_fname],
-                True,
-                True,
-            )
-            self._test_permissions(
-                [
-                    _STRATIS_CLI,
-                    "key",
-                    "reset",
-                    "testkey2",
-                    "--keyfile-path",
-                    second_fname,
-                ],
-                True,
-                True,
-            )
-
-        self._test_permissions([_STRATIS_CLI, "key", "unset", "testkey2"], True, True)
-
-    def test_pool_create(self):
-        """
-        Test creating a pool.
-        """
-        pool_name = p_n()
-        self.unittest_command(
-            [_STRATIS_CLI, "pool", "create", pool_name, StratisCertify.DISKS[0]],
-            0,
-            True,
-            True,
-        )
-
-    def test_pool_create_permissions(self):
-        """
-        Test creating a pool fails with dropped permissions.
-        """
-        pool_name = p_n()
-        self._test_permissions(
-            [_STRATIS_CLI, "pool", "create", pool_name, StratisCertify.DISKS[0]],
-            True,
-            True,
-        )
-
-    def test_pool_create_encrypted(self):
-        """
-        Test creating an encrypted pool.
-        """
-        with KernelKey("test-password") as key_desc:
-            pool_name = p_n()
-            self.unittest_command(
-                [
-                    _STRATIS_CLI,
-                    "pool",
-                    "create",
-                    "--key-desc",
-                    key_desc,
-                    pool_name,
-                    StratisCertify.DISKS[0],
-                ],
-                0,
-                True,
-                True,
-            )
-
-    def test_pool_create_encrypted_with_cache(self):
-        """
-        Test creating an encrypted pool with cache.
-
-        This should fail.
-        """
-        with KernelKey("test-password") as key_desc:
-            pool_name = make_test_pool(StratisCertify.DISKS[0:2], key_desc)
-            self.unittest_command(
-                [
-                    _STRATIS_CLI,
-                    "pool",
-                    "init-cache",
-                    pool_name,
-                    StratisCertify.DISKS[2],
-                ],
-                1,
-                False,
-                True,
-            )
-
-    def test_pool_list_not_empty(self):
-        """
-        Test listing an existent pool.
-        """
-        make_test_pool(StratisCertify.DISKS[0:1])
-        self.unittest_command([_STRATIS_CLI, "pool", "list"], 0, True, False)
-
-    def test_pool_list_not_empty_permissions(self):
-        """
-        Test listing an existent pool succeeds with dropped permissions.
-        """
-        make_test_pool(StratisCertify.DISKS[0:1])
-        self._test_permissions([_STRATIS_CLI, "pool", "list"], False, False)
-
-    def test_blockdev_list(self):
-        """
-        Test listing a blockdev.
-        """
-        self.unittest_command([_STRATIS_CLI, "blockdev", "list"], 0, True, False)
-
-    def test_blockdev_list_permissions(self):
-        """
-        Test listing a blockdev succeeds with dropped permissions.
-        """
-        self._test_permissions([_STRATIS_CLI, "blockdev", "list"], False, False)
-
-    def test_pool_create_same_name(self):
-        """
-        Test creating a pool that already exists.
-        """
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "pool",
-                "create",
-                make_test_pool(StratisCertify.DISKS[0:1]),
-                StratisCertify.DISKS[1],
-            ],
-            1,
-            False,
-            True,
-        )
-
-    def test_pool_init_cache(self):
-        """
-        Test initialzing the cache for a pool.
-        """
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "pool",
-                "init-cache",
-                make_test_pool(StratisCertify.DISKS[0:2]),
-                StratisCertify.DISKS[2],
-            ],
-            0,
-            True,
-            True,
-        )
-
-    def test_pool_init_cache_permissions(self):
-        """
-        Test initialzing the cache for a pool fails with dropped permissions.
-        """
-        self._test_permissions(
-            [
-                _STRATIS_CLI,
-                "pool",
-                "init-cache",
-                make_test_pool(StratisCertify.DISKS[0:2]),
-                StratisCertify.DISKS[2],
-            ],
-            True,
-            True,
-        )
-
-    def test_pool_destroy(self):
-        """
-        Test destroying a pool.
-        """
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "pool",
-                "destroy",
-                make_test_pool(StratisCertify.DISKS[0:1]),
-            ],
-            0,
-            True,
-            True,
-        )
-
-    def test_pool_destroy_permissions(self):
-        """
-        Test destroying a pool fails with dropped permissions.
-        """
-        self._test_permissions(
-            [
-                _STRATIS_CLI,
-                "pool",
-                "destroy",
-                make_test_pool(StratisCertify.DISKS[0:1]),
-            ],
-            True,
-            True,
-        )
-
-    def test_filesystem_create(self):
-        """
-        Test creating a filesystem.
-        """
-        filesystem_name = fs_n()
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "create",
-                make_test_pool(StratisCertify.DISKS[0:1]),
-                filesystem_name,
-            ],
-            0,
-            True,
-            True,
-        )
-
-    def test_filesystem_create_permissions(self):
-        """
-        Test creating a filesystem fails with dropped permissions.
-        """
-        filesystem_name = fs_n()
-        self._test_permissions(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "create",
-                make_test_pool(StratisCertify.DISKS[0:1]),
-                filesystem_name,
-            ],
-            True,
-            True,
-        )
-
-    def test_pool_add_data(self):
-        """
-        Test adding data to a pool.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        self.unittest_command(
-            [_STRATIS_CLI, "pool", "add-data", pool_name, StratisCertify.DISKS[1]],
-            0,
-            True,
-            True,
-        )
-
-    def test_pool_add_data_permissions(self):
-        """
-        Test adding data to a pool fails with dropped permissions.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        self._test_permissions(
-            [_STRATIS_CLI, "pool", "add-data", pool_name, StratisCertify.DISKS[1]],
-            True,
-            True,
-        )
-
-    def test_filesystem_list_not_empty(self):
-        """
-        Test listing an existent filesystem.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        make_test_filesystem(pool_name)
-        self.unittest_command([_STRATIS_CLI, "filesystem", "list"], 0, True, False)
-
-    def test_filesystem_list_not_empty_permissions(self):
-        """
-        Test listing an existent filesystem succeeds with dropped permissions.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        make_test_filesystem(pool_name)
-        self._test_permissions([_STRATIS_CLI, "filesystem", "list"], False, False)
-
-    def test_filesystem_create_same_name(self):
-        """
-        Test creating a filesystem that already exists.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        self.unittest_command(
-            [_STRATIS_CLI, "filesystem", "create", pool_name, filesystem_name],
-            1,
-            False,
-            True,
-        )
-
-    def test_filesystem_rename(self):
-        """
-        Test renaming a filesystem to a new name.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        fs_name_rename = fs_n()
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "rename",
-                pool_name,
-                filesystem_name,
-                fs_name_rename,
-            ],
-            0,
-            True,
-            True,
-        )
-
-    def test_filesystem_rename_permissions(self):
-        """
-        Test renaming a filesystem fails with dropped permissions.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        fs_name_rename = fs_n()
-        self._test_permissions(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "rename",
-                pool_name,
-                filesystem_name,
-                fs_name_rename,
-            ],
-            True,
-            True,
-        )
-
-    def test_filesystem_rename_same_name(self):
-        """
-        Test renaming a filesystem to the same name.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "rename",
-                pool_name,
-                filesystem_name,
-                filesystem_name,
-            ],
-            1,
-            False,
-            True,
-        )
-
-    def test_filesystem_snapshot(self):
-        """
-        Test snapshotting a filesystem.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        snapshot_name = fs_n()
-        self.unittest_command(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "snapshot",
-                pool_name,
-                filesystem_name,
-                snapshot_name,
-            ],
-            0,
-            True,
-            True,
-        )
-
-    def test_filesystem_snapshot_permissions(self):
-        """
-        Test snapshotting a filesystem fails with dropped permissions.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        snapshot_name = fs_n()
-        self._test_permissions(
-            [
-                _STRATIS_CLI,
-                "filesystem",
-                "snapshot",
-                pool_name,
-                filesystem_name,
-                snapshot_name,
-            ],
-            True,
-            True,
-        )
-
-    def test_filesystem_destroy(self):
-        """
-        Test destroying a filesystem.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        self.unittest_command(
-            [_STRATIS_CLI, "filesystem", "destroy", pool_name, filesystem_name],
-            0,
-            True,
-            True,
-        )
-
-    def test_filesystem_destroy_permissions(self):
-        """
-        Test destroying a filesystem fails with dropped permissions.
-        """
-        pool_name = make_test_pool(StratisCertify.DISKS[0:1])
-        filesystem_name = make_test_filesystem(pool_name)
-        self._test_permissions(
-            [_STRATIS_CLI, "filesystem", "destroy", pool_name, filesystem_name],
-            True,
-            True,
-        )
-
-
-def main():
-    """
-    The main method.
-    """
-    argument_parser = argparse.ArgumentParser()
-    argument_parser.add_argument(
-        "--disk",
-        action="append",
-        dest="DISKS",
-        default=[],
-        help="disks to use, a minimum of 3 in order to run every test",
-    )
-    parsed_args, unittest_args = argument_parser.parse_known_args()
-    StratisCertify.DISKS = parsed_args.DISKS
-    print("Using block device(s) for tests: %s" % StratisCertify.DISKS)
-    unittest.main(argv=sys.argv[:1] + unittest_args)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/blackbox/stratisd_cert.py b/tests/blackbox/stratisd_cert.py
deleted file mode 100644
index a1af04916..000000000
--- a/tests/blackbox/stratisd_cert.py
+++ /dev/null
@@ -1,756 +0,0 @@
-# Copyright 2019 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Tests of stratisd.
-"""
-
-# isort: STDLIB
-import argparse
-import json
-import os
-import sys
-import time
-import unittest
-from tempfile import NamedTemporaryFile
-
-# isort: THIRDPARTY
-import dbus
-from testlib.dbus import StratisDbus, fs_n, p_n
-from testlib.infra import KernelKey, clean_up
-from testlib.utils import exec_command, process_exists, resolve_symlink
-
-_ROOT = 0
-_NON_ROOT = 1
-
-
-def _raise_error_exception(return_code, msg, return_value_exists):
-    """
-    Check result of a D-Bus call in a context where it is in error
-    if the call fails.
-    :param int return_code: the return code from the D-Bus call
-    :param str msg: the message returned on the D-Bus
-    :param bool return_value_exists: whether a value representing
-                                     a valid result was returned
-    """
-    if return_code != 0:
-        raise RuntimeError(
-            "Expected return code of 0; actual return code: %s, error_msg: %s"
-            % (return_code, msg)
-        )
-
-    if not return_value_exists:
-        raise RuntimeError(
-            "Result value was default or placeholder value and does not represent a valid result"
-        )
-
-
-def make_test_pool(pool_name, pool_disks):
-    """
-    Create a test pool that will later get destroyed
-    :param str pool_name: Name of the pool to be created
-    :param list pool_disks: List of disks with which the pool will be created
-    :return: Object path of the created pool
-    """
-    (obj_path_exists, (obj_path, _)), return_code, msg = StratisDbus.pool_create(
-        pool_name, pool_disks, None
-    )
-
-    _raise_error_exception(return_code, msg, obj_path_exists)
-    return obj_path
-
-
-def make_test_filesystem(pool_path, fs_name):
-    """
-    Create a test filesystem that will later get destroyed
-    :param str pool_path: Object path of a test pool
-    :param str fs_name: Name of the filesystem to be created
-    :return: Object path of the created filesystem
-    """
-    (
-        (filesystems_created, (array_of_tuples_with_obj_paths_and_names),),
-        return_code,
-        msg,
-    ) = StratisDbus.fs_create(pool_path, fs_name)
-
-    _raise_error_exception(return_code, msg, filesystems_created)
-    exec_command(["udevadm", "settle"])
-    return array_of_tuples_with_obj_paths_and_names[0][0]
-
-
-def acquire_filesystem_symlink_targets(pool_path, filesystem_path):
-    """
-    Acquire the symlink targets of the "/dev/stratis" symlink,
-    and the equivalent device-mapper "/dev/mapper" link, generated
-    via the info from get_managed_objects().
-    NOTE: This may require a preceding "udevadm settle" call, to
-    ensure that up-to-date pool and filesystem information is being
-    collected.
-    :param str pool_path: pool path
-    :param str filesystem_path: filesystem path
-    :return: str fsdevdest, str fsdevmapperlinkdest
-    """
-    objects = StratisDbus.get_managed_objects()
-
-    pool_gmodata = objects[pool_path]
-    pool_uuid = pool_gmodata[StratisDbus.POOL_IFACE]["Uuid"]
-    filesystem_gmodata = objects[filesystem_path]
-    filesystem_uuid = filesystem_gmodata[StratisDbus.FS_IFACE]["Uuid"]
-
-    filesystem_devnode = filesystem_gmodata[StratisDbus.FS_IFACE]["Devnode"]
-
-    fs_devmapperlinkstr = (
-        "/dev/mapper/stratis-1-" + pool_uuid + "-thin-fs-" + filesystem_uuid
-    )
-
-    fsdevdest = resolve_symlink(filesystem_devnode)
-    fsdevmapperlinkdest = resolve_symlink(fs_devmapperlinkstr)
-    return fsdevdest, fsdevmapperlinkdest
-
-
-class StratisCertify(unittest.TestCase):  # pylint: disable=too-many-public-methods
-    """
-    Unit tests for Stratis
-    """
-
-    def setUp(self):
-        """
-        Setup for an individual test.
-        * Register a cleanup action, to be run if the test fails.
-        * Ensure that stratisd is running via systemd.
-        * Use the running stratisd instance to destroy any existing
-        Stratis filesystems, pools, etc.
-        * Call "udevadm settle" so udev database can be updated with changes
-        to Stratis devices.
-        :return: None
-        """
-        self.addCleanup(clean_up)
-
-        if process_exists("stratisd") is None:
-            exec_command(["systemctl", "start", "stratisd"])
-            time.sleep(20)
-
-        clean_up()
-
-        time.sleep(1)
-        exec_command(["udevadm", "settle"])
-
-    def _inequality_test(self, result, expected_non_result):
-        """
-        :param object result: the result of a test
-        :param object expected_non_result: a value which the result must
-                                           not match, but which has the
-                                           expected type
-        """
-        self.assertIsInstance(result, type(expected_non_result))
-        self.assertNotEqual(result, expected_non_result)
-
-    def _unittest_command(self, result, expected_return_code):
-        """
-        :param result: a tuple of the (optional) return value, the
-                       return code, and the return message from a
-                       D-Bus call
-        :type result: tuple of object * dbus.UInt16 * str OR tuple
-                      of dbus.UInt16 * str if there is no return value
-        :raises: AssertionError if the actual return code is not
-                 equal to the expected return code
-        """
-        if len(result) == 3:
-            (_, return_code, msg) = result
-        else:
-            (return_code, msg) = result
-
-        self.assertEqual(return_code, expected_return_code, msg=msg)
-
-    def _test_permissions(self, dbus_method, args, permissions):
-        """
-        Test running dbus_method with and without root permissions.
-        :param dbus_method: D-Bus method to be tested
-        :type dbus_method: StratisDbus method
-        :param args: the arguments to be passed to the D-Bus method
-        :type args: list of objects
-        :param bool permissions: True if dbus_method needs root permissions to succeed.
-                                False if dbus_method should succeed without root permissions.
-        """
-        _permissions_flag = False
-
-        euid = os.geteuid()
-        if euid != _ROOT:
-            raise RuntimeError(
-                "This process should be running as root, but the current euid is %d."
-                % euid
-            )
-
-        os.seteuid(_NON_ROOT)
-        StratisDbus.reconnect()
-
-        try:
-            dbus_method(*args)
-        except dbus.exceptions.DBusException as err:
-            if err.get_dbus_name() == "org.freedesktop.DBus.Error.AccessDenied":
-                _permissions_flag = True
-            else:
-                os.seteuid(_ROOT)
-                raise err
-        except Exception as err:
-            os.seteuid(_ROOT)
-            raise err
-
-        os.seteuid(_ROOT)
-        StratisDbus.reconnect()
-
-        dbus_method(*args)
-
-        self.assertEqual(_permissions_flag, permissions)
-
-    def test_get_managed_objects(self):
-        """
-        Test that GetManagedObjects returns a dict w/out failure.
-        """
-        self._inequality_test(StratisDbus.get_managed_objects(), {})
-
-    def test_get_managed_objects_permissions(self):
-        """
-        Test that GetManagedObjects succeeds when root permissions are dropped.
-        """
-        self._test_permissions(StratisDbus.get_managed_objects, [], False)
-
-    def test_stratisd_version(self):
-        """
-        Test getting the daemon version.
-        """
-        self._inequality_test(StratisDbus.stratisd_version(), "")
-
-    def test_stratisd_version_permissions(self):
-        """
-        Test that getting daemon version succeeds when permissions are dropped.
-        """
-        self._test_permissions(StratisDbus.stratisd_version, [], False)
-
-    def test_pool_list_empty(self):
-        """
-        Test listing an non-existent pool.
-        """
-        result = StratisDbus.pool_list()
-        self.assertEqual(result, [])
-
-    def test_pool_list_permissions(self):
-        """
-        Test listing pool succeeds when root permissions are dropped.
-        """
-        self._test_permissions(StratisDbus.pool_list, [], False)
-
-    def test_blockdev_list(self):
-        """
-        Test listing a blockdev.
-        """
-        result = StratisDbus.blockdev_list()
-        self.assertEqual(result, [])
-
-    def test_blockdev_list_permissions(self):
-        """
-        Test that listing blockdevs suceeds when root permissions are dropped.
-        """
-        self._test_permissions(StratisDbus.blockdev_list, [], False)
-
-    def test_filesystem_list_empty(self):
-        """
-        Test listing an non-existent filesystem.
-        """
-        result = StratisDbus.fs_list()
-        self.assertEqual(result, {})
-
-    def test_filesystem_list_permissions(self):
-        """
-        Test that listing filesystem suceeds when root permissions are dropped.
-        """
-        self._test_permissions(StratisDbus.fs_list, [], False)
-
-    def test_key_set_unset(self):
-        """
-        Test setting a key.
-        """
-        key_desc = "test-description"
-
-        with NamedTemporaryFile(mode="w") as temp_file:
-            temp_file.write("test-password")
-            temp_file.flush()
-
-            self._unittest_command(
-                StratisDbus.set_key(key_desc, temp_file), dbus.UInt16(0)
-            )
-
-        self._unittest_command(StratisDbus.unset_key(key_desc), dbus.UInt16(0))
-
-    def test_key_set_unset_permissions(self):
-        """
-        Test setting and unsetting a key fails when root permissions are dropped.
-        """
-        key_desc = "test-description"
-
-        def set_key():
-            """
-            Set up a keyfile and set the value of the key in the kernel
-            keyring.
-            """
-            with NamedTemporaryFile(mode="w") as temp_file:
-                temp_file.write("test-password")
-                temp_file.flush()
-
-                StratisDbus.set_key(key_desc, temp_file)
-
-        self._test_permissions(set_key, [], True)
-
-        self._test_permissions(StratisDbus.unset_key, [key_desc], True)
-
-    def test_pool_create(self):
-        """
-        Test creating a pool.
-        """
-        pool_name = p_n()
-
-        self._unittest_command(
-            StratisDbus.pool_create(pool_name, StratisCertify.DISKS, None),
-            dbus.UInt16(0),
-        )
-
-    def test_pool_create_permissions(self):
-        """
-        Test that creating a pool fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        self._test_permissions(
-            StratisDbus.pool_create, [pool_name, StratisCertify.DISKS, None], True
-        )
-
-    def test_pool_create_encrypted(self):
-        """
-        Test creating an encrypted pool.
-        """
-        with KernelKey("test-password") as key_desc:
-            pool_name = p_n()
-
-            self._unittest_command(
-                StratisDbus.pool_create(pool_name, StratisCertify.DISKS, key_desc),
-                dbus.UInt16(0),
-            )
-
-    def test_pool_add_cache(self):
-        """
-        Test adding cache to a pool.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._unittest_command(
-            StratisDbus.pool_init_cache(pool_path, StratisCertify.DISKS[1:2]),
-            dbus.UInt16(0),
-        )
-        self._unittest_command(
-            StratisDbus.pool_add_cache(pool_path, StratisCertify.DISKS[2:3]),
-            dbus.UInt16(0),
-        )
-
-    def test_pool_add_cache_permissions(self):
-        """
-        Test that adding cache to pool fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._test_permissions(
-            StratisDbus.pool_init_cache, [pool_path, StratisCertify.DISKS[1:2]], True
-        )
-        self._test_permissions(
-            StratisDbus.pool_add_cache, [pool_path, StratisCertify.DISKS[2:3]], True
-        )
-
-    def test_pool_create_after_cache(self):
-        """
-        Test creating existing pool after cache was added
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._unittest_command(
-            StratisDbus.pool_init_cache(pool_path, StratisCertify.DISKS[1:2]),
-            dbus.UInt16(0),
-        )
-        self._unittest_command(
-            StratisDbus.pool_create(pool_name, StratisCertify.DISKS[0:1], None),
-            dbus.UInt16(0),
-        )
-
-    def test_pool_create_with_cache(self):
-        """
-        Test creating existing pool with device already used by cache fails
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._unittest_command(
-            StratisDbus.pool_init_cache(pool_path, StratisCertify.DISKS[1:2]),
-            dbus.UInt16(0),
-        )
-        self._unittest_command(
-            StratisDbus.pool_create(pool_name, StratisCertify.DISKS[0:2], None),
-            dbus.UInt16(1),
-        )
-
-    def test_pool_add_data(self):
-        """
-        Test adding data to a pool.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:2])
-
-        self._unittest_command(
-            StratisDbus.pool_add_data(pool_path, StratisCertify.DISKS[2:3]),
-            dbus.UInt16(0),
-        )
-
-    def test_pool_add_data_permissions(self):
-        """
-        Test that adding data to a pool fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:2])
-
-        self._test_permissions(
-            StratisDbus.pool_add_data, [pool_path, StratisCertify.DISKS[2:3]], True
-        )
-
-    def test_pool_list_not_empty(self):
-        """
-        Test listing an non-existent pool.
-        """
-        pool_name = p_n()
-        make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._inequality_test(StratisDbus.pool_list(), [])
-
-    def test_pool_create_same_name_and_devices(self):
-        """
-        Test creating a pool that already exists with the same devices.
-        """
-        pool_name = p_n()
-        make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._unittest_command(
-            StratisDbus.pool_create(pool_name, StratisCertify.DISKS[0:1], None),
-            dbus.UInt16(0),
-        )
-
-    def test_pool_create_same_name_different_devices(self):
-        """
-        Test creating a pool that already exists with different devices.
-        """
-        pool_name = p_n()
-        make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._unittest_command(
-            StratisDbus.pool_create(pool_name, StratisCertify.DISKS[1:3], None),
-            dbus.UInt16(1),
-        )
-
-    def test_pool_destroy(self):
-        """
-        Test destroying a pool.
-        """
-        pool_name = p_n()
-        make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._unittest_command(StratisDbus.pool_destroy(pool_name), dbus.UInt16(0))
-
-        self.assertEqual(StratisDbus.fs_list(), {})
-
-    def test_pool_destroy_permissions(self):
-        """
-        Test that destroying a pool fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        self._test_permissions(StratisDbus.pool_destroy, [pool_name], True)
-
-    def test_filesystem_create(self):
-        """
-        Test creating a filesystem.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-
-        self._unittest_command(
-            StratisDbus.fs_create(pool_path, fs_name), dbus.UInt16(0)
-        )
-
-    def test_filesystem_create_permissions(self):
-        """
-        Test that creating a filesystem fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-
-        self._test_permissions(StratisDbus.fs_create, [pool_path, fs_name], True)
-
-    def test_filesystem_udev_symlink_create(self):
-        """
-        Test the udev symlink creation for filesystem devices.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        filesystem_path = make_test_filesystem(pool_path, fs_name)
-
-        fsdevdest, fsdevmapperlinkdest = acquire_filesystem_symlink_targets(
-            pool_path, filesystem_path
-        )
-        self.assertEqual(fsdevdest, fsdevmapperlinkdest)
-
-    def test_filesystem_udev_symlink_fsrename(self):
-        """
-        Test the udev symlink creation for filesystem devices after fs rename.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        filesystem_path = make_test_filesystem(pool_path, fs_name)
-
-        fs_name_rename = fs_n()
-
-        self._unittest_command(
-            StratisDbus.fs_rename(fs_name, fs_name_rename), dbus.UInt16(0)
-        )
-        # Settle after rename, to allow udev to recognize the fs rename
-        exec_command(["udevadm", "settle"])
-
-        fsdevdest, fsdevmapperlinkdest = acquire_filesystem_symlink_targets(
-            pool_path, filesystem_path
-        )
-        self.assertEqual(fsdevdest, fsdevmapperlinkdest)
-
-    def test_filesystem_udev_symlink_poolrename(self):
-        """
-        Test the udev symlink creation for filesystem devices after pool rename.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        filesystem_path = make_test_filesystem(pool_path, fs_name)
-
-        pool_name_rename = p_n()
-
-        self._unittest_command(
-            StratisDbus.pool_rename(pool_name, pool_name_rename), dbus.UInt16(0)
-        )
-        # Settle after rename, to allow udev to recognize the fs rename
-        exec_command(["udevadm", "settle"])
-
-        fsdevdest, fsdevmapperlinkdest = acquire_filesystem_symlink_targets(
-            pool_path, filesystem_path
-        )
-        self.assertEqual(fsdevdest, fsdevmapperlinkdest)
-
-    def test_filesystem_udev_symlink_fsrename_poolrename(self):
-        """
-        Test the udev symlink creation for filesystem devices after fs and pool rename.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        filesystem_path = make_test_filesystem(pool_path, fs_name)
-
-        fs_name_rename = fs_n()
-
-        self._unittest_command(
-            StratisDbus.fs_rename(fs_name, fs_name_rename), dbus.UInt16(0)
-        )
-
-        pool_name_rename = p_n()
-
-        self._unittest_command(
-            StratisDbus.pool_rename(pool_name, pool_name_rename), dbus.UInt16(0)
-        )
-        # Settle after rename, to allow udev to recognize the pool rename
-        exec_command(["udevadm", "settle"])
-
-        fsdevdest, fsdevmapperlinkdest = acquire_filesystem_symlink_targets(
-            pool_path, filesystem_path
-        )
-        self.assertEqual(fsdevdest, fsdevmapperlinkdest)
-
-    def test_filesystem_rename(self):
-        """
-        Test renaming a filesystem.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        make_test_filesystem(pool_path, fs_name)
-
-        fs_name_rename = fs_n()
-
-        self._unittest_command(
-            StratisDbus.fs_rename(fs_name, fs_name_rename), dbus.UInt16(0)
-        )
-
-    def test_filesystem_rename_permissions(self):
-        """
-        Test that renaming a filesystem fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        make_test_filesystem(pool_path, fs_name)
-
-        fs_name_rename = fs_n()
-
-        self._test_permissions(StratisDbus.fs_rename, [fs_name, fs_name_rename], True)
-
-    def test_filesystem_rename_same_name(self):
-        """
-        Test renaming a filesystem.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        make_test_filesystem(pool_path, fs_name)
-
-        self._unittest_command(StratisDbus.fs_rename(fs_name, fs_name), dbus.UInt16(0))
-
-    def test_filesystem_snapshot(self):
-        """
-        Test snapshotting a filesystem.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        fs_path = make_test_filesystem(pool_path, fs_name)
-
-        snapshot_name = fs_n()
-
-        self._unittest_command(
-            StratisDbus.fs_snapshot(pool_path, fs_path, snapshot_name), dbus.UInt16(0)
-        )
-
-    def test_filesystem_snapshot_permissions(self):
-        """
-        Test snapshotting a filesystem fails when root permissions are dropped.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        fs_path = make_test_filesystem(pool_path, fs_name)
-
-        snapshot_name = fs_n()
-
-        self._test_permissions(
-            StratisDbus.fs_snapshot, [pool_path, fs_path, snapshot_name], True
-        )
-
-    def test_filesystem_list_not_empty(self):
-        """
-        Test listing an existent filesystem.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        make_test_filesystem(pool_path, fs_name)
-
-        self._inequality_test(StratisDbus.fs_list(), {})
-
-    def test_filesystem_create_same_name(self):
-        """
-        Test creating a filesystem that already exists.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        make_test_filesystem(pool_path, fs_name)
-
-        self._unittest_command(
-            StratisDbus.fs_create(pool_path, fs_name), dbus.UInt16(0)
-        )
-
-    def test_filesystem_destroy(self):
-        """
-        Test destroying a filesystem.
-        """
-        pool_name = p_n()
-        pool_path = make_test_pool(pool_name, StratisCertify.DISKS[0:1])
-
-        fs_name = fs_n()
-        make_test_filesystem(pool_path, fs_name)
-
-        self._unittest_command(
-            StratisDbus.fs_destroy(pool_name, fs_name), dbus.UInt16(0)
-        )
-
-        self.assertEqual(StratisDbus.fs_list(), {})
-
-    def test_get_report(self):
-        """
-        Test getting a valid and invalid report.
-        """
-        (result, return_code, _) = StratisDbus.get_report("errored_pool_report")
-        self._inequality_test(result, dbus.String(""))
-        self.assertEqual(return_code, dbus.UInt16(0))
-        # Test that we have received valid JSON.
-        json.loads(result)
-
-        (result, return_code, _) = StratisDbus.get_report("invalid_report")
-        self.assertEqual(result, dbus.String(""))
-        self._inequality_test(return_code, dbus.UInt16(0))
-
-    def test_get_report_permissions(self):
-        """
-        Test that getting a valid report succeeds when root permissions are dropped.
-        """
-        self._test_permissions(StratisDbus.get_report, ["errored_pool_report"], False)
-
-
-def main():
-    """
-    The main method.
-    """
-    argument_parser = argparse.ArgumentParser()
-    argument_parser.add_argument(
-        "--disk",
-        action="append",
-        dest="DISKS",
-        default=[],
-        help="disks to use, a minimum of 3 in order to run every test",
-    )
-    parsed_args, unittest_args = argument_parser.parse_known_args()
-    StratisCertify.DISKS = parsed_args.DISKS
-    print("Using block device(s) for tests: %s" % StratisCertify.DISKS)
-    unittest.main(argv=sys.argv[:1] + unittest_args)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/blackbox/testlib/__init__.py b/tests/blackbox/testlib/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/blackbox/testlib/dbus.py b/tests/blackbox/testlib/dbus.py
deleted file mode 100644
index e382a2af5..000000000
--- a/tests/blackbox/testlib/dbus.py
+++ /dev/null
@@ -1,510 +0,0 @@
-# Copyright 2019 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-DBus methods for blackbox testing.
-"""
-# isort: STDLIB
-import os
-
-# isort: THIRDPARTY
-import dbus
-
-from .utils import random_string
-
-_TEST_PREF = os.getenv("STRATIS_UT_PREFIX", "STRATI5_DE5TROY_ME1_")
-
-
-def p_n():
-    """
-    Return a random pool name
-    :return: Random String
-    """
-    return _TEST_PREF + "pool" + random_string()
-
-
-def fs_n():
-    """
-    Return a random FS name
-    :return: Random String
-    """
-    return _TEST_PREF + "fs" + random_string()
-
-
-# This function is an exact copy of the get_timeout function in
-# the stratis_cli source code, except that it raises RuntimeError where
-# that function raises StratisCliEnvironmentError.
-# The function should not be imported, as these tests are intended to be
-# run in an environment where the Stratis CLI source code is not certainly
-# available.
-def _get_timeout(value):
-    """
-    Turn an input str or int into a float timeout value.
-
-    :param value: the input str or int
-    :type value: str or int
-    :raises RuntimeError:
-    :returns: float
-    """
-
-    maximum_dbus_timeout_ms = 1073741823
-
-    # Ensure the input str is not a float
-    if isinstance(value, float):
-        raise RuntimeError(
-            "The timeout value provided is a float; it should be an integer."
-        )
-
-    try:
-        timeout_int = int(value)
-
-    except ValueError:
-        raise RuntimeError("The timeout value provided is not an integer.")
-
-    # Ensure the integer is not too small
-    if timeout_int < -1:
-        raise RuntimeError(
-            "The timeout value provided is smaller than the smallest acceptable value, -1."
-        )
-
-    # Ensure the integer is not too large
-    if timeout_int > maximum_dbus_timeout_ms:
-        raise RuntimeError(
-            "The timeout value provided exceeds the largest acceptable value, %s."
-            % maximum_dbus_timeout_ms
-        )
-
-    # Convert from milliseconds to seconds
-    return timeout_int / 1000
-
-
-class StratisDbus:
-    "Wrappers around stratisd DBus calls"
-
-    _OBJECT_MANAGER = "org.freedesktop.DBus.ObjectManager"
-    _BUS = dbus.SystemBus()
-    _BUS_NAME = "org.storage.stratis2"
-    _TOP_OBJECT = "/org/storage/stratis2"
-
-    _MNGR_IFACE = "org.storage.stratis2.Manager.r3"
-    _REPORT_IFACE = "org.storage.stratis2.Report.r1"
-    _POOL_IFACE = "org.storage.stratis2.pool.r3"
-    _FS_IFACE = "org.storage.stratis2.filesystem"
-    _BLKDEV_IFACE = "org.storage.stratis2.blockdev.r2"
-    _FETCH_PROPERTIES_IFACE = "org.storage.stratis2.FetchProperties.r3"
-    POOL_IFACE = _POOL_IFACE
-    FS_IFACE = _FS_IFACE
-
-    _DBUS_TIMEOUT_SECONDS = 120
-    _TIMEOUT = _get_timeout(
-        os.environ.get("STRATIS_DBUS_TIMEOUT", _DBUS_TIMEOUT_SECONDS * 1000)
-    )
-
-    @staticmethod
-    def get_managed_objects():
-        """
-        Get managed objects for stratis
-        :return: A dict,  Keys are object paths with dicts containing interface
-                          names mapped to property dicts.
-                          Property dicts map names to values.
-        """
-        object_manager = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._OBJECT_MANAGER,
-        )
-        return object_manager.GetManagedObjects(timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def stratisd_version():
-        """
-        Get stratisd version
-        :return: The current stratisd version
-        :rtype: str
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            dbus.PROPERTIES_IFACE,
-        )
-        return iface.Get(
-            StratisDbus._MNGR_IFACE, "Version", timeout=StratisDbus._TIMEOUT
-        )
-
-    @staticmethod
-    def pool_list():
-        """
-        Query the pools
-        :return: A list of pool names
-        :rtype: List of str
-        """
-        pool_objects = [
-            obj_data[StratisDbus._POOL_IFACE]
-            for _, obj_data in StratisDbus.get_managed_objects().items()
-            if StratisDbus._POOL_IFACE in obj_data
-            and obj_data[StratisDbus._POOL_IFACE]["Name"].startswith(_TEST_PREF)
-        ]
-
-        return [pool_obj["Name"] for pool_obj in pool_objects]
-
-    @staticmethod
-    def blockdev_list():
-        """
-        Query the blockdevs
-        :return: A list of blockdev names
-        :rtype: List of str
-        """
-        blockdev_objects = [
-            obj_data[StratisDbus._BLKDEV_IFACE]
-            for _, obj_data in StratisDbus.get_managed_objects().items()
-            if StratisDbus._BLKDEV_IFACE in obj_data
-            and obj_data[StratisDbus._BLKDEV_IFACE]["Name"].startswith(_TEST_PREF)
-        ]
-
-        return [blockdev_obj["Name"] for blockdev_obj in blockdev_objects]
-
-    @staticmethod
-    def set_key(key_desc, temp_file):
-        """
-        Set a key
-        :param str key_desc: The key description
-        :param temp_file:
-        """
-        manager_iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._MNGR_IFACE,
-        )
-
-        with open(temp_file.name, "r") as fd_for_dbus:
-            return manager_iface.SetKey(key_desc, fd_for_dbus.fileno(), False)
-
-    @staticmethod
-    def unset_key(key_desc):
-        """
-        Unset a key
-        """
-        manager_iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._MNGR_IFACE,
-        )
-
-        return manager_iface.UnsetKey(key_desc)
-
-    @staticmethod
-    def get_keys():
-        """
-        Return a list of the key descriptions of all keys with a
-        distinguishing Stratis prefix.
-
-        :returns: a list of key descriptions
-        :rtype: list of str
-        :raises RuntimeError: if key descriptions could not be returned
-        """
-        prop_name = "KeyList"
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._FETCH_PROPERTIES_IFACE,
-        )
-        result = iface.GetProperties([prop_name], timeout=StratisDbus._TIMEOUT)
-        (success, key_descriptions) = result[prop_name]
-        if not success:
-            raise RuntimeError(
-                "The daemon encountered an error while getting the Stratis key descriptions: %s"
-                % key_descriptions
-            )
-        return key_descriptions
-
-    @staticmethod
-    def pool_create(pool_name, devices, key_desc):
-        """
-        Create a pool
-        :param str pool_name: The name of the pool to create
-        :param str devices: A list of devices that can be used to create the pool
-        :param key_desc: Key description
-        :type key_desc: str or NoneType
-        :return: The return values of the CreatePool call
-        :rtype: The D-Bus types (b(oao)), q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._MNGR_IFACE,
-        )
-        return iface.CreatePool(
-            pool_name,
-            (dbus.Boolean(False), dbus.UInt16(0)),
-            devices,
-            (True, key_desc) if key_desc is not None else (False, ""),
-            timeout=StratisDbus._TIMEOUT,
-        )
-
-    @staticmethod
-    def pool_destroy(pool_name):
-        """
-        Destroy a pool
-        :param str pool_name: The name of the pool to destroy
-        :return: The object path of the DestroyPool call, or None
-        :rtype: The D-Bus types (bs), q, and s, or None
-        """
-        pool_objects = {
-            path: obj_data[StratisDbus._POOL_IFACE]
-            for path, obj_data in StratisDbus.get_managed_objects().items()
-            if StratisDbus._POOL_IFACE in obj_data
-            and obj_data[StratisDbus._POOL_IFACE]["Name"].startswith(_TEST_PREF)
-        }
-
-        pool_paths = [
-            path
-            for path, pool_obj in pool_objects.items()
-            if pool_obj["Name"] == pool_name
-        ]
-        if pool_paths == []:
-            return None
-
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._MNGR_IFACE,
-        )
-        return iface.DestroyPool(pool_paths[0], timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def fs_list():
-        """
-        Query the file systems
-        :return: A dict,  Key being the fs name, the value being the pool name
-        :rtype: dict mapping str to str
-        """
-        objects = StratisDbus.get_managed_objects().items()
-
-        fs_objects = [
-            obj_data[StratisDbus._FS_IFACE]
-            for _, obj_data in objects
-            if StratisDbus._FS_IFACE in obj_data
-            and obj_data[StratisDbus._FS_IFACE]["Name"].startswith(_TEST_PREF)
-        ]
-
-        pool_path_to_name = {
-            obj: obj_data[StratisDbus._POOL_IFACE]["Name"]
-            for obj, obj_data in objects
-            if StratisDbus._POOL_IFACE in obj_data
-            and obj_data[StratisDbus._POOL_IFACE]["Name"].startswith(_TEST_PREF)
-        }
-
-        return {
-            fs_object["Name"]: pool_path_to_name[fs_object["Pool"]]
-            for fs_object in fs_objects
-        }
-
-    @staticmethod
-    def pool_init_cache(pool_path, devices):
-        """
-        Initialize the cache for a pool with a list of devices.
-        :param str pool_path: The object path of the pool to which the cache device will be added
-        :param str devices: A list of devices that can be initialized as a cache device
-        :return: The return values of the InitCache call
-        :rtype: The D-Bus types (bao), q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_path),
-            StratisDbus._POOL_IFACE,
-        )
-        return iface.InitCache(devices, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def pool_add_cache(pool_path, devices):
-        """
-        Add a block device as a cache device
-        :param str pool_path: The object path of the pool to which the cache device will be added
-        :param str devices: A list of devices that can be added as a cache device
-        :return: The return values of the AddCacheDevs call
-        :rtype: The D-Bus types (bao), q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_path),
-            StratisDbus._POOL_IFACE,
-        )
-        return iface.AddCacheDevs(devices, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def pool_add_data(pool_path, devices):
-        """
-        Add a disk to an existing pool
-        :param str pool_path: The object path of the pool to which the data device will be added
-        :param str devices: A list of devices that can be added as a data device
-        :return: The return values of the AddCacheDevs call
-        :rtype: The D-Bus types (bao), q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_path),
-            StratisDbus._POOL_IFACE,
-        )
-        return iface.AddDataDevs(devices, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def pool_rename(pool_name, pool_name_rename):
-        """
-        Rename a pool
-        :param str pool_name: The name of the pool to be renamed
-        :param str pool_name_rename: The new name that the pool will have
-        :return: The return values of the SetName call, or None
-        :rtype: The D-Bus types (bs), q, and s, or None
-        """
-        objects = StratisDbus.get_managed_objects().items()
-
-        pool_objects = {
-            path: obj_data[StratisDbus._POOL_IFACE]
-            for path, obj_data in objects
-            if StratisDbus._POOL_IFACE in obj_data
-            and obj_data[StratisDbus._POOL_IFACE]["Name"].startswith(_TEST_PREF)
-        }
-
-        pool_paths = [
-            path
-            for path, pool_obj in pool_objects.items()
-            if pool_obj["Name"] == pool_name
-        ]
-        if pool_paths == []:
-            return None
-
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_paths[0]),
-            StratisDbus._POOL_IFACE,
-        )
-        return iface.SetName(pool_name_rename, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def fs_create(pool_path, fs_name):
-        """
-        Create a filesystem
-        :param str pool_path: The object path of the pool in which the filesystem will be created
-        :param str fs_name: The name of the filesystem to create
-        :return: The return values of the CreateFilesystems call
-        :rtype: The D-Bus types (ba(os)), q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_path),
-            StratisDbus._POOL_IFACE,
-        )
-
-        return iface.CreateFilesystems([fs_name], timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def fs_destroy(pool_name, fs_name):
-        """
-        Destroy a filesystem
-        :param str pool_name: The name of the pool which contains the filesystem
-        :param str fs_name: The name of the filesystem to destroy
-        :return: The return values of the DestroyFilesystems call, or None
-        :rtype: The D-Bus types (bas), q, and s, or None
-        """
-        objects = StratisDbus.get_managed_objects().items()
-
-        pool_objects = {
-            path: obj_data[StratisDbus._POOL_IFACE]
-            for path, obj_data in objects
-            if StratisDbus._POOL_IFACE in obj_data
-            and obj_data[StratisDbus._POOL_IFACE]["Name"].startswith(_TEST_PREF)
-        }
-        fs_objects = {
-            path: obj_data[StratisDbus._FS_IFACE]
-            for path, obj_data in objects
-            if StratisDbus._FS_IFACE in obj_data
-            and obj_data[StratisDbus._FS_IFACE]["Name"].startswith(_TEST_PREF)
-        }
-
-        pool_paths = [
-            path
-            for path, pool_obj in pool_objects.items()
-            if pool_obj["Name"] == pool_name
-        ]
-        if pool_paths == []:
-            return None
-
-        fs_paths = [
-            path for path, fs_obj in fs_objects.items() if fs_obj["Name"] == fs_name
-        ]
-        if fs_paths == []:
-            return None
-
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_paths[0]),
-            StratisDbus._POOL_IFACE,
-        )
-        return iface.DestroyFilesystems(fs_paths, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def fs_rename(fs_name, fs_name_rename):
-        """
-        Rename a filesystem
-        :param str fs_name: The name of the filesystem to be renamed
-        :param str fs_name_rename: The new name that the snapshot will have
-        :return: The return values of the SetName call, or None
-        :rtype: The D-Bus types (bs), q, and s, or None
-        """
-        objects = StratisDbus.get_managed_objects().items()
-
-        fs_objects = {
-            path: obj_data[StratisDbus._FS_IFACE]
-            for path, obj_data in objects
-            if StratisDbus._FS_IFACE in obj_data
-            and obj_data[StratisDbus._FS_IFACE]["Name"].startswith(_TEST_PREF)
-        }
-
-        fs_paths = [
-            path for path, fs_obj in fs_objects.items() if fs_obj["Name"] == fs_name
-        ]
-        if fs_paths == []:
-            return None
-
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, fs_paths[0]),
-            StratisDbus._FS_IFACE,
-        )
-        return iface.SetName(fs_name_rename, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def fs_snapshot(pool_path, fs_path, snapshot_name):
-        """
-        Snapshot a filesystem
-        :param str pool_path: The object path of the pool containing the fs
-        :param str fs_name: The object path of the filesystem to snapshot
-        :param str snapshot_name: The name of the snapshot to be made
-        :return: The return values of the SnapshotFilesystem call
-        :rtype: The D-Bus types (bo), q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, pool_path),
-            StratisDbus._POOL_IFACE,
-        )
-        return iface.SnapshotFilesystem(
-            fs_path, snapshot_name, timeout=StratisDbus._TIMEOUT
-        )
-
-    @staticmethod
-    def get_report(report_name):
-        """
-        Get the report with the given name.
-        :param str report_name: The name of the report
-        :return: The JSON report as a string with a status code and string
-        :rtype: The D-Bus types s, q, and s
-        """
-        iface = dbus.Interface(
-            StratisDbus._BUS.get_object(StratisDbus._BUS_NAME, StratisDbus._TOP_OBJECT),
-            StratisDbus._REPORT_IFACE,
-        )
-        return iface.GetReport(report_name, timeout=StratisDbus._TIMEOUT)
-
-    @staticmethod
-    def reconnect():
-        """
-        Close and reopen bus connection.
-        """
-        StratisDbus._BUS.close()
-        StratisDbus._BUS = dbus.SystemBus()
diff --git a/tests/blackbox/testlib/infra.py b/tests/blackbox/testlib/infra.py
deleted file mode 100644
index 264d199eb..000000000
--- a/tests/blackbox/testlib/infra.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# Copyright 2020 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Methods and classes that do infrastructure tasks.
-"""
-# isort: STDLIB
-import base64
-from tempfile import NamedTemporaryFile
-
-from .dbus import StratisDbus
-from .utils import exec_command
-
-
-def umount_mdv():
-    """
-    Locate and umount any stratis mdv mounts
-    :return: None
-    """
-    with open("/proc/self/mounts", "r") as mounts:
-        for line in mounts.readlines():
-            if "/stratis/.mdv-" in line:
-                mountpoint = line.split()[1]
-                exec_command(["umount", mountpoint])
-
-
-def clean_up():
-    """
-    Try to clean up after a test failure.
-
-    :return: None
-    """
-
-    exec_command(["udevadm", "settle"])
-
-    umount_mdv()
-
-    error_strings = []
-
-    def check_result(result, format_str, format_str_args):
-        if result is None:
-            return
-        (_, code, msg) = result
-        if code == 0:
-            return
-        error_strings.append("%s: %s" % ((format_str % format_str_args), msg))
-
-    # Remove FS
-    for name, pool_name in StratisDbus.fs_list().items():
-        check_result(
-            StratisDbus.fs_destroy(pool_name, name),
-            "failed to destroy filesystem %s in pool %s",
-            (name, pool_name),
-        )
-
-    # Remove Pools
-    for name in StratisDbus.pool_list():
-        check_result(StratisDbus.pool_destroy(name), "failed to destroy pool %s", name)
-
-    # Unset all Stratis keys
-    for key in StratisDbus.get_keys():
-        check_result(StratisDbus.unset_key(key), "failed to unset key %s", key)
-
-    # Report an error if any filesystems, pools or keys are found to be
-    # still in residence
-    remnant_filesystems = StratisDbus.fs_list()
-    if remnant_filesystems != {}:
-        error_strings.append(
-            "remnant filesystems: %s"
-            % ", ".join(
-                map(
-                    lambda x: "%s in pool %s" % (x[0], x[1]),
-                    remnant_filesystems.items(),
-                )
-            )
-        )
-
-    remnant_pools = StratisDbus.pool_list()
-    if remnant_pools != []:
-        error_strings.append("remnant pools: %s" % ", ".join(remnant_pools))
-
-    remnant_keys = StratisDbus.get_keys()
-    if remnant_keys != []:
-        error_strings.append("remnant keys: %s" % ", ".join(remnant_keys))
-
-    if error_strings != []:
-        raise RuntimeError("clean_up failed: %s" % "; ".join(error_strings))
-
-
-class KernelKey:  # pylint: disable=attribute-defined-outside-init
-    """
-    A handle for operating on keys in the kernel keyring. The specified key will
-    be available for the lifetime of the test when used with the Python with
-    keyword and will be cleaned up at the end of the scope of the with block.
-    """
-
-    _OK = 0
-
-    def __init__(self, key_data):
-        """
-        Initialize a key with the provided key data (passphrase).
-        :param bytes key_data: The desired key contents
-        """
-        self._key_data = key_data
-
-    def __enter__(self):
-        """
-        This method allows KernelKey to be used with the "with" keyword.
-        :return: The key description that can be used to access the
-                 provided key data in __init__.
-        :raises RuntimeError: if setting the key using the stratisd D-Bus API
-                              returns a non-zero return code
-        """
-        with open("/dev/urandom", "rb") as urandom_f:
-            self._key_desc = base64.b64encode(urandom_f.read(16)).decode("utf-8")
-
-        with NamedTemporaryFile(mode="w") as temp_file:
-            temp_file.write(self._key_data)
-            temp_file.flush()
-
-            (_, return_code, message) = StratisDbus.set_key(self._key_desc, temp_file)
-
-        if return_code != KernelKey._OK:
-            raise RuntimeError(
-                "Setting the key using stratisd failed with an error: %s" % message
-            )
-
-        return self._key_desc
-
-    def __exit__(self, exception_type, exception_value, traceback):
-        message = None
-        try:
-            (_, return_code, message) = StratisDbus.unset_key(self._key_desc)
-
-            if return_code != KernelKey._OK:
-                raise RuntimeError(
-                    "Unsetting the key using stratisd failed with an error: %s"
-                    % message
-                )
-        except Exception as rexc:
-            if exception_value is None:
-                raise rexc
-            raise rexc from exception_value
diff --git a/tests/blackbox/testlib/utils.py b/tests/blackbox/testlib/utils.py
deleted file mode 100644
index 1db1e66c0..000000000
--- a/tests/blackbox/testlib/utils.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright 2019 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Utility functions for blackbox testing.
-"""
-# isort: STDLIB
-import os
-import random
-import string
-from subprocess import PIPE, Popen
-from tempfile import NamedTemporaryFile
-
-# isort: THIRDPARTY
-import psutil
-
-
-def random_string(length=4):
-    """
-    Generates a random string
-    :param length: Length of random string
-    :return: String
-    """
-    return "{0}".format(
-        "".join(random.choice(string.ascii_uppercase) for _ in range(length))
-    )
-
-
-def resolve_symlink(link):
-    """
-    Resolves the destination of a symlink
-    :param link: filename of the link
-    :return: String
-    """
-    return os.path.abspath(os.path.join(os.path.dirname(link), os.readlink(link)))
-
-
-def process_exists(name):
-    """
-    Look through processes, using their pids, to find one matching 'name'.
-    Return None if no such process found, else return the pid.
-    :param name: name of process to check
-    :type name: str
-    :return: pid or None
-    :rtype: int or NoneType
-    """
-    for proc in psutil.process_iter(["name"]):
-        try:
-            if proc.name() == name:
-                return proc.pid
-        except psutil.NoSuchProcess:
-            pass
-
-    return None
-
-
-def exec_command(cmd):
-    """
-    Executes the specified infrastructure command.
-
-    :param cmd: command to execute
-    :type cmd: list of str
-    :returns: standard output
-    :rtype: str
-    :raises RuntimeError: if exit code is non-zero
-    """
-    exit_code, stdout_text, stderr_text = exec_test_command(cmd)
-
-    if exit_code != 0:
-        raise RuntimeError(
-            "exec_command: non-zero exit code: %d\nSTDOUT=%s\nSTDERR=%s"
-            % (exit_code, stdout_text, stderr_text)
-        )
-    return stdout_text
-
-
-def exec_test_command(cmd):
-    """
-    Executes the specified test command
-    :param cmd: Command and arguments as list
-    :type cmd: list of str
-    :returns: (exit code, std out text, std err text)
-    :rtype: triple of int * str * str
-    """
-    process = Popen(cmd, stdout=PIPE, stderr=PIPE, close_fds=True, env=os.environ)
-    result = process.communicate()
-    return (
-        process.returncode,
-        bytes(result[0]).decode("utf-8"),
-        bytes(result[1]).decode("utf-8"),
-    )
-
-
-class RandomKeyTmpFile:
-    """
-    Generate a random passphrase and put it in a temporary file.
-    """
-
-    def __init__(self, key_bytes=32):
-        """
-        Initializer
-
-        :param int key_bytes: the desired length of the key in bytes
-        """
-        self._tmpfile = NamedTemporaryFile("wb")
-        with open("/dev/urandom", "rb") as urandom_f:
-            random_bytes = urandom_f.read(key_bytes)
-            self._tmpfile.write(random_bytes)
-            self._tmpfile.flush()
-
-    def tmpfile_name(self):
-        """
-        Get the name of the temporary file.
-        """
-        return self._tmpfile.name
-
-    def close(self):
-        """
-        Close and delete the temporary file.
-        """
-        self._tmpfile.close()
-
-    def __enter__(self):
-        """
-        For use with the "with" keyword.
-
-        :return str: the path of the file with the random key
-        """
-        return self._tmpfile.name
-
-    def __exit__(self, exc_type, exc_value, traceback):
-        try:
-            self._tmpfile.close()
-        except Exception as error:
-            if exc_value is None:
-                raise error
-
-            raise error from exc_value
diff --git a/tests/whitebox/_misc.py b/tests/whitebox/_misc.py
index 55cb1cfa5..867b5192b 100644
--- a/tests/whitebox/_misc.py
+++ b/tests/whitebox/_misc.py
@@ -18,8 +18,10 @@
 # isort: STDLIB
 import os
 import random
+import signal
 import string
 import subprocess
+import sys
 import time
 import unittest
 
@@ -61,28 +63,45 @@ def setup(self):
         """
         try:
             stratisd_var = os.environ["STRATISD"]
-        except KeyError:
+        except KeyError as err:
             raise RuntimeError(
                 "STRATISD environment variable must be set to absolute path of stratisd executable"
+            ) from err
+        self._stratisd = (  # pylint: disable=attribute-defined-outside-init
+            subprocess.Popen(
+                [os.path.join(stratisd_var), "--sim"],
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                universal_newlines=True,
             )
-        self._stratisd = subprocess.Popen(  # pylint: disable=attribute-defined-outside-init
-            [os.path.join(stratisd_var), "--sim"]
         )
         time.sleep(1)
 
     def teardown(self):
         """
         Stop the stratisd simulator and daemon.
+
+        :return: a tuple of stdout and stderr.
         """
-        self._stratisd.terminate()
-        self._stratisd.wait()
+        self._stratisd.send_signal(signal.SIGINT)
+        return self._stratisd.communicate()
 
     def cleanup(self):
         """
         Stop the daemon if it has been started.
+
+        If the daemon has been started print the daemon log entries.
         """
         if hasattr(self, "_stratisd"):
-            self.teardown()
+            (_, stderrdata) = self.teardown()
+
+            print("", file=sys.stdout, flush=True)
+            print(
+                "Log output from this invocation of stratisd:",
+                file=sys.stdout,
+                flush=True,
+            )
+            print(stderrdata, file=sys.stdout, flush=True)
 
 
 class RunTestCase(unittest.TestCase):
@@ -169,7 +188,22 @@ def setUp(self):
         self._service.setup()
 
 
-RUNNER = run()
+_RUNNER = run()
+
+
+def run_with_delay(command_line_args):
+    """
+    Wait 1/4 of a second before running stratis with the specified
+    command-line arguments.
+
+    :param command_line_args: the command line args to pass
+    :type command_line_args: list of str
+    """
+    time.sleep(0.25)
+    return _RUNNER(command_line_args)
+
+
+RUNNER = run_with_delay
 
 
 class StratisCliTestRunError(AssertionError):
diff --git a/tests/whitebox/integration/logical/test_list.py b/tests/whitebox/integration/logical/test_list.py
index c9e7806f0..44d28282d 100644
--- a/tests/whitebox/integration/logical/test_list.py
+++ b/tests/whitebox/integration/logical/test_list.py
@@ -98,6 +98,19 @@ def test_list(self):
         command_line = ["--propagate", "filesystem", "list", self._POOLNAME]
         TEST_RUNNER(command_line)
 
+    def test_list_unhyphenated(self):
+        """
+        Listing multiple volumes with unhyphenated uuids in a non-empty pool should succeed.
+        """
+        command_line = [
+            "--unhyphenated-uuids",
+            "--propagate",
+            "filesystem",
+            "list",
+            self._POOLNAME,
+        ]
+        TEST_RUNNER(command_line)
+
 
 class List5TestCase(SimTestCase):
     """
diff --git a/tests/whitebox/integration/pool/test_add.py b/tests/whitebox/integration/pool/test_add.py
index cfc58bb63..9d405aa99 100644
--- a/tests/whitebox/integration/pool/test_add.py
+++ b/tests/whitebox/integration/pool/test_add.py
@@ -85,6 +85,18 @@ def setUp(self):
         command_line = ["pool", "create", self._POOLNAME] + self._DEVICES
         RUNNER(command_line)
 
+    def test_relative_path_add_data(self):
+        """
+        Test that no assertion is thrown if path arguments are relative.
+        """
+        command_line = self._MENU + [
+            self._POOLNAME,
+            "../dev",
+            "./fake",
+            "/abc",
+        ]
+        TEST_RUNNER(command_line)
+
     def test_add_data(self):
         """
         Test that adding new devices to data tier succeeds.
@@ -190,6 +202,18 @@ def test_add_cache(self):
         command_line = self._MENU + [self._POOLNAME] + _DEVICE_STRATEGY()
         TEST_RUNNER(command_line)
 
+    def test_relative_path_add_cache(self):
+        """
+        Test that no assertion is thrown if path arguments are relative.
+        """
+        command_line = self._MENU + [
+            self._POOLNAME,
+            "../dev",
+            "./fake",
+            "/abc",
+        ]
+        TEST_RUNNER(command_line)
+
     def test_add_cache_again(self):
         """
         Test that trying to add the same devices twice results in an
diff --git a/tests/whitebox/integration/pool/test_bind.py b/tests/whitebox/integration/pool/test_bind.py
index 1108020fa..33bc0dfe6 100644
--- a/tests/whitebox/integration/pool/test_bind.py
+++ b/tests/whitebox/integration/pool/test_bind.py
@@ -20,7 +20,7 @@
 from stratis_cli._errors import StratisCliEngineError, StratisCliNoChangeError
 
 from .._keyutils import RandomKeyTmpFile
-from .._misc import RUNNER, SimTestCase, device_name_list
+from .._misc import RUNNER, TEST_RUNNER, SimTestCase, device_name_list
 
 _ERROR = StratisCliErrorCodes.ERROR
 _DEVICE_STRATEGY = device_name_list(1, 1)
@@ -46,7 +46,6 @@ def test_bind_when_unencrypted_tang(self):
         command_line = self._MENU + [
             "nbde",
             self._POOLNAME,
-            "fake_key",
             "URL",
             "--thumbprint",
             "fake thumbprint",
@@ -60,7 +59,6 @@ def test_bind_when_unencrypted_tang_trust(self):
         command_line = self._MENU + [
             "nbde",
             self._POOLNAME,
-            "fake_key",
             "URL",
             "--trust-url",
         ]
@@ -73,10 +71,16 @@ def test_bind_when_unencrypted_tpm(self):
         command_line = self._MENU + [
             "tpm2",
             self._POOLNAME,
-            "fake_key",
         ]
         self.check_error(StratisCliEngineError, command_line, _ERROR)
 
+    def test_bind_when_unencrypted_keyring(self):
+        """
+        Binding when unencrypted with keyring should return an error.
+        """
+        command_line = self._MENU + ["keyring", self._POOLNAME, "keydesc"]
+        self.check_error(StratisCliEngineError, command_line, _ERROR)
+
 
 class BindTestCase2(SimTestCase):
     """
@@ -110,7 +114,7 @@ def setUp(self):
         ] + _DEVICE_STRATEGY()
         RUNNER(command_line)
 
-    def test_bind_when_bound(self):
+    def test_bind_when_bound_1(self):
         """
         Binding when encrypted and bound should raise a no change error,
         as the action is assumed to be unintentional.
@@ -118,12 +122,64 @@ def test_bind_when_bound(self):
         command_line = self._MENU + [
             "tpm2",
             self._POOLNAME,
-            "fake_key",
         ]
         RUNNER(command_line)
         command_line = self._MENU + [
             "tpm2",
             self._POOLNAME,
-            "fake_key",
         ]
         self.check_error(StratisCliNoChangeError, command_line, _ERROR)
+
+    def test_bind_when_bound_2(self):
+        """
+        Binding when encrypted already should raise a no change error.
+        """
+        command_line = self._MENU + [
+            "keyring",
+            self._POOLNAME,
+            self._KEY_DESC,
+        ]
+        self.check_error(StratisCliNoChangeError, command_line, _ERROR)
+
+
+class BindTestCase3(SimTestCase):
+    """
+    Test binding when pool is encrypted.
+    """
+
+    _MENU = ["--propagate", "pool", "bind"]
+    _POOLNAME = "poolname"
+    _KEY_DESC = "keydesc"
+
+    def setUp(self):
+        super().setUp()
+        with RandomKeyTmpFile() as fname:
+            command_line = [
+                "--propagate",
+                "key",
+                "set",
+                "--keyfile-path",
+                fname,
+                self._KEY_DESC,
+            ]
+            RUNNER(command_line)
+
+        command_line = [
+            "--propagate",
+            "pool",
+            "create",
+            "--clevis=tpm2",
+            self._POOLNAME,
+        ] + _DEVICE_STRATEGY()
+        RUNNER(command_line)
+
+    def test_bind_when_bound(self):
+        """
+        Binding with keyring when already bound with clevis should succeed.
+        """
+        command_line = self._MENU + [
+            "keyring",
+            self._POOLNAME,
+            self._KEY_DESC,
+        ]
+        TEST_RUNNER(command_line)
diff --git a/tests/whitebox/integration/pool/test_create.py b/tests/whitebox/integration/pool/test_create.py
index 8cc211bf2..e1e2f51e2 100644
--- a/tests/whitebox/integration/pool/test_create.py
+++ b/tests/whitebox/integration/pool/test_create.py
@@ -22,13 +22,33 @@
     StratisCliNameConflictError,
 )
 
-from .._misc import RUNNER, SimTestCase, device_name_list
+from .._misc import RUNNER, TEST_RUNNER, SimTestCase, device_name_list
 
 _DEVICE_STRATEGY = device_name_list(1)
 _DEVICE_STRATEGY_2 = device_name_list(2)
 _ERROR = StratisCliErrorCodes.ERROR
 
 
+class CreateTestCase(SimTestCase):
+    """
+    Test create with relative paths
+    """
+
+    _MENU = ["--propagate", "pool", "create"]
+
+    def test_relative_paths(self):
+        """
+        Verify that no assertion is thrown if path arguments are relative.
+        """
+        command_line = self._MENU + [
+            "some_pool",
+            "../dev",
+            "./fake",
+            "/abc",
+        ]
+        TEST_RUNNER(command_line)
+
+
 class Create3TestCase(SimTestCase):
     """
     Test 'create' on name collision.
@@ -85,3 +105,44 @@ def test_create_same_devices(self):
         """
         command_line = self._MENU + [self._POOLNAME_2] + self._DEVICES
         self.check_error(StratisCliInUseSameTierError, command_line, _ERROR)
+
+
+class Create5TestCase(SimTestCase):
+    """
+    Test creating with Clevis options.
+    """
+
+    _POOLNAME = "deadpool1"
+    _MENU = ["--propagate", "pool", "create"]
+    _DEVICES = _DEVICE_STRATEGY_2()
+
+    def test_create_tpm(self):
+        """
+        Test that creating with tpm2 does something reasonable.
+        """
+        command_line = self._MENU + [self._POOLNAME] + self._DEVICES + ["--clevis=tpm2"]
+        TEST_RUNNER(command_line)
+
+    def test_create_tang_1(self):
+        """
+        Test that creating with tang does something reasonable.
+        """
+        command_line = (
+            self._MENU
+            + [self._POOLNAME]
+            + self._DEVICES
+            + ["--clevis=tang", "--trust-url", "--tang-url=http"]
+        )
+        TEST_RUNNER(command_line)
+
+    def test_create_tang_2(self):
+        """
+        Test that creating with tang does something reasonable.
+        """
+        command_line = (
+            self._MENU
+            + [self._POOLNAME]
+            + self._DEVICES
+            + ["--clevis=tang", "--thumbprint=print", "--tang-url=http"]
+        )
+        TEST_RUNNER(command_line)
diff --git a/tests/whitebox/integration/pool/test_destroy.py b/tests/whitebox/integration/pool/test_destroy.py
index 94bdbd269..d7da9749d 100644
--- a/tests/whitebox/integration/pool/test_destroy.py
+++ b/tests/whitebox/integration/pool/test_destroy.py
@@ -102,6 +102,6 @@ def test_with_filesystem_removed(self):
         This should succeed since the filesystem is removed first.
         """
         command_line = ["filesystem", "destroy", self._POOLNAME, self._VOLNAME]
-        TEST_RUNNER(command_line)
+        RUNNER(command_line)
         command_line = self._MENU + [self._POOLNAME]
         TEST_RUNNER(command_line)
diff --git a/tests/whitebox/integration/pool/test_init_cache.py b/tests/whitebox/integration/pool/test_init_cache.py
index 1500683fd..8a50fc679 100644
--- a/tests/whitebox/integration/pool/test_init_cache.py
+++ b/tests/whitebox/integration/pool/test_init_cache.py
@@ -167,3 +167,15 @@ def test_init_cache(self):
         """
         command_line = self._MENU + [self._POOLNAME] + _DEVICE_STRATEGY()
         TEST_RUNNER(command_line)
+
+    def test_relative_paths(self):
+        """
+        Verify that no assertion is thrown if path arguments are relative.
+        """
+        command_line = self._MENU + [
+            self._POOLNAME,
+            "../dev",
+            "./fake",
+            "/abc",
+        ]
+        TEST_RUNNER(command_line)
diff --git a/tests/whitebox/integration/pool/test_list.py b/tests/whitebox/integration/pool/test_list.py
index 277cb4050..cc7672fb0 100644
--- a/tests/whitebox/integration/pool/test_list.py
+++ b/tests/whitebox/integration/pool/test_list.py
@@ -67,6 +67,13 @@ def test_list(self):
         command_line = self._MENU
         TEST_RUNNER(command_line)
 
+    def test_list_unhyphenated(self):
+        """
+        Test listing with unhyphenated-uuids flag
+        """
+        command_line = ["--unhyphenated-uuids"] + self._MENU
+        TEST_RUNNER(command_line)
+
     def test_list_default(self):
         """
         Test default listing action when "list" is not specified.
diff --git a/tests/whitebox/integration/pool/test_unbind.py b/tests/whitebox/integration/pool/test_unbind.py
index 73c7c8210..35134e21f 100644
--- a/tests/whitebox/integration/pool/test_unbind.py
+++ b/tests/whitebox/integration/pool/test_unbind.py
@@ -43,7 +43,7 @@ def test_unbind_when_unencrypted(self):
         """
         Unbinding when unencrypted should return an error.
         """
-        command_line = self._MENU + [self._POOLNAME]
+        command_line = self._MENU + ["clevis", self._POOLNAME]
         self.check_error(StratisCliEngineError, command_line, _ERROR)
 
 
@@ -84,7 +84,7 @@ def test_unbind_when_unbound(self):
         Unbinding when encrypted but unbound should raise a no change error,
         as the action is assumed to be unintentional.
         """
-        command_line = self._MENU + [self._POOLNAME]
+        command_line = self._MENU + ["clevis", self._POOLNAME]
         self.check_error(StratisCliNoChangeError, command_line, _ERROR)
 
     def test_unbind_when_bound(self):
@@ -97,10 +97,9 @@ def test_unbind_when_bound(self):
             "bind",
             "nbde",
             self._POOLNAME,
-            "fake_key",
             "URL",
             "--trust-url",
         ]
         RUNNER(command_line)
-        command_line = self._MENU + [self._POOLNAME]
+        command_line = self._MENU + ["clevis", self._POOLNAME]
         TEST_RUNNER(command_line)
diff --git a/tests/whitebox/integration/report/test_get_report.py b/tests/whitebox/integration/report/test_get_report.py
index cad0b5893..4890782ef 100644
--- a/tests/whitebox/integration/report/test_get_report.py
+++ b/tests/whitebox/integration/report/test_get_report.py
@@ -17,7 +17,6 @@
 
 # isort: LOCAL
 from stratis_cli import StratisCliErrorCodes
-from stratis_cli._errors import StratisCliEngineError
 
 from .._misc import TEST_RUNNER, SimTestCase
 
@@ -26,7 +25,7 @@
 
 class ReportTestCase(SimTestCase):
     """
-    Test getting the errored pool and a nonexistent report"
+    Test getting the errored pool, engine state, and a nonexistent report
     """
 
     _MENU = ["--propagate", "report"]
@@ -37,8 +36,14 @@ def test_report(self):
         """
         TEST_RUNNER(self._MENU + ["errored_pool_report"])
 
-    def test_nonexistent_report(self):
+    def test_report_no_name(self):
         """
-        Test getting nonexistent report.
+        Test getting engine state report when no name specified.
         """
-        self.check_error(StratisCliEngineError, self._MENU + ["notreport"], _ERROR)
+        TEST_RUNNER(self._MENU)
+
+    def test_engine_state_report(self):
+        """
+        Test getting engine state report.
+        """
+        TEST_RUNNER(self._MENU + ["engine_state_report"])
diff --git a/tests/whitebox/integration/test_parser.py b/tests/whitebox/integration/test_parser.py
index 2107f3256..6c762fcd3 100644
--- a/tests/whitebox/integration/test_parser.py
+++ b/tests/whitebox/integration/test_parser.py
@@ -17,6 +17,10 @@
 
 # isort: LOCAL
 from stratis_cli import StratisCliErrorCodes
+from stratis_cli._errors import (
+    StratisCliMissingClevisTangURLError,
+    StratisCliMissingClevisThumbprintError,
+)
 
 from ._misc import RUNNER, RunTestCase, SimTestCase
 
@@ -84,10 +88,39 @@ def test_redundancy(self):
         for prefix in [[], ["--propagate"]]:
             self.check_system_exit(prefix + command_line, _PARSE_ERROR)
 
+    def test_nonexistent_report(self):
+        """
+        Test getting nonexistent report.
+        """
+        command_line = ["report", "notreport"]
+        for prefix in [[], ["--propagate"]]:
+            self.check_system_exit(prefix + command_line, _PARSE_ERROR)
+
+    def test_create_with_clevis_1(self):
+        """
+        Test parsing when creating a pool w/ clevis tang, a URL, but both
+        thumbprint and --trust-url set.
+        """
+        command_line = [
+            "pool",
+            "create",
+            "--clevis=tang",
+            "--tang-url=url",
+            "--thumbprint=jkj",
+            "--trust-url",
+            "pn",
+            "/dev/n",
+        ]
+        for prefix in [[], ["--propagate"]]:
+            self.check_system_exit(prefix + command_line, _PARSE_ERROR)
+
 
 class ParserSimTestCase(SimTestCase):
     """
     Parser tests which require the sim engine to be running.
+
+    Includes tests which are not strictly parser errors, i.e., where the
+    command-line is grammatical, but the command is simply invalid.
     """
 
     def test_stratis_list_default(self):
@@ -98,3 +131,33 @@ def test_stratis_list_default(self):
         for subcommand in [["pool"], ["filesystem"], ["blockdev"]]:
             for prefix in [[], ["--propagate"]]:
                 self.assertEqual(RUNNER(prefix + subcommand), 0)
+
+    def test_create_with_clevis_1(self):
+        """
+        Test parsing when creating a pool w/ clevis tang but no URL.
+        """
+        command_line = [
+            "--propagate",
+            "pool",
+            "create",
+            "--clevis=tang",
+            "pn",
+            "/dev/n",
+        ]
+        self.check_error(StratisCliMissingClevisTangURLError, command_line, 1)
+
+    def test_create_with_clevis_2(self):
+        """
+        Test parsing when creating a pool w/ clevis tang, a URL, but no
+        thumbprint or trust-url.
+        """
+        command_line = [
+            "--propagate",
+            "pool",
+            "create",
+            "--clevis=tang",
+            "--tang-url=url",
+            "pn",
+            "/dev/n",
+        ]
+        self.check_error(StratisCliMissingClevisThumbprintError, command_line, 1)
diff --git a/tests/whitebox/unittest/test_timeout.py b/tests/whitebox/unittest/test_timeout.py
index f15cefe54..40c5eb6ac 100644
--- a/tests/whitebox/unittest/test_timeout.py
+++ b/tests/whitebox/unittest/test_timeout.py
@@ -20,7 +20,7 @@
 
 # isort: LOCAL
 from stratis_cli import StratisCliEnvironmentError
-from stratis_cli._actions._utils import get_timeout
+from stratis_cli._actions._environment import get_timeout
 
 
 class TimeoutTestCase(unittest.TestCase):