#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/common/pywrap", sys.argv)

# This file is part of Cockpit.
#
# Copyright (C) 2021 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import os

from machineslib import VirtualMachinesCase
from testlib import nondestructive, test_main, wait


@nondestructive
class TestMachinesStoragePools(VirtualMachinesCase):

    def gotoVolumesTab(self, poolName, connectionName="system"):
        selector = f"tr[data-row-id='pool-{poolName}-{connectionName}'] + tr li:contains('Storage volumes')"
        self.browser.click(selector + " > button")
        self.browser.wait_attr(selector, "class", "pf-v5-c-tabs__item pf-m-current")

    def gotoOverviewTab(self, poolName, connectionName="system"):
        selector = f"tr[data-row-id='pool-{poolName}-{connectionName}'] + tr li:contains('Overview')"
        self.browser.click(selector + " > button")
        self.browser.wait_attr(selector, "class", "pf-v5-c-tabs__item pf-m-current")

    def testStoragePoolsBasic(self):
        b = self.browser
        m = self.machine

        self.createVm("subVmTest1")

        self.login_and_go("/machines")
        self.waitPageInit()
        self.waitVmRow("subVmTest1")

        b.wait_in_text("#card-pf-storage-pools", "1 Storage pool")
        b.wait_in_text("#card-pf-storage-pools .pf-v5-c-card__header button", "Storage pool")

        # prepare libvirt storage pools
        p1 = os.path.join(self.vm_tmpdir, "vm_one")
        p2 = os.path.join(self.vm_tmpdir, "vm_two")
        p3 = os.path.join(self.vm_tmpdir, "vm_three")
        m.execute(f"mkdir --mode 777 {p1} {p2} {p3}")
        m.execute(f"virsh pool-define-as myPoolOne --type dir --target {p1}; virsh pool-start myPoolOne")
        m.execute(f"virsh pool-define-as myPoolTwo --type dir --target {p2}; virsh pool-start myPoolTwo")
        m.execute(f"virsh pool-create-as myPoolThree --type dir --target {p3}")  # Transient pool

        b.wait_in_text("#card-pf-storage-pools", "3 Storage pools")

        m.execute("virsh vol-create-as myPoolTwo VolumeOne --capacity 1G --format qcow2")
        m.execute("virsh vol-create-as myPoolTwo VolumeTwo --capacity 1G --format qcow2")
        m.execute("virsh vol-create-as myPoolTwo VolumeThree --capacity 1G --format qcow2")
        m.execute("virsh vol-create-as myPoolTwo VolumeFour --capacity 1G --format qcow2")
        wait(lambda: all(volume in m.execute("virsh vol-list myPoolTwo") for volume in ["VolumeOne", "VolumeTwo", "VolumeThree", "VolumeFour"]))
        m.execute('virsh pool-refresh myPoolOne; virsh pool-refresh myPoolTwo')

        connectionName = m.execute("virsh uri | head -1 | cut -d/ -f4").strip()

        diskXML = """'<disk type="volume" device="disk">
          <driver name="qemu"/>
          <source pool="myPoolTwo" volume="VolumeOne"/>
          <target dev="vdc" bus="virtio"/>
        </disk>'""".replace("\n", "")

        m.execute(f"echo {diskXML} > /tmp/disk.xml; virsh attach-device --config --file /tmp/disk.xml subVmTest1")

        # Shuf off the VM in order to allow deleting volumes that are used as
        # disks later
        self.performAction("subVmTest1", "forceOff")

        # Click on Storage pools card
        b.click(".pf-v5-c-card .pf-v5-c-card__header button:contains(Storage pools)")

        # Check that all defined pools are there
        b.wait_in_text("body", "Storage pools")
        self.waitPoolRow("myPoolOne", connectionName)
        self.waitPoolRow("myPoolTwo", connectionName)
        b.wait_visible("#create-storage-pool:not(:disabled)")

        b.assert_pixels(
            "#app", "storage-pools-page",
            ignore=(
                # HACK: the font rendering of middle columns is jittery, even after sleep()
                [f"td:nth-child({n})" for n in range(2, 6)] +
                [f"th:nth-child({n})" for n in range(2, 6)]
            ),
            skip_layouts=["rtl"]
        )

        # Check basic pool properties
        self.togglePoolRow("myPoolOne", connectionName)
        b.wait_in_text(f"#pool-myPoolOne-{connectionName}-target-path", p1)
        b.wait_in_text(f"#pool-myPoolOne-{connectionName}-type", "dir")

        # Check storage volumes of a pool
        self.gotoVolumesTab("myPoolOne")
        b.wait_in_text(f"tr[data-row-id='pool-myPoolOne-{connectionName}'] + tr .pf-v5-c-empty-state",
                       "No storage volumes defined for this storage pool")

        # Close expanded row for this pool
        self.togglePoolRow("myPoolOne", connectionName)

        # Expand row for second storage pool and check list of storage volumes
        self.togglePoolRow("myPoolTwo", connectionName)
        self.gotoVolumesTab("myPoolTwo")
        b.wait_visible(f"#pool-myPoolTwo-{connectionName}-volume-VolumeOne-name")
        b.wait_in_text(f"#pool-myPoolTwo-{connectionName}-volume-VolumeOne-name", "VolumeOne")
        b.wait_in_text(f"#pool-myPoolTwo-{connectionName}-volume-VolumeTwo-name", "VolumeTwo")
        b.wait_in_text(f"#pool-myPoolTwo-{connectionName}-volume-VolumeThree-name", "VolumeThree")
        b.wait_in_text(f"#pool-myPoolTwo-{connectionName}-volume-VolumeFour-name", "VolumeFour")
        b.wait_not_present("#storage-volumes-delete")

        # Delete a volume from terminal and verify API error will be shown when trying to delete from UI
        m.execute("virsh vol-delete --pool myPoolTwo --vol VolumeFour")
        b.wait_visible(f"#pool-myPoolTwo-{connectionName}-volume-VolumeFour-name")
        b.click("tbody input[aria-label='Select row 0']")
        b.wait_in_text("#storage-volumes-delete", "Delete 1 volume")
        b.click("#storage-volumes-delete")
        b.wait_in_text(".pf-v5-c-alert .pf-v5-c-alert__title", "Storage volumes could not be deleted")
        b.reload()
        b.enter_page('/machines')
        self.togglePoolRow("myPoolTwo", connectionName)
        self.gotoVolumesTab("myPoolTwo")

        # Delete a volume from terminal and verify that refresh worked by reloading the browser page
        m.execute(f"rm -f {os.path.join(p2, 'VolumeThree')}")
        b.wait_visible(f"#pool-myPoolTwo-{connectionName}-volume-VolumeThree-name")
        b.reload()
        b.enter_page('/machines')
        self.togglePoolRow("myPoolTwo", connectionName)
        self.gotoVolumesTab("myPoolTwo")
        b.wait_not_present(f"#pool-myPoolTwo-{connectionName}-volume-VolumeThree-name")

        # Delete Storage Volume that is not used by any VM
        b.wait_visible(f"#pool-myPoolTwo-{connectionName}-volume-VolumeTwo-name")
        b.click("tbody input[aria-label='Select row 1']")
        b.wait_in_text("#storage-volumes-delete", "Delete 1 volume")
        b.click("#storage-volumes-delete")
        b.wait_not_present(f"#pool-myPoolTwo-{connectionName}-volume-VolumeTwo-name")

        # Try to Delete Storage Volume which is used by a VM
        b.wait_visible(f"#pool-myPoolTwo-{connectionName}-volume-VolumeOne-name")
        b.click("tbody input[aria-label='Select row 0']")
        b.wait_visible("#storage-volumes-delete:disabled")
        b.wait_in_text("#storage-volumes-delete", "Delete 1 volume")

        # Test operations on storage pools
        self.togglePoolRow("myPoolOne", connectionName)
        self.gotoVolumesTab("myPoolOne")

        # Try deactivating and activating a pool
        b.click(f"#deactivate-pool-myPoolOne-{connectionName}")
        b.wait_in_text(f"#pool-myPoolOne-{connectionName}-state", "inactive")
        b.wait_in_text(f"tr[data-row-id='pool-myPoolOne-{connectionName}'] + tr .pf-v5-c-empty-state", "Activate the storage pool to administer volumes")
        b.wait_visible(f'#myPoolOne-{connectionName}-create-volume-button:disabled')
        b.click(f"#activate-pool-myPoolOne-{connectionName}")
        b.wait_in_text(f"#pool-myPoolOne-{connectionName}-state", "active")
        b.wait_visible(f'#myPoolOne-{connectionName}-create-volume-button:enabled')

        # See deletion of Pool is disabled because this pool is referenced by VM's disk
        b.click(f"#pool-myPoolTwo-{connectionName}-action-kebab > button")
        b.wait_visible(f"#delete-pool-myPoolTwo-{connectionName} > a[aria-disabled=true]")

        # VM is not running, so we need to reload the page
        b.reload()
        b.enter_page('/machines')

        # Expand row for myPoolTwo and check list of storage volumes
        self.togglePoolRow("myPoolTwo", connectionName)
        self.gotoVolumesTab("myPoolTwo")

        b.wait_visible(f"#pool-myPoolTwo-{connectionName}-volume-VolumeOne-usedby")
        b.wait_in_text(f"#pool-myPoolTwo-{connectionName}-volume-VolumeOne-usedby", "subVmTest1")

        m.execute("virsh detach-disk --config --target vdc subVmTest1")
        b.wait_in_text(f"#pool-myPoolTwo-{connectionName}-volume-VolumeOne-usedby", "")

        # Transient pool
        self.togglePoolRow("myPoolThree", connectionName)
        b.wait_not_present(f"#pool-myPoolThree-{connectionName}-autostart")  # Transient pool shouldn't have autostart option
        b.click(f"#pool-myPoolThree-{connectionName}-action-kebab > button")
        b.wait_visible(f'#delete-pool-myPoolThree-{connectionName} > a[aria-disabled=true]')  # Transient pool cannot be deleted
        b.click(f'#deactivate-pool-myPoolThree-{connectionName}')  # Deactivate transient pool
        # Check it's not present after deactivation
        self.waitPoolRow("myPoolThree", connectionName, False)

    def testStoragePoolsCreate(self):
        b = self.browser
        m = self.machine

        p1 = os.path.join(self.vm_tmpdir, "my_dir_pool_one")
        p2 = os.path.join(self.vm_tmpdir, "my_dir_pool_two")
        mnt_exports = os.path.join(self.vm_tmpdir, "mnt_exports")
        m.execute(f"mkdir {p1} {p2} {mnt_exports}")
        self.restore_file("/etc/exports")
        m.execute(f"echo '{mnt_exports} 127.0.0.1/24(rw,sync,no_root_squash,no_subtree_check)' > /etc/exports")
        m.execute("systemctl restart nfs-server")

        self.login_and_go("/machines")
        self.waitPageInit()

        # Click on Storage pools card
        b.wait_in_text("#card-pf-storage-pools .pf-v5-c-card__header button", "Storage pools")
        b.click(".pf-v5-c-card .pf-v5-c-card__header button:contains(Storage pools)")

        class StoragePoolCreateDialog(object):
            def __init__(
                self, test_obj, name, pool_type=None, target=None, source=None,
                autostart=None, xfail=False, xfail_error=None, remove=True,
            ):
                self.test_obj = test_obj
                self.name = name
                self.pool_type = pool_type
                self.target = target
                self.source = source or {}
                self.autostart = autostart
                self.xfail = xfail
                self.xfail_error = xfail_error
                self.remove = remove

            def execute(self):
                self.open()
                self.fill()
                self.create()
                if not self.xfail:
                    self.verify_dialog()
                    self.verify_overview()
                    if self.remove:
                        self.cleanup()

            def open(self):
                b.click("#create-storage-pool")
                b.wait_visible("#create-storage-pool-dialog")
                b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-modal-box__header .pf-v5-c-modal-box__title", "Create storage pool")

            def fill(self):
                b.set_input_text("#storage-pool-dialog-name", self.name)

                if "rhel-9" in m.image or m.image in ["centos-9-stream"]:
                    b.wait_not_present("#storage-pool-dialog-type option[value='iscsi-direct']")

                if self.pool_type:
                    b.wait_visible("#storage-pool-dialog-type")
                    b.set_val("#storage-pool-dialog-type", self.pool_type)

                if self.target:
                    b.set_file_autocomplete_val("#storage-pool-dialog-target-group", self.target)

                if 'source_path' in self.source:
                    if self.pool_type != 'disk':
                        b.set_input_text("#storage-pool-dialog-source", self.source['source_path'])
                    else:
                        b.set_file_autocomplete_val("#storage-pool-dialog-source-group", self.source['source_path'])

                if 'format' in self.source:
                    b.select_from_dropdown("#storage-pool-dialog-source-format", self.source['format'])

                if 'host' in self.source:
                    b.set_input_text("#storage-pool-dialog-host", self.source['host'])

                if 'initiator' in self.source:
                    b.set_input_text('#storage-pool-dialog-initiator', self.source['initiator'])

                if (self.autostart):
                    b.click("storage-pool-dialog-autostart")

            def cancel(self):
                b.click(".pf-v5-c-modal-box__footer button:contains(Cancel)")
                b.wait_not_present("#create-storage-pool-dialog")

            def create(self):
                b.click(".pf-v5-c-modal-box__footer button:contains(Create)")

                if not self.xfail:
                    b.wait_not_present("#create-storage-pool-dialog")
                else:
                    # Check incomplete dialog
                    if (not self.name):
                        b.wait_visible("#storage-pool-dialog-name-helper")

                    if (not self.target):
                        b.wait_visible("#storage-pool-dialog-target-helper")
                    # Check errors from backend
                    if self.xfail_error:
                        error_location = "#create-storage-pool-dialog .pf-v5-c-modal-box__body .pf-m-danger"
                        b.wait_visible(error_location)
                        error_message = b.text(error_location)
                        self.test_obj.assertIn(self.xfail_error, error_message)

                    self.cancel()

                    # If pool creation failed make sure that the pool is not shown in the UI
                    if self.xfail_error and 'already exists' not in self.xfail_error:
                        self.test_obj.waitPoolRow(self.name, "system", False)

            def verify_dialog(self):
                # Check that the defined pools is now visible
                b.wait_in_text("body", "Storage pools")
                self.test_obj.waitPoolRow(self.name)

                # Verify libvirt XML
                pool_xml = f"virsh -c qemu:///system pool-dumpxml {self.name}"
                xmllint_element = f"{pool_xml} | xmllint --xpath 'string(//pool/{{prop}})' - 2>&1 || true"

                self.test_obj.assertEqual(self.name, m.execute(xmllint_element.format(prop='name')).strip())
                if (self.target):
                    self.test_obj.assertEqual(self.target, m.execute(xmllint_element.format(prop='target/path')).strip() + '/')
                self.test_obj.assertEqual(self.pool_type, m.execute(xmllint_element.format(prop='@type')).strip())

                host = m.execute(xmllint_element.format(prop='source/host/@name')).strip()
                if "host" in self.source:
                    self.test_obj.assertEqual(self.source["host"], host)
                else:
                    self.test_obj.assertEqual("", host.rstrip())

                source_path_dir = m.execute(xmllint_element.format(prop="source/dir/@path")).strip()
                source_path_device = m.execute(xmllint_element.format(prop="source/device/@path")).strip()
                source_name = m.execute(xmllint_element.format(prop="source/name")).strip()
                if "source_path" in self.source:
                    self.test_obj.assertTrue(self.source["source_path"] in [source_path_dir, source_path_device, source_name])
                else:
                    self.test_obj.assertEqual("", host.rstrip())

                initiator = m.execute(xmllint_element.format(prop='source/initiator/iqn/@name')).strip()
                if "initiator" in self.source:
                    self.test_obj.assertEqual(self.source["initiator"], initiator)
                else:
                    self.test_obj.assertEqual("", initiator.rstrip())

                sourceFormat = m.execute(xmllint_element.format(prop='source/format/@type')).strip()
                if "format" in self.source:
                    self.test_obj.assertEqual(self.source["format"], sourceFormat)
                else:
                    if self.pool_type == 'netfs':
                        self.test_obj.assertEqual("auto", sourceFormat)
                    elif self.pool_type == 'logical':
                        self.test_obj.assertEqual("lvm2", sourceFormat)
                    else:
                        self.test_obj.assertEqual("", sourceFormat.rstrip())

            def verify_overview(self):
                # Check basic pool properties
                connectionName = m.execute("virsh uri | head -1 | cut -d/ -f4").strip()
                self.test_obj.togglePoolRow(self.name, connectionName)

                if self.target:
                    b.wait_in_text(f"#pool-{self.name}-{connectionName}-target-path", self.target[:-1])
                b.wait_in_text(f"#pool-{self.name}-{connectionName}-type", self.pool_type)
                if "host" in self.source:
                    b.wait_in_text(f"#pool-{self.name}-{connectionName}-host", self.source["host"])
                if "source_path" in self.source:
                    b.wait_in_text(f"#pool-{self.name}-{connectionName}-source-path", self.source["source_path"])

                self.test_obj.gotoVolumesTab(self.name)
                b.click(f"#activate-pool-{self.name}-{connectionName}")
                b.wait_in_text(f"#pool-{self.name}-{connectionName}-state", "active")
                if "iscsi" in self.pool_type:
                    b.wait_visible(f'#{self.name}-{connectionName}-create-volume-button:disabled')
                else:
                    b.wait_visible(f'#{self.name}-{connectionName}-create-volume-button:enabled')
                b.click(f"#deactivate-pool-{self.name}-{connectionName}")
                b.wait_in_text(f"#pool-{self.name}-{connectionName}-state", "inactive")

            def cleanup(self):
                m.execute(f"virsh pool-undefine {self.name}")

        StoragePoolCreateDialog(
            self,
            name="my_dir_pool_one",
            pool_type="dir",
            target=p1 + "/",
            remove=False,
        ).execute()

        # XFAIL: Try to create a pool with used name
        StoragePoolCreateDialog(
            self,
            name="my_dir_pool_one",
            pool_type="dir",
            target=p1 + "/",
            xfail=True,
            xfail_error="pool 'my_dir_pool_one' already exists"
        ).execute()

        # Manually remove the created pool
        m.execute("virsh pool-undefine my_dir_pool_one")

        # XFAIL: Try to create a pool with incomplete modal dialog
        StoragePoolCreateDialog(
            self,
            name="",
            pool_type="dir",
            target="",
            xfail=True,
        ).execute()

        StoragePoolCreateDialog(
            self,
            name="my_dir_pool_two",
            pool_type="netfs",
            target=p2 + "/",
            source={"host": "127.0.0.1", "source_path": mnt_exports}
        ).execute()

        # Prepare a disk with gpt partition table to test the disk storage pool type
        dev = self.add_ram_disk(2)
        m.execute(f"parted {dev} mklabel gpt")

        if m.image == "arch":
            m.execute("mkdir -p /media/")

        StoragePoolCreateDialog(
            self,
            name="my_disk_pool",
            pool_type="disk",
            target="/media/",
            source={"source_path": dev, "format": "gpt"},
        ).execute()

        # Debian images' -cloud kernel don't have target-cli-mod kmod
        # TODO: Arch image has no iscsi yet
        if "debian" not in m.image and "arch" != m.image:
            # Preparations for testing ISCSI pools

            target_iqn = "iqn.2019-09.cockpit.lan"
            orig_iqn = self.prepareStorageDeviceOnISCSI(target_iqn)

            StoragePoolCreateDialog(
                self,
                name="my_iscsi_pool",
                pool_type="iscsi",
                target="/dev/disk/by-id/",
                source={"host": "127.0.0.1", "source_path": target_iqn}
            ).execute()

            if "debian" not in m.image and "ubuntu" not in m.image and "rhel-9" not in m.image and "centos-9" not in m.image:
                # iscsi-direct pool type is not available in Debian/Ubuntu (https://bugs.debian.org/918728)
                StoragePoolCreateDialog(
                    self,
                    name="my_iscsi_direct_pool",
                    pool_type="iscsi-direct",
                    source={"host": "127.0.0.1",
                            "source_path": target_iqn,
                            "initiator": orig_iqn}
                ).execute()
            else:
                # Ensure that iscsi-direct is absent from the types dropdown
                b.click("#create-storage-pool")
                b.wait_visible("#create-storage-pool-dialog")
                b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-modal-box__header .pf-v5-c-modal-box__title", "Create storage pool")
                b.click("#storage-pool-dialog-type")
                b.wait_not_present("#storage-pool-dialog-type option[value*='isci-direct']")
                b.click(".pf-v5-c-modal-box__footer button:contains(Cancel)")
                b.wait_not_present("#create-storage-pool-dialog")

        # Prepare a Volume Group to be used as source for the LVM pool
        m.execute("wipefs -a {0}; pvcreate {0}; vgcreate vol_grp1 {0}".format(dev))
        b.go("/machines#/storages")
        b.enter_page("/machines")

        StoragePoolCreateDialog(
            self,
            name="my_logical_pool",
            pool_type="logical",
            source={"source_path": "vol_grp1"},
        ).execute()

    def testStorageVolumesCreate(self):
        b = self.browser
        m = self.machine

        # Prepare dir pool
        dir_pool = os.path.join(self.vm_tmpdir, "dir_pool")
        m.execute(f"mkdir --mode 777 {dir_pool}")
        m.execute(f"virsh pool-define-as dir-pool --type dir --target {dir_pool}; virsh pool-start dir-pool")

        # Prepare net-fs pool
        nfs_pool = os.path.join(self.vm_tmpdir, "nfs_pool")
        mnt_exports = os.path.join(self.vm_tmpdir, "mnt_exports")
        self.restore_file("/etc/exports")
        m.execute(f"mkdir {nfs_pool} {mnt_exports}")
        m.write("/etc/exports", f"{mnt_exports} 127.0.0.1/24(rw,sync,no_root_squash,no_subtree_check,fsid=0)")
        m.execute("systemctl restart nfs-server")
        m.execute(f"virsh pool-define-as nfs-pool --type netfs --target {nfs_pool} --source-host 127.0.0.1 --source-path {mnt_exports}")
        m.execute("virsh pool-start nfs-pool")
        self.addCleanup(m.execute, "virsh pool-destroy nfs-pool || true")  # Destroy pool as it block removal of `nfs_pool`

        # Prepare disk/block pool
        dev = self.add_ram_disk(10)
        self.machine.execute(f"""
            virsh pool-define-as disk-pool disk - - {dev} - {os.path.join(self.vm_tmpdir, 'poolDiskImages')}
            virsh pool-build disk-pool --overwrite
            virsh pool-start disk-pool""")

        self.login_and_go("/machines")
        self.waitPageInit()

        # Click on Storage pools card
        b.wait_in_text("#card-pf-storage-pools .pf-v5-c-card__header button", "Storage pools")
        b.click(".pf-v5-c-card .pf-v5-c-card__header button:contains(Storage pools)")

        class StorageVolumeCreateDialog(object):
            def __init__(
                self, test_obj, pool_name, vol_name, size="128", unit="MiB", fmt='qcow2',
                pool_type="dir", xfail=False, xfail_objects=None, xfail_error=None, remove=True,
            ):
                self.test_obj = test_obj
                self.pool_name = pool_name
                self.pool_type = pool_type
                self.vol_name = vol_name
                self.size = size
                self.unit = unit
                self.fmt = fmt
                self.xfail = xfail
                self.xfail_objects = xfail_objects or []
                self.xfail_error = xfail_error
                self.remove = remove

            def execute(self):
                self.open()
                self.fill()
                self.create()
                if not self.xfail:
                    self.verify()
                else:
                    self.cancel()
                self.close()

            def open(self):
                self.test_obj.togglePoolRow(self.pool_name)

                self.test_obj.gotoVolumesTab(self.pool_name)

                b.click(f"#{self.pool_name}-system-create-volume-button")  # open the "Storage volumes" subtab

                b.wait_visible("#create-volume-dialog-modal")
                b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-modal-box__header .pf-v5-c-modal-box__title", "Create storage volume")

            def fill(self):
                b.set_input_text("#create-volume-dialog-name", self.vol_name)

                if self.size:
                    b.set_input_text("#create-volume-dialog-size", self.size)

                if self.unit:
                    b.select_from_dropdown("#create-volume-dialog-unit", self.unit)

                if self.fmt:
                    b.select_from_dropdown("#create-volume-dialog-format", self.fmt)

            def cancel(self):
                b.click(".pf-v5-c-modal-box__footer button:contains(Cancel)")
                b.wait_not_present("#create-storage-pool-dialog")

            def create(self):
                b.click(".pf-v5-c-modal-box__footer button:contains(Create)")

                if not self.xfail:
                    # Creation of volumes might take longer for some pool types
                    if self.pool_type in ["disk", "netfs"]:
                        b.wait_visible(".pf-v5-c-modal-box__footer button.pf-m-in-progress")
                        b.wait_visible(".pf-v5-c-modal-box__footer button:contains(Create):disabled")
                    b.wait_not_present("#create-volume-dialog-modal")
                else:
                    for xfo in self.xfail_objects:
                        b.wait_in_text(f"#create-volume-dialog-{xfo}-group .pf-v5-c-form__helper-text .pf-m-error", self.xfail_error)

            def verify(self):
                # Check that the defined volume is now visible
                b.wait_visible(f"#pool-{self.pool_name}-system-volume-{self.vol_name}-name")

                # Verify libvirt XML
                vol_xml = f"virsh -c qemu:///system vol-dumpxml --pool {self.pool_name} --vol {self.vol_name}"
                xmllint_element = f"{vol_xml} | xmllint --xpath 'string(//volume/{{prop}})' - 2>&1 || true"

                self.test_obj.assertEqual(self.vol_name, m.execute(xmllint_element.format(prop='name')).strip())

                if (self.fmt):
                    self.test_obj.assertEqual(self.fmt, m.execute(xmllint_element.format(prop='target/format/@type')).strip())

                size = int(m.execute(xmllint_element.format(prop='capacity')).strip())
                if self.unit == "GiB":
                    size = round(size / (1024**3))
                else:
                    size = round(size / (1024**2))
                self.test_obj.assertEqual(size, int(self.size))

            def cleanup(self):
                m.execute("virsh pool-destroy {0}; virsh pool-undefine {0}".format(self.pool_name))

            def close(self):
                self.test_obj.gotoOverviewTab(self.pool_name)

                self.test_obj.togglePoolRow(self.pool_name)

        # Check size validation
        StorageVolumeCreateDialog(
            self,
            pool_name="dir-pool",
            vol_name="volume_of_dir_pool",
            size="1000",
            unit="GiB",
            fmt="qcow2",
            remove=True,
            xfail=True,
            xfail_objects=['size'],
            xfail_error="exceed the storage pool",
        ).execute()

        # Check volume creation for various pool types
        StorageVolumeCreateDialog(
            self,
            pool_name="dir-pool",
            vol_name="volume_of_dir_pool",
            size="256",
            unit="MiB",
            fmt="qcow2",
            remove=True,
        ).execute()

        StorageVolumeCreateDialog(
            self,
            pool_name="nfs-pool",
            pool_type="netfs",
            vol_name="volume_of_nfs_dir",
            size="10",  # Creation of big nfs vol might take so long it will result in timetout
            unit="MiB",
            fmt="raw",  # Creation of qcow2 nfs vol might take so long it will result in timetout
            remove=True,
        ).execute()

        StorageVolumeCreateDialog(
            self,
            pool_name="disk-pool",
            pool_type="disk",
            vol_name=dev.split("/")[-1] + "1",  # Partition names must follow pattern of sda1, sda2...
            size="2",  # Only 10MiB available on disk-pool
            unit="MiB",
            fmt="none",
            remove=True,
        ).execute()

        StorageVolumeCreateDialog(
            self,
            pool_name="disk-pool",
            pool_type="disk",
            vol_name="unacceptable name",  # Partition names must follow pattern of sda1, sda2..., anything else will fail
            size="2",
            unit="MiB",
            fmt="none",
            xfail=True,
            xfail_error="invalid partition name",
            remove=True,
        ).execute()

        # Try raw format
        StorageVolumeCreateDialog(
            self,
            pool_name="dir-pool",
            vol_name="volume_of_dir_pool2",
            fmt="raw",
            remove=True,
        ).execute()

        # create disk for lvm-pool
        m.execute("wipefs -a {0}; pvcreate {0}; vgcreate vol_grp1 {0}".format(dev))
        m.execute("virsh pool-define-as lvm-pool --type logical --source-name vol_grp1; virsh pool-start lvm-pool")

        StorageVolumeCreateDialog(
            self,
            pool_name="lvm-pool",
            vol_name="lvm_vol",
            size="4",
            fmt="",
            remove=True,
        ).execute()

    def testStoragePoolsDeletion(self):
        b = self.browser
        m = self.machine

        # Prepare a shutoff VM and a disk attached to the VM
        args = self.createVm("subVmTest1", running=False)

        disk_xml = '''
<disk type='volume' device='disk'>
    <driver name='qemu' type='qcow2'/>
    <source pool='images' volume='forDeletion'/>
    <target dev='vdb' bus='virtio'/>
</disk>'''
        m.execute("virsh vol-create-as images forDeletion 1M")
        m.execute(f'echo "{disk_xml}" > /tmp/diskXML; virsh attach-device --current subVmTest1 /tmp/diskXML')

        # Prepare dir pool
        dir_pool = os.path.join(self.vm_tmpdir, "dir_pool")
        m.execute(f"mkdir --mode 777 {dir_pool}")

        # Prepare some storage pools dependencies
        # NFS
        m.execute("if selinuxenabled 2>/dev/null; then setsebool -P virt_use_nfs 1; fi")
        nfs_pool = os.path.join(self.vm_tmpdir, "nfs_pool")
        mnt_exports = os.path.join(self.vm_tmpdir, "mnt_exports")
        m.execute(f"mkdir {nfs_pool} {mnt_exports}")
        self.write_file(
            "/etc/exports",
            f"{mnt_exports} 127.0.0.1/24(rw,sync,no_root_squash,no_subtree_check,fsid=0)",
            append=True)
        m.execute("systemctl restart nfs-server")

        # Physcial disk device
        dev = self.add_ram_disk(2)
        target_path = os.path.join(self.vm_tmpdir, "mnt")
        # Create mount path and parted the physical disk device
        m.execute(f"mkdir {target_path}; parted {dev} mklabel msdos")

        # ISCSI
        # TODO: no iscsid in arch
        if "debian" not in m.image and "arch" not in m.image:
            target_iqn = "iqn.2019-09.cockpit.lan"
            self.prepareStorageDeviceOnISCSI(target_iqn)

        self.login_and_go("/machines")
        self.waitPageInit()

        b.wait_in_text("#card-pf-storage-pools .pf-v5-c-card__header button", "Storage pool")
        b.click(".pf-v5-c-card .pf-v5-c-card__header button:contains(Storage pool)")
        b.wait_visible("#storage-pools-listing")

        class StoragePoolsCreationAndDeletion:
            def __init__(self,
                         test_obj,
                         name,
                         pool_type,
                         target,
                         connection="system",
                         source_path=None,
                         source_dev=None,
                         source_host="127.0.0.1",
                         source_format="dos",
                         vol_name=None,
                         activate=False,
                         remove_volume=False,
                         xfail=False,
                         xfail_error=None):
                self.test_obj = test_obj
                self.connection = connection
                self.name = name
                self.pool_type = pool_type
                self.target = target
                self.source_path = source_path
                self.source_dev = source_dev
                self.source_host = source_host
                self.source_format = source_format
                self.vol_name = vol_name
                self.activate = activate
                self.remove_volume = remove_volume
                self.xfail = xfail
                self.xfail_error = xfail_error

            def execute(self):
                self.create()
                self.delete()
                self.verify()

            def create(self):
                create_cmd = f"virsh pool-define-as {self.name} --type {self.pool_type} --target {self.target} "
                if self.pool_type == "netfs":
                    create_cmd += f"--source-host {self.source_host} --source-path {self.source_path}"
                elif self.pool_type == "iscsi":
                    create_cmd += f"--source-host {self.source_host} --source-dev {self.source_dev}"
                elif self.pool_type == "disk":
                    create_cmd += f"--source-dev {self.source_dev} --source-format {self.source_format}"

                m.execute(create_cmd)
                # Check whether the pool creation is successful
                wait(lambda: self.name in m.execute("virsh pool-list --all"))
                # Check the pool state
                b.wait_text(f"#pool-{self.name}-{self.connection}-state", "inactive")

                if self.activate:
                    m.execute(f"virsh pool-start {self.name}")
                    b.wait_text(f"#pool-{self.name}-{self.connection}-state", "active")

                    if self.pool_type == "iscsi":
                        wait(lambda: "unit:0:0:0" in m.execute(f"virsh pool-refresh {self.name}; virsh vol-list {self.name}"))
                    elif self.vol_name:
                        if self.vol_name not in m.execute(f"virsh vol-list {self.name}"):
                            m.execute(f"virsh vol-create-as {self.name} {self.vol_name} 1M")
                            wait(lambda: self.vol_name in m.execute(f"virsh vol-list {self.name}"))
                            # need refresh after volume creation
                            b.reload()
                            b.enter_page('/machines')

                self.test_obj.waitPoolRow(self.name)
                self.test_obj.togglePoolRow(self.name)

            def delete(self):
                # Expand and click the delete button
                b.click(f"#pool-{self.name}-{self.connection}-action-kebab button")
                b.click(f"#delete-pool-{self.name}-{self.connection}")
                # Check if deletion dialog is shown
                b.wait_visible("div.pf-v5-c-modal-box")

                if self.activate:
                    if self.vol_name or self.pool_type == "iscsi":
                        b.set_checked("#storage-pool-delete-volumes", self.remove_volume)
                        b.wait_in_text(".pool-volumes-delete-list", self.vol_name if self.pool_type != "iscsi" else "unit:0:0:0")
                    else:
                        # Check when no volume
                        b.wait_not_present("#storage-pool-delete-volumes")
                        b.wait_in_text(".pf-v5-c-modal-box__body div",
                                       "No volumes exist in this storage pool.")
                else:
                    # Check the content of inactive pool deletion dialog
                    b.wait_text(".pf-v5-c-modal-box__body div",
                                "Deleting an inactive storage pool will only undefine the pool. Its content will not be deleted.")

                b.click('.pf-v5-c-modal-box__footer button:contains("Delete")')

                if self.xfail:
                    b.wait_visible("div.pf-v5-c-modal-box")
                    if self.xfail_error:
                        # Check the error shown on the footer of the deletion of dialog
                        b.wait_in_text("div.pf-v5-c-modal-box .pf-v5-c-alert__description", self.xfail_error)
                    # Close the dialog
                    b.click('.pf-v5-c-modal-box__footer button:contains("Cancel")')
                    # Clean the pool if pool deletion is failed
                    if self.activate:
                        m.execute(f"virsh pool-destroy {self.name}")
                    m.execute(f"virsh pool-undefine {self.name}")

                b.wait_not_present("div.pf-v5-c-modal-box")

            def verify(self):
                if self.activate and self.vol_name:
                    if self.remove_volume:
                        if self.pool_type == "netfs":
                            wait(lambda: self.vol_name not in m.execute(f'ls -A {self.source_path}'))
                        elif self.pool_type == "dir":
                            wait(lambda: self.vol_name not in m.execute(f'ls -A {self.target}'))
                        elif self.pool_type == "disk":
                            wait(lambda: self.vol_name not in m.execute("lsblk"))
                    else:
                        if self.pool_type == "netfs":
                            vol_path = os.path.join(self.source_path, self.vol_name)
                            wait(lambda: m.execute(f'test -f {vol_path}'))
                        elif self.pool_type == "dir":
                            vol_path = os.path.join(self.target, self.vol_name)
                            wait(lambda: m.execute(f'test -f {vol_path}'))
                        elif self.pool_type == "disk":
                            wait(lambda: self.vol_name in m.execute("lsblk"))

                # general check
                self.test_obj.waitPoolRow(self.name, self.connection, present=False)
                wait(lambda: self.name not in m.execute("virsh pool-list --all"))

        # dir pool deletion
        StoragePoolsCreationAndDeletion(self,
                                        name="dir-pool-1",
                                        pool_type="dir",
                                        target=dir_pool,
                                        activate=True).execute()

        StoragePoolsCreationAndDeletion(self,
                                        name="dir-pool-2",
                                        pool_type="dir",
                                        target=dir_pool,
                                        activate=True,
                                        vol_name="dir-pool-2-vol1",
                                        remove_volume=True).execute()

        StoragePoolsCreationAndDeletion(self,
                                        name="dir-pool-3",
                                        pool_type="dir",
                                        target=dir_pool).execute()

        # netfs pool deletion
        StoragePoolsCreationAndDeletion(self,
                                        name="nfs-pool-1",
                                        pool_type="netfs",
                                        target=nfs_pool,
                                        source_path=mnt_exports,
                                        activate=True).execute()

        StoragePoolsCreationAndDeletion(self,
                                        name="nfs-pool-2",
                                        pool_type="netfs",
                                        target=nfs_pool,
                                        source_path=mnt_exports,
                                        activate=True,
                                        vol_name="nfs-pool-2-vol1",
                                        remove_volume=True).execute()

        StoragePoolsCreationAndDeletion(self,
                                        name="nfs-pool-3",
                                        pool_type="netfs",
                                        target=nfs_pool,
                                        source_path=mnt_exports).execute()

        # pdd pool deletion
        StoragePoolsCreationAndDeletion(self,
                                        name="pdd-pool-1",
                                        pool_type="disk",
                                        target="/media/",
                                        source_dev=dev,
                                        activate=True).execute()

        StoragePoolsCreationAndDeletion(self,
                                        name="pdd-pool-2",
                                        pool_type="disk",
                                        target="/media/",
                                        source_dev=dev,
                                        activate=True,
                                        vol_name=dev.split("/")[-1] + "1",
                                        remove_volume=True).execute()

        # Workaround Firefox on TF taking longer to find tr[data-row-id=\"pool-pdd-pool-3-system\"]
        with b.wait_timeout(120):
            StoragePoolsCreationAndDeletion(self,
                                            name="pdd-pool-3",
                                            pool_type="disk",
                                            target="/media/",
                                            source_dev=dev).execute()

        # iscsi pool deletion
        # TODO: Arch image has no iscsi yet
        if "debian" not in m.image and "arch" not in m.image:
            StoragePoolsCreationAndDeletion(self,
                                            name="iscsi-pool-1",
                                            pool_type="iscsi",
                                            target="/dev/disk/by-id/",
                                            source_dev=target_iqn,
                                            activate=True,
                                            remove_volume=True,
                                            xfail=True,
                                            xfail_error="this function is not supported by the connection driver: storage pool does not support vol deletion").execute()

            StoragePoolsCreationAndDeletion(self,
                                            name="iscsi-pool-2",
                                            pool_type="iscsi",
                                            target="/dev/disk/by-id/",
                                            source_dev=target_iqn).execute()

            StoragePoolsCreationAndDeletion(self,
                                            name="iscsi-pool-3",
                                            pool_type="iscsi",
                                            target="/dev/disk/by-id/",
                                            source_dev=target_iqn,
                                            activate=True).execute()

        # Check storage pool deletion when disk is attached
        self.waitPoolRow("images")
        self.togglePoolRow("images")
        self.gotoVolumesTab("images")

        b.wait_in_text("#pool-images-system-volume-forDeletion-usedby", "subVmTest1")

        b.click("#pool-images-system-action-kebab button")
        b.mouse("#delete-pool-images-system a[aria-disabled=true]", "mouseenter")
        b.wait_in_text("#delete-tooltip", "Pool's volumes are used by VMs")
        b.mouse("#delete-pool-images-system a[aria-disabled=true]", "mouseleave")

        # Detach the disk
        m.execute("virsh detach-disk subVmTest1 vdb --config")
        # Need to reload to update the disk usedby field
        b.reload()
        b.enter_page("/machines")

        self.waitPoolRow("images")
        self.togglePoolRow("images")
        self.gotoVolumesTab("images")

        b.wait_not_in_text("#pool-images-system-volume-forDeletion-usedby", "subVmTest1")

        # Check for deleting a pool whose disk is used by a VM
        b.click("#pool-images-system-action-kebab button")
        b.click("#pool-images-system-action-kebab a:contains(Delete)")
        b.set_checked("#storage-pool-delete-volumes", True)
        b.wait_in_text(".pf-v5-c-backdrop .pf-v5-c-helper-text__item-text", "Pool's volumes are used by")
        b.wait_visible(".pf-v5-c-backdrop footer button[aria-disabled=true]:contains(Delete)")

        # Check "Cancel" button
        b.click(".pf-v5-c-backdrop footer button:contains(Cancel)")
        b.wait_not_present(".pf-v5-c-backdrop")

        # Check "X" button of the dialog
        b.click("#pool-images-system-action-kebab button")
        b.click("#pool-images-system-action-kebab a:contains(Delete)")
        b.click(".pf-v5-c-backdrop button[aria-label=Close]")
        b.wait_not_present(".pf-v5-c-backdrop")

        # Check that the error shown after the title, not the footer
        # Delete the VM, or the deletion button will be disabled
        m.execute("virsh undefine subVmTest1")
        # Check both command and UI that the VM is not shown
        wait(lambda: "subVmTest1" not in m.execute("virsh list --all"))
        b.click(".machines-listing-breadcrumb li a:contains(Virtual machines)")
        b.wait_not_present("#vm-subVmTest1-system-name")
        # Go back to the storage pool list
        b.wait_in_text("#card-pf-storage-pools button", "Storage pool")
        b.click("#card-pf-storage-pools .pf-v5-c-card__header button")
        b.wait_visible("#storage-pools-listing")

        m.execute(f'chattr +a {args["image"]}')
        self.addCleanup(m.execute, f'chattr -a {args["image"]}')
        b.click("#pool-images-system-action-kebab button")
        b.click("#pool-images-system-action-kebab a:contains(Delete)")
        b.set_checked("#storage-pool-delete-volumes", True)
        b.click(".pf-v5-c-backdrop footer button[aria-disabled=false]:contains(Delete)")
        b.assert_pixels("#storage-pool-delete-modal", "storage-pool-delete-modal-error")
        wait(lambda: "forDeletion" not in m.execute("ls /var/lib/libvirt/images/"))


if __name__ == '__main__':
    test_main()
