#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)## Copyright (C) 2021 Red Hat, Inc.# SPDX-License-Identifier: LGPL-2.1-or-laterimport subprocessimport packagelibimport storagelibimport testlibPV_SIZE = 4000 # 4 GB in MB# List of images that support Stratis and create V1 pools by# default. When this gets empty, Cockpit itself can be cleaned up to# drop support for Stratis API revisions before "r8".#V1_POOL_IMAGES = ["rhel-9-10", "rhel-9-9", "rhel-9-8", "rhel-9-7", "centos-9-bootc", "centos-9", "fedora-eln"]def get_stratis_stop_type_opt(execute):"""Get `stratis stop pool` required option for a pool nameThe CLI changed in an incompatible way in Fedora 40, it needs an extra --name optionwhich cannot be provided in earlier versions."""try:if '--name' in execute("stratis pool stop --help"):return "--name"except subprocess.CalledProcessError:# on RHEL 8 this fails with "error: invalid choice"passreturn ""def create_pool_key(machine, keyname, passphrase):# this is a bit complicated, see https://bugzilla.redhat.com/show_bug.cgi?id=2246923machine.execute(f"echo -n '{passphrase}' | stratis key set --keyfile-path /dev/stdin {keyname}")def create_legacy_pool(machine, disks, name="pool0", keydesc=None, tang=None):keydesc_opt = "--key-desc " + keydesc if keydesc else ""clevis_opt = "--clevis tang --trust-url --tang-url " + tang if tang else ""machine.execute(f"yes | stratisd-tools stratis-legacy-pool {keydesc_opt} {clevis_opt} {name} {' '.join(disks)}")testlib.wait(lambda: name in machine.execute("stratis pool list"))@testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "arch")@testlib.skipImage("stratis not installed", "*-bootc")@testlib.skipImage("commit 817c957899a4 removed Stratis 2 support", "rhel-8-*")@testlib.nondestructiveclass TestStorageStratis(storagelib.StorageCase):def setUp(self):super().setUp()exe = self.machine.executeexe("systemctl start stratisd")self.addCleanup(exe, "systemctl stop stratisd")self.stop_type_opt = get_stratis_stop_type_opt(exe)def tearDown(self):# we need to clean up stratis *first*, before add_loopback_disk()'s addCleanup() handlers# tearDown() runs before addCleanup()exe = self.machine.executeexe("mount | grep mapper/stratis | awk '{print $1}' | xargs --no-run-if-empty umount")exe(f"stratis report | jq -r '.pools[] | .name' | xargs -n1 --no-run-if-empty stratis pool stop {self.stop_type_opt}")exe("stratis report | jq -r '.pools[] | .name' | xargs -n1 --no-run-if-empty stratis pool destroy")exe("dmsetup ls | cut -f1 | grep ^stratis | xargs -n1 --no-run-if-empty dmsetup remove --force")super().tearDown()def testBasic(self, legacy=False):m = self.machineb = self.browserself.login_and_go("/storage")# use fixed names, to avoid grabbing loop1 and loop12 (which losetup sometimes likes to do)# as that clashes with :contains(loop1) below; also, fix names for pixel testdev_1 = self.add_loopback_disk(PV_SIZE, name="loop10")dev_2 = self.add_loopback_disk(PV_SIZE, name="loop11")dev_3 = self.add_loopback_disk(PV_SIZE, name="loop12")dev_4 = self.add_loopback_disk(PV_SIZE, name="loop13")dev_5 = self.add_loopback_disk(PV_SIZE, name="loop14")b.wait_visible(self.card_row("Storage", name=dev_1))b.wait_visible(self.card_row("Storage", name=dev_2))b.wait_visible(self.card_row("Storage", name=dev_3))b.wait_visible(self.card_row("Storage", name=dev_4))b.wait_visible(self.card_row("Storage", name=dev_5))# Create a poolif not legacy:self.dialog_open_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_is_present('disks', dev_2) andself.dialog_check({"name": "pool0"})))self.dialog_set_val("disks", {dev_1: True, dev_2: True})b.assert_pixels("#dialog", "create-pool")self.dialog_apply()self.dialog_wait_close()else:create_legacy_pool(m, disks=[dev_1, dev_2])b.wait_visible(self.card_row("Storage", name="pool0"))b.wait_not_present(self.card_row("Storage", name="pool0") + " .ct-icon-exclamation-triangle")# Check that the next name is "pool1"self.click_devices_dropdown("Create Stratis pool")self.dialog_wait_open()self.dialog_wait_val("name", "pool1")self.dialog_cancel()self.dialog_wait_close()# Stop the poolm.execute(f"stratis pool stop {self.stop_type_opt} pool0")b.wait_in_text(self.card_row("Storage", name="pool0"), "Stratis pool (stopped)")# Start itself.click_dropdown(self.card_row("Storage", name="pool0"), "Start")b.wait_in_text(self.card_row("Storage", name="pool0"), "Stratis filesystems")self.click_card_row("Storage", name="pool0")b.wait_text(self.card_desc("Stratis pool", "Name"), "pool0")b.wait_in_text(self.card_desc("Stratis pool", "Usage"), "8 GB")b.wait_not_present('.pf-v6-c-alert')udisk_contains_stratis_private = "physical-originsub" in m.execute("udisksctl dump")# Create two filesystemsb.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog_wait_open()self.dialog_set_val('name', 'fsys1')self.dialog_set_val('mount_point', '/run/fsys1')if not legacy:b.assert_pixels("#dialog", "create-fsys")self.dialog_apply()self.dialog_wait_close()self.addCleanupMount("/run/fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1")self.assertEqual(self.inode(m.execute("findmnt -n -o SOURCE /run/fsys1").strip()),self.inode("/dev/stratis/pool0/fsys1"))b.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog({'name': 'fsys2','mount_point': '/run/fsys2'})self.addCleanupMount("/run/fsys2")b.wait_text(self.card_row_col("Stratis filesystems", 2, 1), "fsys2")b.wait_text(self.card_row_col("Stratis filesystems", 2, 3), "/run/fsys2")if not legacy:b.assert_pixels(self.card("Stratis filesystems"), "fsys-rows")self.assertEqual(self.inode(m.execute("findmnt -n -o SOURCE /run/fsys2").strip()),self.inode("/dev/stratis/pool0/fsys2"))m.write("/run/fsys2/FILE", "Hello Stratis!")# Check that they have entries in fstabself.assertNotEqual(m.execute("grep /run/fsys1 /etc/fstab"), "")self.assertNotEqual(m.execute("grep /run/fsys2 /etc/fstab"), "")# Rename one filesystemself.click_card_row("Stratis filesystems", 1)b.click(self.card_desc_action("Stratis filesystem", "Name"))self.dialog({'name': "fsys1-renamed"})b.wait_text(self.card_desc("Stratis filesystem", "Name"), "fsys1-renamed")b.click(self.card_parent_link())b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1-renamed")# Destroy one filesystemself.click_card_row("Stratis filesystems", 1)self.click_card_dropdown("Stratis filesystem", "Delete")self.dialog_wait_open()if not legacy:b.assert_pixels("#dialog", "delete-fsys")self.dialog_apply()self.dialog_wait_close()b.wait_visible(self.card("Stratis filesystems"))b.wait_not_present(self.card_row("Stratis filesystems", name="fsys1-renamed"))# Unmount and remount the other filesystemself.click_dropdown(self.card_row("Stratis filesystems", 1), "Unmount")self.confirm()b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys2 (not mounted)")self.click_dropdown(self.card_row("Stratis filesystems", 1), "Mount")self.dialog({})b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys2")# Make a copy of the filesystemself.click_dropdown(self.card_row("Stratis filesystems", 1), "Snapshot")self.dialog_wait_open()self.dialog_set_val('name', 'fsys2-copy')self.dialog_set_val('mount_point', '/run/fsys2-copy')self.dialog_set_val('at_boot', 'never')if not legacy:b.assert_pixels("#dialog", "copy-fsys")self.dialog_apply()self.dialog_wait_close()b.wait_text(self.card_row_col("Stratis filesystems", 2, 1), "fsys2-copy")b.wait_text(self.card_row_col("Stratis filesystems", 2, 3), "/run/fsys2-copy")self.assertEqual("Hello Stratis!", m.execute("cat /run/fsys2-copy/FILE"))# Delete the copyself.click_card_row("Stratis filesystems", 2)self.click_card_dropdown("Stratis filesystem", "Delete")self.confirm()b.wait_visible(self.card("Stratis filesystems"))b.wait_not_present(self.card_row("Stratis filesystems", name="fsys2-copy"))# Make an unmounted copy of the filesystemself.click_dropdown(self.card_row("Stratis filesystems", 1), "Snapshot")self.dialog_wait_open()self.dialog_set_val('name', 'fsys2-copy')self.dialog_set_val('at_boot', 'never')self.dialog_apply_secondary()self.dialog_wait_close()b.wait_text(self.card_row_col("Stratis filesystems", 2, 1), "fsys2-copy")b.wait_text(self.card_row_col("Stratis filesystems", 2, 3), "(not mounted)")# Delete the copyself.click_card_row("Stratis filesystems", 2)self.click_card_dropdown("Stratis filesystem", "Delete")self.confirm()b.wait_visible(self.card("Stratis filesystems"))b.wait_not_present(self.card_row("Stratis filesystems", name="fsys2-copy"))# Create an unmounted filesystemb.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog_wait_open()self.dialog_set_val('name', 'fsys-unmounted')self.dialog_apply_secondary()self.dialog_wait_close()b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys-unmounted")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "(not mounted)")# Delete the unmounted filesystemself.click_card_row("Stratis filesystems", 1)self.click_card_dropdown("Stratis filesystem", "Delete")self.confirm()b.wait_visible(self.card("Stratis filesystems"))b.wait_not_present(self.card_row("Stratis filesystems", name="fsys-unmounted"))# Add a data blockdevb.click(self.card_button("Stratis pool", "Add block device"))self.dialog_wait_open()self.dialog_apply()self.dialog_wait_error("disks", "At least one")self.dialog_set_val('disks', {dev_3: True})if not legacy:b.assert_pixels("#dialog", "add-disk")self.dialog_apply()self.dialog_wait_close()b.wait_visible(self.card_row("Stratis pool", name=dev_3))b.wait_in_text(self.card_desc("Stratis pool", "Usage"), "12 GB")# Add a cache blockdevb.click(self.card_button("Stratis pool", "Add block device"))self.dialog({'tier': "cache",'disks': {dev_4: True}})b.wait_in_text(self.card_row("Stratis pool", name=dev_4), "cache")# Add a second cache blockdev, this uses a different code pathb.click(self.card_button("Stratis pool", "Add block device"))self.dialog({'tier': "cache",'disks': {dev_5: True}})b.wait_in_text(self.card_row("Stratis pool", name=dev_5), "cache")# Rename the poolb.click(self.card_desc_action("Stratis pool", "Name"))self.dialog({'name': "pool0-renamed"})b.wait_text(self.card_desc("Stratis pool", "Name"), "pool0-renamed")# Create another filesystemb.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog({'name': 'fsys3','mount_point': '/run/fsys3'})b.wait_text(self.card_row_col("Stratis filesystems", 2, 1), "fsys3")b.wait_text(self.card_row_col("Stratis filesystems", 2, 3), "/run/fsys3")self.assertEqual(self.inode(m.execute("findmnt -n -o SOURCE /run/fsys3").strip()),self.inode("/dev/stratis/pool0-renamed/fsys3"))# Destroy the poolself.click_card_dropdown("Stratis pool", "Delete")self.dialog_wait_open()if not legacy:b.assert_pixels('#dialog', "delete-pool")self.dialog_apply()self.dialog_wait_close()b.wait_visible(self.card("Storage"))b.wait_not_present(self.card_row("Storage", name="pool0-renamed"))# Check that the entries have disappeared from fstabself.assertEqual(m.execute("grep /run/fsys1 /etc/fstab || true"), "")self.assertEqual(m.execute("grep /run/fsys2 /etc/fstab || true"), "")self.assertEqual(m.execute("grep /run/fsys3 /etc/fstab || true"), "")m.execute("! findmnt /run/fsys1")m.execute("! findmnt /run/fsys2")m.execute("! findmnt /run/fsys2-copy")m.execute("! findmnt /run/fsys3")# https://bugzilla.redhat.com/show_bug.cgi?id=2183084# Do this assertion in the end so that the previous checks still run.# After the stratis pool is deleted we can't check this, so use the value from earlier.self.assertFalse(udisk_contains_stratis_private)@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testBasicLegacy(self):self.testBasic(legacy=True)@testlib.skipImage("Stratis too old", "rhel-8-*")def testAlerts(self):m = self.machineb = self.browserself.login_and_go("/storage")dev_1 = self.add_loopback_disk(PV_SIZE)dev_2 = self.add_loopback_disk(PV_SIZE)b.wait_visible(self.card_row("Storage", name=dev_1))b.wait_visible(self.card_row("Storage", name=dev_2))# Create an encrypted V1 pool with two block devicesif m.image not in V1_POOL_IMAGES:create_pool_key(m, "pool0", "foodeeboodeebar")create_legacy_pool(m, disks=[dev_1, dev_2], keydesc="pool0")m.execute("stratis key unset pool0")else:self.click_devices_dropdown("Create Stratis pool")self.dialog_wait_open()self.dialog_set_val("encrypt_pass.on", val=True)self.dialog_set_val("passphrase", "foodeeboodeebar")self.dialog_set_val("passphrase2", "foodeeboodeebar")self.dialog_set_val("disks", {dev_1: True})self.dialog_set_val("disks", {dev_2: True})self.dialog_apply()self.dialog_wait_close()b.wait_visible(self.card_row("Storage", name="pool0"))b.wait_not_present(self.card_row("Storage", name="pool0") + " .ct-icon-exclamation-triangle")# Check that there is no alert on the details pageself.click_card_row("Storage", name="pool0")b.wait_visible(self.card("Encrypted Stratis pool"))b.wait_not_present('.pf-v6-c-alert')m.execute(f"""JSON=$(sudo cryptsetup token export --token-id=1 {dev_1} \| jq '.key_description = "stratis-1-key-no-other-is-the-same"')sudo cryptsetup token remove --token-id=1 {dev_1}echo $JSON | sudo cryptsetup token import --token-id=1 {dev_1}systemctl restart stratisd""")b.go('#/')b.wait_visible(self.card_row("Storage", name="pool0") + " .ct-icon-exclamation-triangle")self.click_card_row("Storage", name="pool0")b.wait_visible('.pf-v6-c-alert:contains("This pool is in a degraded state")')b.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog_wait_open()self.dialog_set_val("name", "fsys1")self.dialog_set_val("mount_point", "/run/fsys1")self.dialog_apply()self.dialog_wait_alert("Pool is in state NoRequests where this action cannot be performed until the issue is resolved manually")@testlib.nondestructivedef testCli(self):m = self.machineb = self.browserself.login_and_go("/storage")dev_1 = self.add_loopback_disk(PV_SIZE)dev_2 = self.add_loopback_disk(PV_SIZE)b.wait_visible(self.card_row("Storage", name=dev_1))b.wait_visible(self.card_row("Storage", name=dev_2))# Create a pool outside of Cockpitm.execute(f"stratis pool create TEST1 {dev_1} {dev_2}")b.wait_visible(self.card_row("Storage", name="TEST1"))b.wait_in_text(self.card_row("Storage", name=dev_1), "Stratis block device")b.wait_in_text(self.card_row("Storage", name=dev_1), "TEST1")b.wait_in_text(self.card_row("Storage", name=dev_2), "Stratis block device")b.wait_in_text(self.card_row("Storage", name=dev_1), "TEST1")# Create two filesystems outside of Cockpitm.execute("stratis filesystem create TEST1 fsys1")b.wait_visible(self.card_row("Storage", name="fsys1"))m.execute("stratis filesystem create TEST1 fsys2")b.wait_visible(self.card_row("Storage", name="fsys2"))mount = f"{self.vm_tmpdir}/fsys1"# Mount externally, adjust fstab with Cockpitself.click_card_row("Storage", name="fsys1")m.execute(f"mkdir {mount}; mount /dev/stratis/TEST1/fsys1 {mount}")b.click(self.card_button("Stratis filesystem", f"Mount automatically on {mount} on boot"))b.wait_not_present(self.card_button("Stratis filesystem", f"Mount automatically on {mount} on boot"))self.assertIn("stratis-fstab-setup", m.execute(f"grep {mount} /etc/fstab"))# Unmount externally, adjust fstab with Cockpitm.execute(f"umount {mount}")b.click(self.card_button("Stratis filesystem", "Do not mount automatically on boot"))b.wait_not_present(self.card_button("Stratis filesystem", "Do not mount automatically on boot"))self.assertIn("noauto", m.execute(f"grep {mount} /etc/fstab"))# Destroy them outside of Cockpitb.click(self.card_parent_link())b.wait_visible(self.card("Stratis filesystems"))m.execute("stratis filesystem destroy TEST1 fsys1")b.wait_not_present(self.card_row("Stratis filesystems", name="fsys1"))m.execute("stratis filesystem destroy TEST1 fsys2")b.wait_not_present(self.card_row("Stratis filesystems", name="fsys2"))# Destroy the pool outside of Cockpitm.execute("stratis pool destroy TEST1")b.wait_in_text("main", "Not found")b.go("#/")b.wait_visible(self.card("Storage"))b.wait_not_present(self.card_row("Storage", name="TEST1"))def testOverprovisioning(self, legacy=False):b = self.browserself.login_and_go("/storage")dev_1 = self.add_loopback_disk(PV_SIZE)b.wait_visible(self.card_row("Storage", name=dev_1))# Create a pool with overprov offif not legacy:self.dialog_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})),values={"disks": {dev_1: True},"overprov.on": False})else:create_legacy_pool(self.machine, disks=[dev_1])self.machine.execute("stratis pool overprovision pool0 no")self.click_card_row("Storage", name="pool0")overprov_toggle = "input[aria-label='Allow overprovisioning']"b.wait_visible(f"{overprov_toggle}:not(:checked)")# Create filesystem with defaults. Initial virtual size# should be appropriate.b.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog_wait_open()self.dialog_wait_val('set_custom_size.enabled', val=True)self.dialog_set_val('name', 'fsys1')self.dialog_apply_secondary()self.dialog_wait_close()# Snapshot should be disabled nowself.check_dropdown_action_disabled(self.card_row("Stratis filesystems", name="fsys1"), "Snapshot", "Not enough free space")# Switch overprov onb.click(overprov_toggle)b.wait_visible(f"{overprov_toggle}:checked")# Snapshot succeedsself.click_dropdown(self.card_row("Stratis filesystems", name="fsys1"), "Snapshot")self.dialog_wait_open()self.dialog_set_val('name', 'snap1')self.dialog_apply_secondary()self.dialog_wait_close()# Filesystem creation does not ask for file size by defaultb.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog_wait_open()self.dialog_wait_val('set_custom_size.enabled', val=False)self.dialog_cancel()self.dialog_wait_close()# Turning overprov off should be disabled nowb.wait_visible(overprov_toggle + "[disabled]")# Delete snapshotself.click_dropdown(self.card_row("Stratis filesystems", name="snap1"), "Delete")self.confirm()# Switch overprov offb.click(overprov_toggle)b.wait_visible(f"{overprov_toggle}:not(:checked)")@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testOverprovisioningLegacy(self):self.testOverprovisioning(legacy=True)def testFilesystemSizes(self, legacy=False):b = self.browserself.login_and_go("/storage")dev_1 = self.add_loopback_disk(PV_SIZE)b.wait_visible(self.card_row("Storage", name=dev_1))# Create a poolif not legacy:self.dialog_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})),values={"disks": {dev_1: True}})else:create_legacy_pool(self.machine, disks=[dev_1])self.click_card_row("Storage", name="pool0")# Create filesystem with initial virtual size and a limitb.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog_wait_open()self.dialog_set_val('name', 'fsys1')self.dialog_set_val('set_custom_size.enabled', val=True)self.dialog_set_val('size', 800)self.dialog_set_val('set_custom_limit.enabled', val=True)self.dialog_set_val('limit', 1600)self.dialog_apply_secondary()self.dialog_wait_close()self.click_card_row("Stratis filesystems", name="fsys1")b.wait_text(self.card_desc("Stratis filesystem", "Virtual size"), "800 MB")b.wait_text(self.card_desc("Stratis filesystem", "Virtual size limit"), "1.60 GB")# Raise the limitb.click(self.card_desc_action("Stratis filesystem", "Virtual size limit"))self.dialog_wait_open()self.dialog_wait_val('size_options.custom_limit', val=True)self.dialog_wait_val('limit', 1600)self.dialog_set_val('limit', 3200)self.dialog_apply()self.dialog_wait_close()b.wait_text(self.card_desc("Stratis filesystem", "Virtual size limit"), "3.20 GB")# Remove limitb.click(self.card_desc_action("Stratis filesystem", "Virtual size limit"))self.dialog_wait_open()self.dialog_set_val('size_options.custom_limit', val=False)self.dialog_apply()self.dialog_wait_close()b.wait_text(self.card_desc("Stratis filesystem", "Virtual size limit"), "none")@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testFilesystemSizesLegacy(self):self.testFilesystemSizes(legacy=True)@testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "arch")@testlib.skipImage("stratis not installed", "*-bootc")@testlib.skipImage("commit 817c957899a4 removed Stratis 2 support", "rhel-8-*")class TestStorageStratisReboot(storagelib.StorageCase):def setUp(self):super().setUp()exe = self.machine.executeif self.image == "arch":# Arch Linux does not enable systemd units by defaultexe("systemctl enable --now stratisd")self.addCleanup(self.machine.execute, "systemctl disable --now stratisd")# these tests rebootself.allow_restart_journal_messages()def testEncrypted(self, legacy=False):m = self.machineb = self.browserself.login_and_go("/storage")dev_1 = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev_1))dev_2 = "/dev/sdb"m.add_disk("4G", serial="DISK2")b.wait_visible(self.card_row("Storage", name=dev_2))dev_3 = "/dev/sdc"m.add_disk("4G", serial="DISK3")b.wait_visible(self.card_row("Storage", name=dev_3))passphrase = "foodeeboodeebar"# Create an encrypted pool with a filesystem, but don't mount# it. Cockpit will chose a key description for the pool and# we occupy its first choice in order to force Cockpit to use# something else.create_pool_key(m, "pool0", "not-the-passphrase")if not legacy:self.dialog_open_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})))self.dialog_set_val("encrypt_pass.on", val=True)self.dialog_set_val("passphrase", passphrase)self.dialog_set_val("passphrase2", passphrase)self.dialog_set_val("disks", {dev_1: True})b.assert_pixels("#dialog", "create-encrypted-pool",# The small checkbox ticks render inconsistentlyignore=["input[type=checkbox]"])self.dialog_apply()self.dialog_wait_close()else:create_pool_key(m, "pool0.1", passphrase)create_legacy_pool(m, disks=[dev_1], keydesc="pool0.1")m.execute("stratis key unset pool0.1")m.execute("stratis key unset pool0")v1_pool = legacy or m.image in V1_POOL_IMAGESself.click_card_row("Storage", name="pool0")b.wait_visible(self.card("Encrypted Stratis pool"))b.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog({'name': 'fsys1','mount_point': '/run/fsys1','at_boot': 'local'},secondary=True)b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1 (not mounted)")# Check that it has an entry in fstab and that it is "noauto"self.assertIn("noauto", m.execute("grep /run/fsys1 /etc/fstab"))# Add a data blockdevb.click(self.card_button("Encrypted Stratis pool", "Add block device"))self.dialog_wait_open()self.dialog_set_val('disks', {dev_2: True})self.dialog_apply()if v1_pool:self.dialog_wait_error("pool_passphrase", "Passphrase cannot be empty")self.dialog_set_val('pool_passphrase', passphrase)self.dialog_apply()self.dialog_wait_close()b.wait_in_text(self.card_row("Encrypted Stratis pool", name=dev_2), "data")# Change the passphraseif v1_pool:b.wait_visible(self.card_desc("Encryption tokens", "Passphrase") + " button:contains('Remove'):disabled")b.click(self.card_desc("Encryption tokens", "Passphrase") + " button:contains('Change')")else:self.click_dropdown(self.card_row("Encryption tokens", index=1), "Change")self.dialog({'old_passphrase': passphrase,'new_passphrase': "boodeefoodeebar",'new_passphrase2': "boodeefoodeebar"})# do it again, with the old passphrase in the keyringcreate_pool_key(m, "pool0", "boodeefoodeebar")if v1_pool:b.click(self.card_desc("Encryption tokens", "Passphrase") + " button:contains('Change')")else:self.click_dropdown(self.card_row("Encryption tokens", index=1), "Change")self.dialog({'new_passphrase': passphrase,'new_passphrase2': passphrase})m.execute("stratis key unset pool0")if not v1_pool:# Add a second passphraseb.wait_text(self.card_row_col("Encryption tokens", 1, 2), "Passphrase")b.click(self.card_button("Encryption tokens", "Add passphrase"))self.dialog({'pool_passphrase': passphrase,'passphrase': "boodeefoodeebar",'passphrase2': "boodeefoodeebar"})b.wait_text(self.card_row_col("Encryption tokens", 2, 2), "Passphrase")# And remove it againself.click_dropdown(self.card_row("Encryption tokens", index=2), "Remove")self.confirm()# Add a cache blockdevb.click(self.card_button("Encrypted Stratis pool", "Add block device"))self.dialog_wait_open()self.dialog_set_val('tier', "cache")self.dialog_set_val('disks', {dev_3: True})if v1_pool:self.dialog_set_val('pool_passphrase', passphrase)self.dialog_apply()self.dialog_wait_close()b.wait_in_text(self.card_row("Encrypted Stratis pool", name=dev_3), "cache")self.reboot()m.start_cockpit()b.relogin()b.enter_page("/storage")b.wait_visible(self.card("Stratis pool"))b.wait_in_text(self.card("Stratis pool"), "DISK1")b.wait_in_text(self.card("Stratis pool"), "DISK2")# Unlock the poolb.click(self.card_button("Stratis pool", "Start"))self.dialog_wait_open()self.dialog_set_val('passphrase', "wrong-passphrase")self.dialog_apply()with b.wait_timeout(60):b.wait_visible("#dialog .pf-v6-c-alert.pf-m-danger")self.dialog_set_val('passphrase', passphrase)self.dialog_apply()self.dialog_wait_close()b.wait_text(self.card_desc("Encrypted Stratis pool", "Name"), "pool0")# Mount the filesystemb.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1 (not mounted)")self.click_dropdown(self.card_row("Stratis filesystems", 1), "Mount")self.dialog({})b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1")# Reboot (this requires the passphrase)self.setup_systemd_password_agent(passphrase)self.reboot()m.start_cockpit()b.relogin()b.enter_page("/storage")b.wait_text(self.card_desc("Encrypted Stratis pool", "Name"), "pool0")# Filesystem should be mounted nowb.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1")# Destroy the poolself.click_card_dropdown("Encrypted Stratis pool", "Delete")self.confirm()b.wait_visible(self.card("Storage"))b.wait_not_present(self.card_row("Storage", name="pool0"))# Check that the entry has disappeared from fstabself.assertEqual(m.execute("grep /run/fsys1 /etc/fstab || true"), "")@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testEncryptedLegacy(self):self.testEncrypted(legacy=True)def testReboot(self, legacy=False):m = self.machineb = self.browserself.login_and_go("/storage")dev_1 = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev_1))# Create a poolif not legacy:self.dialog_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})),values={"disks": {dev_1: True}})else:create_legacy_pool(m, disks=[dev_1])self.click_card_row("Storage", name="pool0")# Create a filesystemsb.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog({'name': 'fsys1','mount_point': '/run/fsys1'})b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1")self.reboot()m.start_cockpit()b.relogin()b.enter_page("/storage")# Filesystem should be mounted nowb.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1")@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testRebootLegacy(self):self.testReboot(legacy=True)def testAtBoot(self):m = self.machineb = self.browserself.login_and_go("/storage")dev_1 = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev_1))# Create a poolself.dialog_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})),values={"disks": {dev_1: True}})self.click_card_row("Storage", name="pool0")def create(at_boot):b.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog({'name': 'fsys1','mount_point': f'{self.mnt_dir}/foo','at_boot': at_boot})b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), f"{self.mnt_dir}/foo")def destroy():self.click_card_row("Stratis filesystems", 1)self.click_card_dropdown("Stratis filesystem", "Delete")self.dialog_wait_open()self.dialog_apply_with_retry("Device or resource busy")b.wait_visible(self.card("Stratis filesystems"))b.wait_not_present(self.card_row("Stratis filesystems", name="fsys1"))create("local")self.assertNotIn("noauto", m.execute(f"findmnt --fstab -n -o OPTIONS {self.mnt_dir}/foo"))destroy()create("nofail")self.assertIn("nofail", m.execute(f"findmnt --fstab -n -o OPTIONS {self.mnt_dir}/foo"))destroy()create("netdev")self.assertIn("_netdev", m.execute(f"findmnt --fstab -n -o OPTIONS {self.mnt_dir}/foo"))destroy()create("never")self.assertIn("x-cockpit-never-auto", m.execute(f"findmnt --fstab -n -o OPTIONS {self.mnt_dir}/foo"))self.assertIn("noauto", m.execute(f"findmnt --fstab -n -o OPTIONS {self.mnt_dir}/foo"))destroy()@testlib.skipImage("Stratis too old", "rhel-8-*")def testPoolResize(self, legacy=False):m = self.machineb = self.browserself.login_and_go("/storage")dev = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev))# Create a logical volume that we will later growm.execute(f"vgcreate vgroup0 {dev}; lvcreate vgroup0 -n lvol0 -L 1500000256b")b.wait_visible(self.card_row("Storage", name="lvol0"))# Create a poolif not legacy:self.dialog_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: self.dialog_is_present('disks', "/dev/vgroup0/lvol0"),values={"disks": {"/dev/vgroup0/lvol0": True}})else:create_legacy_pool(m, disks=["/dev/vgroup0/lvol0"])b.wait_in_text(self.card_row("Storage", name="pool0"), "1.5 GB")# Grow the logical volume in Cockpit, the pool should grow automaticallyself.click_card_row("Storage", name="lvol0")b.click(self.card_button("LVM2 logical volume", "Grow"))self.dialog({"size": 1600})b.go("#/")b.wait_in_text(self.card_row("Storage", name="pool0"), "1.6 GB")# Grow the logical volume from outside of Cockpit, the pool should complainm.execute("lvresize vgroup0/lvol0 -L +100000256b")b.wait_visible(self.card_row("Storage", name="pool0") + ' .ct-icon-exclamation-triangle')self.click_card_row("Storage", name="pool0")b.wait_visible('.pf-v6-c-alert:contains("This pool does not use all the space")')b.click('button:contains("Grow the pool")')b.wait_not_present('.pf-v6-c-alert')b.wait_in_text(self.card_desc("Stratis pool", "Usage"), "1.7 GB")b.go("#/")# Grow the logical volume from outside of Cockpit, the logical volume should also complainm.execute("lvresize vgroup0/lvol0 -L +100000256b")b.wait_visible(self.card_row("Storage", name="lvol0") + " .ct-icon-exclamation-triangle")self.click_card_row("Storage", name="lvol0")# First shrink the volume to test whether Cockpit can figure out the right size for thatb.click(self.card_button("LVM2 logical volume", "Shrink volume"))b.wait_in_text(self.card_desc("LVM2 logical volume", "Size"), "1.70 GB")b.wait_not_present(self.card_button("LVM2 logical volume", "Shrink volume"))# Then enlarge the volume from the outside again and grow the blockdevm.execute("lvresize vgroup0/lvol0 -L +100000256b")b.click(self.card_button("LVM2 logical volume", "Grow content"))b.wait_not_present(self.card_button("LVM2 logical volume", "Grow content"))b.go("#/")b.wait_in_text(self.card_row("Storage", name="pool0"), "1.8 GB")@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testPoolResizeLegacy(self):self.testPoolResize(legacy=True)@testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "arch")@testlib.skipOstree("no package installation on OSTree")class TestStoragePackagesStratis(packagelib.PackageCase, storagelib.StorageCase):def testStratisOndemandInstallation(self):m = self.machineb = self.browser# RHEL 8 should not offer installation of Stratis from Cockpit# itself.#ondemand_stratis = "rhel-8" not in m.imagem.execute("systemctl stop stratisd && dnf remove -y stratisd stratis")if ondemand_stratis:self.addPackageSet("stratis")self.enableRepo()self.login_and_go("/storage")dev_1 = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev_1))if ondemand_stratis:self.click_devices_dropdown("Create Stratis pool")self.dialog_wait_open()b.wait_in_text("#dialog", "The stratisd package must be installed")self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_val("name", "pool0")self.dialog_set_val("disks", {dev_1: True})self.dialog_apply()self.dialog_wait_close()b.wait_visible(self.card_row("Storage", name="pool0"))else:dropdown_toggle = self.dropdown_toggle(self.card_header("Storage"))raid_action = self.dropdown_action("Create MDRAID device")stratis_action = self.dropdown_action("Create Stratis pool")b.click(dropdown_toggle)b.wait_visible(raid_action)b.wait_not_present(stratis_action)@testlib.skipImage("No Stratis", "debian-*", "ubuntu-*", "arch")@testlib.skipImage("stratis not installed", "*-bootc")@testlib.skipImage("Stratis too old", "rhel-8-*")class TestStorageStratisNBDE(packagelib.PackageCase, storagelib.StorageCase):provision = {"0": {"address": "10.111.112.1/20", "memory_mb": 2048},"tang": {"address": "10.111.112.5/20"}}def setUp(self):super().setUp()if self.image == "arch":# Arch Linux does not enable systemd units by defaultself.machine.execute("systemctl enable --now stratisd")self.addCleanup(self.machine.execute, "systemctl disable --now stratisd")self.stop_type_opt = get_stratis_stop_type_opt(self.machine.execute)def testBasic(self, legacy=False):m = self.machineb = self.browsertang_m = self.machines["tang"]tang_m.execute("systemctl start tangd.socket")tang_m.execute("firewall-cmd --add-port 80/tcp")self.login_and_go("/storage")dev_1 = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev_1))dev_2 = "/dev/sdb"m.add_disk("5G", serial="DISK2")b.wait_visible(self.card_row("Storage", name=dev_2))# Create an encrypted pool with both a passphrase and a keyserverif not legacy:self.dialog_open_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})))self.dialog_set_val("encrypt_pass.on", val=True)self.dialog_set_val("passphrase", "foodeeboodeebar")self.dialog_set_val("passphrase2", "foodeeboodeebar")self.dialog_set_val("encrypt_tang.on", val=True)self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_set_val("disks", {dev_1: True})self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()else:create_pool_key(m, "pool0", "foodeeboodeebar")create_legacy_pool(m, disks=[dev_1], keydesc="pool0", tang="10.111.112.5")m.execute("stratis key unset pool0")v1_pool = legacy or m.image in V1_POOL_IMAGESself.click_card_row("Storage", name="pool0")if v1_pool:b.wait_visible(self.card_desc("Encryption tokens", "Passphrase"))b.wait_in_text(self.card_desc("Encryption tokens", "Keyserver"), "10.111.112.5")else:b.wait_visible(self.card_row("Encryption tokens", location="passphrase"))b.wait_visible(self.card_row("Encryption tokens", location="10.111.112.5"))if not legacy:b.assert_pixels(self.card("Encrypted Stratis pool"), "header",ignore=['.pf-v6-c-description-list__group:contains("UUID")'])# Remove passphraseif v1_pool:b.click(self.card_desc("Encryption tokens", "Passphrase") + " button:contains('Remove')")self.confirm()b.wait_visible(self.card_desc("Encryption tokens", "Passphrase") + " button:contains('Add passphrase')")b.wait_visible(self.card_desc("Encryption tokens", "Keyserver") + " button:contains('Remove'):disabled")else:self.click_dropdown(self.card_row("Encryption tokens", index=1), "Remove")self.confirm()row = self.card_row("Encryption tokens", location="10.111.112.5")b.wait_visible(row)b.click(self.dropdown_toggle(row))b.wait_visible(self.dropdown_action("Remove") + "[disabled]")b.click(self.dropdown_toggle(row))# Stop the pool and start it again. This should not ask# for the passphrase (since there isn't any)m.execute(f"stratis pool stop {self.stop_type_opt} pool0")b.wait_visible(self.card("Stratis pool"))tang_m.execute("systemctl stop tangd.socket")b.click(self.card_button("Stratis pool", "Start"))self.dialog_wait_open()# stratis' error message for unreachable tang server is very poor:# https://bugzilla.redhat.com/show_bug.cgi?id=2246920 Version < 3.6.0 said# "Error communicating with server 10.111.112.5", check this again after fixingb.wait_in_text("#dialog", "Error")self.dialog_cancel()self.dialog_wait_close()tang_m.execute("systemctl start tangd.socket")b.click(self.card_button("Stratis pool", "Start"))b.wait_visible(self.card("Encrypted Stratis pool"))# Put passphrase back and do the stopping starting again, but# without tangd running. This should try clevis but then fall# back to asking for a passphrase.b.click(self.card_button("Encryption tokens", "Add passphrase"))self.dialog({'passphrase': "foodeeboodeebar",'passphrase2': "foodeeboodeebar"})if v1_pool:b.wait_visible(self.card_desc("Encryption tokens", "Passphrase") + " button:contains('Remove'):not(:disabled)")else:b.wait_visible(self.card_row("Encryption tokens", location="passphrase"))b.click(self.dropdown_toggle(row))b.wait_visible(self.dropdown_action("Remove") + ":not([disabled])")b.click(self.dropdown_toggle(row))m.execute(f"stratis pool stop {self.stop_type_opt} pool0")tang_m.execute("systemctl stop tangd.socket")b.click(self.card_button("Stratis pool", "Start"))self.dialog_wait_open()self.dialog_set_val("passphrase", "foobar")self.dialog_cancel()self.dialog_wait_close()# Finally start tangtang_m.execute("systemctl start tangd.socket")b.click(self.card_button("Stratis pool", "Start"))b.wait_visible(self.card("Encrypted Stratis pool"))# Add a blockdevice.b.click(self.card_button("Encrypted Stratis pool", "Add block device"))if v1_pool:self.dialog({'disks': {dev_2: True}, 'pool_passphrase': "foodeeboodeebar"})else:self.dialog({'disks': {dev_2: True}})# Remove the keyserver and add it backif v1_pool:b.click(self.card_desc("Encryption tokens", "Keyserver") + " button:contains('Remove')")else:self.click_dropdown(self.card_row("Encryption tokens", location="10.111.112.5"), "Remove")self.confirm()if v1_pool:b.wait_not_in_text(self.card_desc("Encryption tokens", "Keyserver"), "10.111.112.5")else:b.wait_not_present(self.card_row("Encryption tokens", location="10.111.112.5"))b.click(self.card_button("Encryption tokens", "Add keyserver"))self.dialog_wait_open()self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_set_val("pool_passphrase", "foodeeboodeebar")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()if v1_pool:b.wait_in_text(self.card_desc("Encryption tokens", "Keyserver"), "10.111.112.5")else:b.wait_visible(self.card_row("Encryption tokens", location="10.111.112.5"))# Remove the keyserver and add it back a second time, but try# first with the wrong passphrase already in the keyringif v1_pool:b.click(self.card_desc("Encryption tokens", "Keyserver") + " button:contains('Remove')")else:self.click_dropdown(self.card_row("Encryption tokens", location="10.111.112.5"), "Remove")self.confirm()create_pool_key(m, "pool0", "foobar")b.click(self.card_button("Encryption tokens", "Add keyserver"))self.dialog_wait_open()self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash")b.wait_in_text("#dialog", tang_m.execute("tang-show-keys").strip())self.dialog_apply()with b.wait_timeout(60):b.wait_in_text('#dialog', "Command failed")m.execute("stratis key unset pool0")create_pool_key(m, "pool0", "foodeeboodeebar")self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()if v1_pool:b.wait_in_text(self.card_desc("Encryption tokens", "Keyserver"), "10.111.112.5")else:b.wait_visible(self.card_row("Encryption tokens", location="10.111.112.5"))m.execute("stratis key unset pool0")# Create a mounted filesystem and reboot.b.click(self.card_button("Stratis filesystems", "Create new filesystem"))self.dialog({'name': 'fsys1','mount_point': '/run/fsys1'})b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1")b.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1")self.reboot()m.start_cockpit()b.relogin()b.enter_page("/storage")b.wait_visible(self.card("Stratis pool"))b.wait_text(self.card_row_col("Stratis filesystems", 1, 1), "fsys1") # should be started after bootb.wait_text(self.card_row_col("Stratis filesystems", 1, 3), "/run/fsys1") # should be mounted after boot@testlib.skipImage("Stratis too old for legacy tests", *V1_POOL_IMAGES)def testBasicLegacy(self):self.testBasic(legacy=True)@testlib.skipImage("Stratis too old", *V1_POOL_IMAGES)def testEncryptionTokens(self):m = self.machineb = self.browsertang_m = self.machines["tang"]tang_m.execute("systemctl start tangd.socket")tang_m.execute("firewall-cmd --add-port 80/tcp")self.login_and_go("/storage")dev_1 = "/dev/sda"m.add_disk("4G", serial="DISK1")b.wait_visible(self.card_row("Storage", name=dev_1))passphrase = "foodeeboodeebar"passphrase2 = "boodeefoodeebar"# Create a pool with a passphraseself.dialog_open_with_retry(trigger=lambda: self.click_devices_dropdown("Create Stratis pool"),expect=lambda: (self.dialog_is_present('disks', dev_1) andself.dialog_check({"name": "pool0"})))self.dialog_set_val("encrypt_pass.on", val=True)self.dialog_set_val("passphrase", passphrase)self.dialog_set_val("passphrase2", passphrase)self.dialog_set_val("disks", {dev_1: True})self.dialog_apply()self.dialog_wait_close()self.click_card_row("Storage", name="pool0")b.wait_visible(self.card("Encrypted Stratis pool"))# Adding a token needs the existing passphrase.b.click(self.card_button("Encryption tokens", "Add keyserver"))self.dialog_wait_open()b.wait_visible(self.dialog_field("pool_passphrase"))b.wait_in_text("#dialog", "Please provide an existing pool passphrase.")b.assert_pixels("#dialog", "dialog")self.dialog_cancel()self.dialog_wait_close()# When the only passphrase is already in the keyring, it must# work.create_pool_key(m, "pool0", passphrase)b.click(self.card_button("Encryption tokens", "Add keyserver"))self.dialog_wait_open()b.wait_in_text("#dialog", "At least one of the non-interactive unlock methods must work.")b.wait_not_present(self.dialog_field("pool_passphrase"))self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash")self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()# A single keyserver will be mentioned explicitlym.execute("stratis key unset pool0")b.click(self.card_button("Encryption tokens", "Add passphrase"))self.dialog_wait_open()self.dialog_set_val("passphrase", passphrase2)self.dialog_set_val("passphrase2", passphrase2)b.wait_visible(self.dialog_field("pool_passphrase"))b.wait_in_text("#dialog", "If the keyserver at 10.111.112.5 is not reachable, you can provide an existing passphrase.")self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()# When only one passphrase is in the keyring, the other one can be provided.create_pool_key(m, "pool0", passphrase)b.click(self.card_button("Encryption tokens", "Add keyserver"))self.dialog_wait_open()b.wait_visible(self.dialog_field("pool_passphrase"))b.wait_in_text("#dialog", "If none of the non-interactive unlock methods works, you can provide an existing passphrase.")self.dialog_cancel()self.dialog_wait_close()# When there is only one tang, it is mentioned explicitly and it must workm.execute("stratis key unset pool0")self.click_dropdown(self.card_row("Encryption tokens", index=1), "Remove")self.confirm()self.click_dropdown(self.card_row("Encryption tokens", location="passphrase"), "Remove")self.confirm()b.click(self.card_button("Encryption tokens", "Add keyserver"))self.dialog_wait_open()b.wait_in_text("#dialog", "The keyserver at 10.111.112.5 must be reachable.")b.wait_not_present(self.dialog_field("pool_passphrase"))self.dialog_set_val("tang_url", "10.111.112.5")self.dialog_apply()b.wait_in_text("#dialog", "Check the key hash")self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()# With two, one must workb.click(self.card_button("Encryption tokens", "Add passphrase"))self.dialog_wait_open()self.dialog_set_val("passphrase", passphrase)self.dialog_set_val("passphrase2", passphrase)b.wait_in_text("#dialog", "At least one keyserver must be reachable.")b.wait_not_present(self.dialog_field("pool_passphrase"))self.dialog_apply()with b.wait_timeout(60):self.dialog_wait_close()# Add TPM2 while letting Tang unlock the tokens for usm.execute("stratis pool bind tpm2 pool0")b.wait_visible(self.card_row("Encryption tokens", location="tpm2"))b.assert_pixels(self.card("Encryption tokens"), "tokens")# Remove TPM2 with Cockpitself.click_dropdown(self.card_row("Encryption tokens", location="tpm2"), "Remove")self.confirm()# Change the passphraseself.click_dropdown(self.card_row("Encryption tokens", location="passphrase"), "Change")self.dialog({'old_passphrase': passphrase,'new_passphrase': passphrase2,'new_passphrase2': passphrase2})if __name__ == '__main__':testlib.test_main()