#!/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 machineslib
import testlib


@testlib.nondestructive
class TestMachinesLifecycle(machineslib.VirtualMachinesCase):

    def testBasic(self):
        self._testBasic(run_pixel_tests=True)

    def createUser(self, user_group):
        user_name = f"test_{user_group if user_group else 'none'}_user"
        self.machine.execute(f"useradd{' -G ' + user_group if user_group else ''} {user_name}")
        self.machine.execute(f"echo '{user_name}:foobar' | chpasswd")
        # user libvirtd instance tends to SIGABRT with "Failed to find user record for uid .."
        # on shutdown during cleanup
        # so make sure that there are no leftover user processes that bleed into the next test
        self.addCleanup(self.machine.execute,
                        f"pkill -u {user_name} || true; while pgrep -u {user_name}; do sleep 0.5; done")
        # HACK: ...but it still tends to crash during shutdown (without known stack trace)
        self.allow_journal_messages('Process .*libvirtd.* of user 10.* dumped core.*')

        return user_name

    def testBasicNonrootUserUnprivileged(self):
        user = self.createUser(user_group=None)
        self._testBasic(user, superuser=False, expect_empty_list=True)

    def testBasicLibvirtUserUnprivileged(self):
        user = self.createUser(user_group='libvirt')
        self._testBasic(user, superuser=False)

    @testlib.skipImage("No SELinux", "debian-*", "ubuntu-*", "arch", "opensuse*")
    @testlib.skipImage("crashes C bridge", "rhel-8*")
    def testBasicSysadmU(self):
        user = self.createUser(user_group='libvirt')
        self.machine.execute(f"semanage login -a -s sysadm_u {user}")
        self.addCleanup(self.machine.execute, f"semanage login -d -s sysadm_u {user}")
        self._testBasic(user, superuser=False)

    def testBasicAdminUser(self):
        # The group in debian based distro should be sudo
        if "ubuntu" in self.machine.image or "debian" in self.machine.image:
            user_group = "sudo"
        else:
            user_group = "wheel"
        user = self.createUser(user_group=user_group)

        # Check NON-PRIVILEGED user won't see any VMs even though they are in wheel group
        self._testBasic(user, superuser=False, expect_empty_list=True)
        self.machine.execute("virsh destroy subVmTest1; virsh undefine subVmTest1")
        # Check wheel group PRIVILEGED user will see VMs and can do operations on them
        self._testBasic(user)

    def _testBasic(self, user=None, *, superuser=True, expect_empty_list=False, run_pixel_tests=False):
        b = self.browser
        m = self.machine

        # We want no initial watchdog
        args = self.createVm("subVmTest1", os="linux2016")

        self.login_and_go("/machines", user=user, superuser=superuser)

        if expect_empty_list:
            with b.wait_timeout(20):
                b.wait_in_text("#virtual-machines-listing .pf-v5-c-empty-state", "No VM is running")
                return
        self.waitPageInit()
        self.waitVmRow("subVmTest1")

        def checkConnectionDescription(button: str, text: str):
            popover_id = "popover-machines-connection-selector-popover-body"
            b.click(button)
            b.wait_visible("#create-vm-dialog")
            b.click('#create-vm-dialog button[aria-label="more info"]')
            b.wait_in_text(f"#{popover_id}", text)

            # Close the connection description dialog
            b.click(f"div[aria-describedby={popover_id}] button[aria-label=Close]")
            b.wait_not_present("#popover-machines-connection-selector-popover-body")
            # Close the VM creation dialog
            b.click('#create-vm-dialog button:contains("Cancel")')
            b.wait_not_present("#create-vm-dialog")

        def checkNeedsShutdownLabel(message):
            b.wait_not_present("#vm-subVmTest1-needs-shutdown")
            m.execute("virt-xml subVmTest1 --add-device --watchdog action=reset --update")
            m.execute("virt-xml subVmTest1 --edit --watchdog action=pause")
            b.click("#vm-subVmTest1-needs-shutdown")
            b.wait_in_text(".pf-v5-c-popover__body", message)
            m.execute("virt-xml subVmTest1 --remove-device --watchdog 1 --update")

        checkConnectionDescription("#import-existing-vm", "Ideal for server VMs")
        checkConnectionDescription("#create-new-vm", "Good choice for desktop virtualization")
        checkNeedsShutdownLabel("Watchdog")

        b.wait_in_text("#vm-subVmTest1-system-state", "Running")
        self.goToVmPage("subVmTest1")
        if run_pixel_tests:
            b.wait_visible("#vm-subVmTest1-add-iface-button:not(:disabled)")
            b.assert_pixels("#vm-details", "vm-details", ignore=[
                ".memory-usage-chart",
                ".vcpu-usage-chart",
                "#vm-subVmTest1-disks .pf-v5-c-card__body",
                "#vm-subVmTest1-networks .pf-v5-c-card__body",
                "#vm-subVmTest1-hostdevs",
                "#vm-subVmTest1-emulated-machine",
                ])
        b.wait_in_text("#vm-subVmTest1-cpu", "1 vCPU")

        b.wait_in_text("#vm-subVmTest1-boot-order", "disk,network")
        emulated_machine = b.text("#vm-subVmTest1-emulated-machine")
        self.assertGreater(len(emulated_machine), 0)  # emulated machine varies across test machines

        # switch to and check Usage
        b.click("#vm-subVmTest1-usage")
        b.wait_in_text(".memory-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure", "128 MiB")
        b.wait_not_in_text(".memory-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure", "0 /")
        usage = b.text(".memory-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure")
        usage = usage.split("/ 128 MiB")[0]
        testlib.wait(lambda: float(usage) > 0.0, delay=3)

        b.wait_in_text(".vcpu-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure", "1 vCPU")
        usage = b.text(".vcpu-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure")
        usage = usage.split("% of 1 vCPU")[0]
        # CPU usage cannot be nonzero with blank image, so just ensure it's a percentage
        testlib.wait(lambda: float(usage) <= 100.0, delay=3)

        # suspend/resume
        m.execute("virsh suspend subVmTest1")
        b.wait_in_text("#vm-subVmTest1-system-state", "Paused")
        # resume sometimes fails with "unable to execute QEMU command 'cont':
        # Resetting the Virtual Machine is required"
        m.execute('virsh resume subVmTest1 || { virsh destroy subVmTest1; virsh start subVmTest1; }')
        b.wait_in_text("#vm-subVmTest1-system-state", "Running")

        # Wait for the system to completely start
        self.waitGuestBooted(args['logfile'])

        # Send Non-Maskable Interrupt (no change in VM state is expected)
        self.performAction("subVmTest1", "sendNMI")

        self.waitLogFile(args['logfile'], "NMI received")

        # pause
        self.performAction("subVmTest1", "pause")
        # check removing button of disk and network interface when the VM is paused
        b.click("#vm-subVmTest1-disks-vda-action-kebab")
        b.wait_visible(".pf-m-disabled #delete-vm-subVmTest1-disks-vda")
        b.click("#vm-subVmTest1-iface-1-action-kebab")
        b.wait_visible(".pf-m-disabled #delete-vm-subVmTest1-iface-1")

        # resume
        self.performAction("subVmTest1", "resume")

        # reboot
        self.performAction("subVmTest1", "reboot", logPath=args['logfile'])
        self.waitLogFile(args['logfile'], "reboot: Power down")
        b.wait_in_text("#vm-subVmTest1-system-state", "Running")

        # force reboot
        self.performAction("subVmTest1", "forceReboot", logPath=args['logfile'])
        self.waitGuestBooted(args['logfile'])
        b.wait_in_text("#vm-subVmTest1-system-state", "Running")

        # the shut off button beside the VM name
        self.waitGuestBooted(args['logfile'])
        b.click("#vm-subVmTest1-system-shutdown-button")
        b.wait_visible("#vm-subVmTest1-system-confirm-action-modal")
        b.click(".pf-v5-c-modal-box__footer #vm-subVmTest1-system-off")
        with b.wait_timeout(60):
            b.wait_in_text("#vm-subVmTest1-system-state", "Shut off")
        b.wait_visible("#vm-subVmTest1-system-run")

        # the shut off button in the extended operation list
        self.machine.execute(f"echo '' > {args['logfile']}")
        b.click("#vm-subVmTest1-system-run")
        b.wait_in_text("#vm-subVmTest1-system-state", "Running")
        self.waitGuestBooted(args['logfile'])
        self.performAction("subVmTest1", "off")

        # force shut off
        b.click("#vm-subVmTest1-system-run")
        b.wait_in_text("#vm-subVmTest1-system-state", "Running")
        self.performAction("subVmTest1", "forceOff")

        # continue shut off validation - usage should drop to zero
        b.wait_in_text(".memory-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure", "0 /")
        b.wait_in_text(".vcpu-usage-chart .pf-v5-c-progress__status > .pf-v5-c-progress__measure", "0%")

        # shut off of a transient VM will redirect us to the main page
        m.execute("virsh dumpxml subVmTest1 > /tmp/subVmTest1.xml")
        m.execute("virsh start {0}; virsh undefine {0}".format("subVmTest1"))
        b.wait_visible("div[data-vm-transient=\"true\"]")
        self.performAction("subVmTest1", "forceOff", checkExpectedState=False)
        b.wait_in_text("#virtual-machines-listing .pf-v5-c-empty-state", "No VM is running")
        m.execute("virsh define --file /tmp/subVmTest1.xml")

        # start another one, should appear automatically
        args2 = self.createVm("subVmTest2")
        b.wait_in_text("#vm-subVmTest2-system-state", "Running")
        self.goToVmPage("subVmTest2")
        b.wait_in_text("#vm-subVmTest2-cpu", "1 vCPU")
        b.wait_in_text("#vm-subVmTest2-boot-order", "disk,network")

        self.goToMainPage()
        self.waitVmRow("subVmTest1")
        self.waitVmRow("subVmTest2")

        if run_pixel_tests:
            b.wait_visible("#import-existing-vm[aria-disabled=false]")
            b.wait_visible("#create-new-vm[aria-disabled=false]")
            b.assert_pixels("#virtual-machines-page", "main-page")

        # stop second VM, event handling should still work
        self.performAction("subVmTest2", "forceOff")

        b.click("#vm-subVmTest2-system-run")

        # reboot through shutdown dialog
        self.machine.execute(f"echo '' > {args2['logfile']}")
        b.click("#vm-subVmTest2-system-shutdown-button")
        b.wait_visible("#vm-subVmTest2-system-confirm-action-modal")
        b.click(".pf-v5-c-modal-box__footer button:contains(Reboot)")
        b.wait_not_present("#vm-subVmTest2-system-confirm-action-modal")
        b.wait_in_text("#vm-subVmTest2-system-state", "Running")

        # Check uptime
        b.click("#vm-subVmTest2-system-shutdown-button")
        b.wait_visible("#vm-subVmTest2-system-confirm-action-modal")
        # it starts with "less than a minute", so this should be reasonably race free
        if superuser:
            b.wait_in_text("#uptime", "minute")
        else:
            # unprivileged sessions can't read the log
            b._wait_present(".uptime-not-available")
        if run_pixel_tests:
            b.assert_pixels("#vm-subVmTest2-system-confirm-action-modal", "shutdown-confirm-dialog")
        b.click(".pf-v5-c-modal-box__footer button:contains(Cancel)")
        b.wait_not_present("#vm-subVmTest2-system-confirm-action-modal")

        # uptime parsing robustness: unparseable format
        self.machine.execute(f"sed -i '/: starting up/ s/^/invalidtime/' {args2['qemulog']}")
        b.click("#vm-subVmTest2-system-shutdown-button")
        b.wait_visible("#vm-subVmTest2-system-confirm-action-modal")
        b._wait_present(".uptime-not-available")
        b.click(".pf-v5-c-modal-box__footer button:contains(Cancel)")
        b.wait_not_present("#vm-subVmTest2-system-confirm-action-modal")

        # uptime parsing robustness: no "starting up" line
        self.machine.execute(f"sed -i '/: starting up/d' {args2['qemulog']}")
        b.click("#vm-subVmTest2-system-shutdown-button")
        b.wait_visible("#vm-subVmTest2-system-confirm-action-modal")
        b._wait_present(".uptime-not-available")
        b.click(".pf-v5-c-modal-box__footer button:contains(Cancel)")
        b.wait_not_present("#vm-subVmTest2-system-confirm-action-modal")

        b.set_input_text("#text-search", "subVmTest2")
        self.waitVmRow("subVmTest2")
        self.waitVmRow("subVmTest1", "system", False)

        # Check VM search is case insensitive
        b.set_input_text("#text-search", "SuBvMtEsT2")
        self.waitVmRow("subVmTest2")
        self.waitVmRow("subVmTest1", "system", False)

        b.select_PF("#vm-state-select-toggle", "Running")
        self.waitVmRow("subVmTest1", "system", False)
        self.waitVmRow("subVmTest2")

        b.set_input_text("#text-search", "")
        self.waitVmRow("subVmTest1", "system", False)
        self.waitVmRow("subVmTest2")

        b.select_PF("#vm-state-select-toggle", "Shut off")
        self.waitVmRow("subVmTest1")
        self.waitVmRow("subVmTest2", "system", False)

        b.select_PF("#vm-state-select-toggle", "All")
        self.waitVmRow("subVmTest1")
        self.waitVmRow("subVmTest2")

        # Check correctness of the toast notifications list
        # We 'll create errors by starting to start domains when the default network in inactive
        self.createVm("subVmTest3")
        m.execute("virsh destroy subVmTest2; virsh destroy subVmTest3; virsh net-destroy default")

        def tryRunDomain(name, connection):
            self.waitVmRow(name)

            b.click(f"#vm-{name}-{connection}-run")

        # Try to run subVmTest1 - it will fail because of inactive default network
        tryRunDomain('subVmTest1', 'system')
        b.wait_visible("#vm-subVmTest1-system-state-error")
        if run_pixel_tests:
            b.assert_pixels("tr[data-row-id=vm-subVmTest1-system]", "vm-state-error")
        b.click('#vm-subVmTest1-system-state-error button:contains("view more")')
        b.wait_in_text(".pf-v5-c-popover", "VM subVmTest1 failed to start")
        b.click('#vm-subVmTest1-system-state-error button[aria-label=Close]')

        # Try to run subVmTest2
        tryRunDomain('subVmTest2', 'system')
        b.click('#vm-subVmTest2-system-state-error button:contains("view more")')
        b.wait_in_text(".pf-v5-c-popover", "VM subVmTest2 failed to start")
        b.click('#vm-subVmTest2-system-state-error button[aria-label=Close]')

    def testCloneSessionConnection(self):
        self.testClone(connectionName='session')

    def testClone(self, connectionName='system'):
        b = self.browser

        self.run_admin("mkdir /tmp/vmdir", connectionName)
        self.addCleanup(self.run_admin, "rm -rf /tmp/vmdir/", connectionName)

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

        self.createVm("subVmTest1", running=False, connection=connectionName)

        self.waitVmRow("subVmTest1", connectionName=connectionName)

        self.performAction("subVmTest1", "clone", connectionName=connectionName)

        b.wait_text(".pf-v5-c-modal-box__title-text", "Create a clone VM based on subVmTest1")
        b.click("footer button.pf-m-primary")
        b.wait_not_present(".pf-v5-c-modal-box")
        self.waitVmRow("subVmTest1-clone", connectionName=connectionName)

    def testRename(self):
        b = self.browser

        self.createVm("old", running=False)

        self.login_and_go("/machines")

        self.waitPageInit()

        self.performAction("old", "rename")

        b.set_input_text("#rename-dialog-new-name", "new")
        b.click("#rename-dialog-confirm")
        self.waitVmRow("new")
        self.waitVmRow("old", "system", False)

        # Rename to itself and expect error
        self.performAction("new", "rename")

        b.set_input_text("#rename-dialog-new-name", "new")
        b.click("#rename-dialog-confirm")
        b.wait_in_text(".pf-v5-c-modal-box__body .pf-v5-c-alert.pf-m-danger", "Can't rename domain to itself")
        b.click(".pf-v5-c-modal-box button:contains('Cancel')")
        b.wait_not_present(".pf-v5-c-modal-box")

        self.goToVmPage("new")
        self.performAction("new", "rename")

        b.set_input_text("#rename-dialog-new-name", "test%")
        b.click("#rename-dialog-confirm")
        b.wait(lambda: "vm?name=test%25&connection=system" in b.eval_js("window.location.href"))
        b.wait_text("h2.vm-name", "test%")
        b.wait_not_present("#navbar-oops")

    def testEditDescription(self):
        b = self.browser

        self.createVm("mac", running=False)
        self.login_and_go("/machines")
        self.waitPageInit()
        self.goToVmPage("mac")

        self.performAction("mac", "edit-description")

        # Non-ascii chars, unbalanced quotes, backslash, multiple lines
        desc = '"Döscrü\\ptiän \'\'\' كرة القدم\nSecond <b>line</b>'

        b.set_input_text("#edit-description-dialog-description", desc)
        b.click("#edit-description-dialog-confirm")
        b.wait_not_present("#edit-description-dialog-confirm")

        b.wait_text(".vm-description", desc)

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

        name = "subVmTest1"
        img2 = f"/var/lib/libvirt/images/{name}-2.img"

        args = self.createVm(name, graphics='vnc')

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

        m.execute(f"test -f {img2}")

        self.goToVmPage("subVmTest1")

        def addDisk(volName, poolName, vmName='subVmTest1'):
            # Virsh does not offer some option to create disks of type volume
            # We have to do this from cockpit UI
            b.click(f"#vm-{vmName}-disks-adddisk")  # button
            b.wait_visible(f"#vm-{vmName}-disks-adddisk-dialog-modal-window")
            b.wait_visible("label:contains(Create new)")  # radio button label in the modal dialog

            b.select_from_dropdown(f"#vm-{vmName}-disks-adddisk-new-select-pool", poolName)
            b.set_input_text(f"#vm-{vmName}-disks-adddisk-new-name", volName)
            b.set_input_text(f"#vm-{vmName}-disks-adddisk-new-size", "10")
            b.select_from_dropdown(f"#vm-{vmName}-disks-adddisk-new-unit", "MiB")
            b.click(f"#vm-{vmName}-disks-adddisk-permanent")

            b.click(f"#vm-{vmName}-disks-adddisk-dialog-add")
            with b.wait_timeout(30):
                b.wait_not_present(f"#vm-{vmName}-disks-adddisk-dialog-modal-window")

            b.wait_visible(f"#vm-{vmName}-disks-vdb-source-volume")
            b.wait_visible(f"#vm-{vmName}-disks-vdb-source-pool")

        secondDiskVolName = "mydisk"
        poolName = "images"
        secondDiskPoolPath = "/var/lib/libvirt/images/"

        addDisk(secondDiskVolName, poolName)

        self.performAction(name, "delete")

        b.wait_visible(f"#vm-{name}-delete-modal-dialog .pf-v5-c-modal-box__body:contains(The VM {name} is running)")
        b.wait_visible(f"#vm-{name}-delete-modal-dialog ul li:first-child .disk-source-file:contains({img2})")
        # virsh attach-disk does not create disks of type volume
        b.wait_visible(f"#vm-{name}-delete-modal-dialog .disk-source-volume:contains({secondDiskVolName})")
        b.wait_visible(f"#vm-{name}-delete-modal-dialog .disk-source-pool:contains({poolName})")
        b.assert_pixels(f"#vm-{name}-delete-modal-dialog", "vm-delete-dialog", skip_layouts=["rtl"])
        b.click(f"#vm-{name}-delete-modal-dialog button:contains(Delete)")
        b.wait_not_present(f"#vm-{name}-delete-modal-dialog")

        self.waitVmRow(name, "system", False)

        m.execute(f"while test -f {img2}; do sleep 1; done")
        m.execute(f"while test -f {secondDiskPoolPath + secondDiskVolName}; do sleep 1; done")

        self.assertNotIn(name, m.execute("virsh list --all --name"))

        # Try to delete a paused VM
        name = "paused-test-vm"
        args = self.createVm(name)

        self.goToVmPage(name)

        # Make sure that the VM booted normally before attempting to suspend it
        self.waitGuestBooted(args['logfile'])

        self.machine.execute(f"virsh -c qemu:///system suspend {name}")
        b.wait_in_text(f"#vm-{name}-system-state", "Paused")
        self.performAction(name, "delete")
        b.click(f"#vm-{name}-delete-modal-dialog button:contains(Delete)")
        self.waitVmRow(name, 'system', False)

        if not m.image.startswith("rhel-8-"):
            # Delete a VM with snapshots and ensure the qcow2 image overlays get also cleaned up
            name = "vm-with-snapshots"
            self.createVm(name, running=False)

            desc = "Description of snapshotA"
            m.execute(f"virsh snapshot-create-as --domain {name} --name snapshotA --description '{desc}'")

            qemu_img_info = m.execute(f"qemu-img info /var/lib/libvirt/images/{name}-2.img")
            self.assertIn("snapshotA", qemu_img_info)

            # snapshots events not available yet: https://gitlab.com/libvirt/libvirt/-/issues/44
            b.reload()
            b.enter_page('/machines')
            self.goToVmPage(name)
            b.wait_in_text(f"#vm-{name}-snapshot-0-name", "snapshotA")

            self.performAction(name, "delete")

            # Unselect the disks - we don't want to delete them
            b.click("#vm-vm-with-snapshots-delete-modal-dialog ul li:first-child input")
            b.click("#vm-vm-with-snapshots-delete-modal-dialog button:contains(Delete)")
            b.wait_not_present(f"#vm-{name}-delete-modal-dialog")

            qemu_img_info = m.execute(f"qemu-img info /var/lib/libvirt/images/{name}-2.img")
            self.assertNotIn("snapshotA", qemu_img_info)

            # Check deleting VM fails if deleting snapshot is not possible and error message is shown
            name = "vm-with-failed-snapshots"
            self.createVm(name, running=False)

            m.execute("virsh vol-create-as --pool images --name tmpdisk --capacity 1 --format qcow2")
            m.execute(f"virt-xml {name} --add-device --disk source.pool=images,source.volume=tmpdisk,target.dev=sdb,driver.type=qcow2")  # noqa: E501
            m.execute(f"virsh snapshot-create-as --domain {name} --name snapshotFails")
            b.reload()
            b.enter_page('/machines')
            self.goToVmPage(name)
            b.wait_text(f"#vm-{name}-snapshot-0-name", "snapshotFails")
            self.goToMainPage()
            self.performAction(name, "delete")
            b.wait_visible(f"#vm-{name}-delete-modal-dialog")
            m.execute(f"virsh snapshot-delete --domain {name} --snapshotname snapshotFails")
            b.click(f"#vm-{name}-delete-modal-dialog button:contains(Delete)")
            b.wait_in_text(f"#vm-{name}-delete-modal-dialog .pf-v5-c-alert__description", "Domain snapshot not found")
            b.click(f"#vm-{name}-delete-modal-dialog button:contains(Cancel)")
            m.execute(f"virsh undefine {name}")

        # Try to delete a VM and keep its volume
        name = "vm-keep-vol"
        args = self.createVm(name)
        self.waitVmRow(name)
        testlib.wait(lambda: name in m.execute("virsh list --all --name"))

        self.performAction(name, "delete")
        b.set_checked(f"#vm-{name}-delete-modal-dialog input[type=checkbox]", False)
        b.click(f"#vm-{name}-delete-modal-dialog button:contains(Delete)")

        self.waitVmRow(name, present=False)
        testlib.wait(lambda: name not in m.execute("virsh list --all --name"))
        m.execute(f"test -f {args['image']}")

        # Try to delete a transient VM
        name = "transient-VM"
        args = self.createVm(name)
        m.execute(f"virsh undefine {name}")
        b.wait_visible(f"tr[data-row-id=vm-{name}-system][data-vm-transient=true]")
        b.click(f"#vm-{name}-system-action-kebab")
        b.wait_visible(f".pf-m-aria-disabled #vm-{name}-system-delete")  # delete buton should be disabled
        b.click(f"#vm-{name}-system-forceOff")
        b.wait_visible(f"#vm-{name}-system-confirm-action-modal")
        b.click(f".pf-v5-c-modal-box__footer #vm-{name}-system-forceOff")
        self.waitVmRow(name, 'system', False)
        b.wait_not_present(f'#vm-{name}-system-state-error button:contains("view more")')

        # Try to delete a VM where storage deletion will fail
        name = "vm-fail-storage-deletion"
        args = self.createVm(name)
        self.waitVmRow(name)
        self.goToVmPage(name)
        testlib.wait(lambda: name in m.execute("virsh list --all --name"))
        addDisk(secondDiskVolName, poolName, name)
        # Remove VM's disk from command line
        m.execute(f"rm {(secondDiskPoolPath + secondDiskVolName)}")
        # Prevent file from being deleting to test handling of unsuccessful storage deletion
        m.execute(f"chattr +i {args['image']}")
        self.addCleanup(m.execute, f"chattr -i {args['image']}")

        self.performAction(name, "delete")
        b.set_checked(f"#vm-{name}-delete-modal-dialog li:nth-child(1) input[type=checkbox]", True)
        b.set_checked(f"#vm-{name}-delete-modal-dialog li:nth-child(2) input[type=checkbox]", True)
        b.click(f"#vm-{name}-delete-modal-dialog button:contains(Delete)")

        # Check VM got deleted, but there is a warning about unsuccessful storage deletion
        self.waitVmRow(name, present=False)
        testlib.wait(lambda: name not in m.execute("virsh list --all --name"))
        b.wait_visible(".pf-v5-c-alert-group li .pf-v5-c-alert")
        b.wait_in_text(".pf-v5-c-alert-group li .pf-v5-c-alert .pf-v5-c-alert__title",
                       f"Could not delete all storage for {name}")
        b.click("button.alert-link.more-button")
        b.wait_in_text(".pf-v5-c-alert-group li .pf-v5-c-alert .pf-v5-c-alert__description", args['image'])
        b.wait_in_text(".pf-v5-c-alert-group li .pf-v5-c-alert .pf-v5-c-alert__description", secondDiskVolName)
        # Close the notification
        b.click(".pf-v5-c-alert-group li .pf-v5-c-alert button.pf-m-plain")

        # Delete a shut-off guest and verify the storage was removed
        name = "vm-shutoff"
        img2 = f"/var/lib/libvirt/images/{name}-2.img"
        self.createVm(name, running=False)
        # Guest disk image exists
        m.execute(f"while ! test -f {img2}; do sleep 1; done")

        self.performAction(name, "delete")

        b.wait_visible(f"#vm-{name}-delete-modal-dialog .disk-source-file:contains({img2})")
        b.wait_visible(f"#vm-{name}-delete-modal-dialog input[name='check-action-vda']:checked")
        b.click(f"#vm-{name}-delete-modal-dialog button:contains(Delete)")
        b.wait_not_present(f"#vm-{name}-delete-modal-dialog")

        self.waitVmRow(name, "system", False)

        # Guest disk image has been deleted
        m.execute(f"while test -f {img2}; do sleep 1; done")

        self.assertNotIn(name, m.execute("virsh list --all --name"))

        # Deleting a running guest will disconnect the serial console
        self.allow_browser_errors("Disconnection timed out.")
        self.allow_journal_messages(".* couldn't shutdown fd: Transport endpoint is not connected")


if __name__ == '__main__':
    testlib.test_main()
