#!/usr/bin/python3

# This file is part of Cockpit.
#
# Copyright (C) 2013 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 functools
import os
import subprocess
import time
import xml.etree.ElementTree as ET

import parent
from testlib import *
from netlib import NetworkHelpers
from storagelib import StorageHelpers
from machinesxmls import *

def getNetworkDevice(m):
    net_devices_str = m.execute("virsh iface-list")
    net_devices_str = net_devices_str.split("\n", 2)[2]; # Remove first 2 lines of table header
    if not net_devices_str in ["\n", "\r\n"]:
        device = net_devices_str.split(' ', 2)[1] # Get the name of device, ignoring spacing before device string
    else: # If $virsh-iface list did not return any device, check virsh nodedev-list net:noh
        net_devices_str = m.execute("virsh nodedev-list net")
        net_devices_str = net_devices_str.split("\n", 2)[0];
        device = net_devices_str.split("_", 2)[1]; # Ignore prefix (example: net_enp0s31f6_8c_16_45_5f_77_34)

    return device

def readFile(name):
    content = ''
    if os.path.exists(name):
        with open(name, 'r') as f:
            content = f.read().replace('\n', '')
    return content

# If this test fails to run, the host machine needs:
# echo "options kvm-intel nested=1" > /etc/modprobe.d/kvm-intel.conf
# rmmod kvm-intel && modprobe kvm-intel || true
@skipImage("Atomic cannot run virtual machines", "fedora-coreos")
@nondestructive
class TestMachines(MachineCase, StorageHelpers, NetworkHelpers):
    created_pool = False

    def setUp(self):
        super().setUp()

        m = self.machine

        # Keep pristine state of libvirt
        self.restore_dir("/var/lib/libvirt")
        self.restore_dir("/etc/libvirt")

        # Cleanup pools
        self.addCleanup(m.execute, "rm -rf /run/libvirt/storage/*")

        # Cleanup networks
        self.addCleanup(m.execute, "rm -rf /run/libvirt/network/test_network*")

        self.startLibvirt()
        self.addCleanup(m.execute, "systemctl stop libvirtd")

        # Stop all networks
        self.addCleanup(m.execute, "for n in $(virsh net-list --all --name); do virsh net-destroy $n || true; done")

        # Stop all domains
        self.addCleanup(m.execute, "for d in $(virsh list --name); do virsh destroy $d || true; done")

        # we don't have configuration to open the firewall for local libvirt machines, so just stop firewalld
        m.execute("systemctl stop firewalld; systemctl try-restart libvirtd")

        # FIXME: report downstream; AppArmor noisily denies some operations, but they are not required for us
        self.allow_journal_messages('.* type=1400 .* apparmor="DENIED" operation="capable" profile="\S*libvirtd.* capname="sys_rawio".*')
        # AppArmor doesn't like the non-standard path for our storage pools
        self.allow_journal_messages('.* type=1400 .* apparmor="DENIED" operation="open" profile="virt-aa-helper" name="%s.*' % self.vm_tmpdir)
        if m.image in ["ubuntu-2004"]:
            self.allow_journal_messages('.* type=1400 .* apparmor="DENIED" operation="open" profile="libvirt.* name="/" .* denied_mask="r" .*')
            self.allow_journal_messages('.* type=1400 .* apparmor="DENIED" operation="open" profile="libvirt.* name="/sys/bus/nd/devices/" .* denied_mask="r" .*')

        # FIXME: report downstream: qemu often crashes in testAddDisk and testMultipleSettings
        if m.image in ["ubuntu-stable"]:
            self.allow_journal_messages('Process .*qemu-system-x86.* of user .* dumped core.')

        # FIXME: testDomainMemorySettings on Fedora-32 reports this. Figure out where it comes from.
        # Ignoring just to unbreak tests for now
        self.allow_journal_messages("Failed to get COMM: No such process")

        # HACK: https://launchpad.net/bugs/1802005
        if m.image == "ubuntu-stable":
            m.execute("until test -e /run/libvirt/libvirt-sock; do sleep 1; done")
            m.execute("chmod o+rwx /run/libvirt/libvirt-sock")

    def toggleVmRow(self, vmName, connectionName='system'):
        self.browser.click("tbody tr[data-row-id=vm-{0}-{1}] .pf-c-table__toggle button".format(vmName, connectionName)) # click on the row header

    def waitVmRow(self, vmName, connectionName='system', present=True):
        b = self.browser
        vm_row = "tbody tr[data-row-id=vm-{0}-{1}] td".format(vmName, connectionName)
        if present:
            b.wait_present(vm_row)
        else:
            b.wait_not_present(vm_row)

    def togglePoolRow(self, poolName, connectionName="system"):
        self.browser.click("tbody tr[data-row-id=pool-{0}-system] th".format(poolName)) # click on the row header

    def waitPoolRow(self, poolName, connectionName="system", present="true"):
        b = self.browser
        pool_row = "tbody tr[data-row-id=pool-{0}-{1}] th".format(poolName, connectionName)
        if present:
            b.wait_present(pool_row)
        else:
            b.wait_not_present(pool_row)

    def toggleNetworkRow(self, networkName, connectionName="system"):
        self.browser.click("tbody tr[data-row-id=network-{0}-system] th".format(networkName)) # click on the row header

    def waitNetworkRow(self, networkName, connectionName="system", present="true"):
        b = self.browser
        network_row = "tbody tr[data-row-id=network-{0}-{1}] th".format(networkName, connectionName)
        if present:
            b.wait_present(network_row)
        else:
            b.wait_not_present(network_row)

    def startLibvirt(self):
        m = self.machine

        # Ensure everything has started correctly
        m.execute("systemctl start libvirtd.service")

        # Wait until we can get a list of domains
        m.execute("until virsh list; do sleep 1; done")
        # Wait for the network 'default' to become active
        m.execute("virsh net-start default || true")
        m.execute("until virsh net-info default | grep 'Active:\s*yes'; do sleep 1; done")

    def startVm(self, name, graphics='spice', ptyconsole=False):
        m = self.machine

        image_file = m.pull("cirros")
        img = "/var/lib/libvirt/images/{0}-2.img".format(name)
        m.upload([image_file], img)
        m.execute("chmod 777 {0}".format(img))

        args = {
            "name": name,
            "image": img,
            "logfile": None,
            "console": "",
        }

        if ptyconsole:
            args["console"] = PTYCONSOLE_XML
        else:
            m.execute("chmod 777 /var/log/libvirt")
            args["logfile"] = "/var/log/libvirt/console-{0}.log".format(name)
            args["console"] = CONSOLE_XML.format(log=args["logfile"])

        if graphics == 'spice':
            cxml = SPICE_XML
        elif graphics == 'vnc':
            cxml = VNC_XML
        elif graphics == 'none':
            cxml = ""
        else:
            assert False, "invalid value for graphics"
        args["graphics"] = cxml.format(**args)

        if not self.created_pool:
            xml = POOL_XML.format(path="/var/lib/libvirt/images")
            m.execute("echo \"{0}\" > /tmp/xml && virsh pool-define /tmp/xml && virsh pool-start images".format(xml))
            self.created_pool = True

        xml = DOMAIN_XML.format(**args)
        m.execute("echo \"{0}\" > /tmp/xml && virsh define /tmp/xml && virsh start {1}".format(xml, name))

        m.execute('[ "$(virsh domstate {0})" = running ] || '
                  '{{ virsh dominfo {0} >&2; cat /var/log/libvirt/qemu/{0}.log >&2; exit 1; }}'.format(name))

        # TODO check if kernel is booted
        # Ideally we would like to check guest agent event for that
        # Libvirt has a signal for that too: VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE
        # https://libvirt.org/git/?p=libvirt-python.git;a=blob;f=examples/guest-vcpus/guest-vcpu-daemon.py;h=30fcb9ce24165c59dec8d9bbe6039f56382e81e3;hb=HEAD

        self.allow_journal_messages('.*denied.*comm="pmsignal".*')

        return args

    # Preparations for iscsi storage pool; return the system's initiator name
    def prepareStorageDeviceOnISCSI(self, target_iqn):
        m = self.machine

        # ensure that we generate a /etc/iscsi/initiatorname.iscsi
        m.execute("systemctl start iscsid")

        orig_iqn = m.execute("sed -n '/^InitiatorName=/ { s/^.*=//; p }' /etc/iscsi/initiatorname.iscsi").strip()

        # Increase the iSCSI timeouts for heavy load during our testing
        self.sed_file(r"s|^\(node\..*log.*_timeout = \).*|\1 60|", "/etc/iscsi/iscsid.conf")

        # make sure this gets cleaned up, to avoid reboot hangs (https://bugzilla.redhat.com/show_bug.cgi?id=1817241)
        self.restore_dir("/var/lib/iscsi")

        # Setup a iSCSI target
        m.execute("""
                  targetcli /backstores/ramdisk create test 50M
                  targetcli /iscsi create %(tgt)s
                  targetcli /iscsi/%(tgt)s/tpg1/luns create /backstores/ramdisk/test
                  targetcli /iscsi/%(tgt)s/tpg1/acls create %(ini)s
                  """ % {"tgt": target_iqn, "ini": orig_iqn})

        self.addCleanup(m.execute, "targetcli /backstores/ramdisk delete test && targetcli /iscsi delete %s && (iscsiadm -m node -o delete || true)" % target_iqn)
        return orig_iqn

    def testState(self):
        b = self.browser
        m = self.machine
        name = "subVmTest1"
        args = self.startVm(name)

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow(name)

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        m.execute('[ "$(virsh domstate {0})" = running ] || '
                  '{{ virsh dominfo {0} >&2; cat /var/log/libvirt/qemu/{0}.log >&2; exit 1; }}'.format(name))

        return args

    def testBasic(self):
        self._testBasic()

    def createUser(self, user_group):
        user_name = 'test_' + user_group + '_user'
        self.machine.execute(
            'useradd -G {0} {1} && echo "{1}:foobar" | chpasswd'.format(user_group, user_name))
        # 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, "pkill -u {0}; while pgrep -u {0}; do sleep 0.5; done".format(user_name))
        # 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

    # FIXME remove this skipImage
    @skipImage('Fails with Rejected send message, 1 matched rules; type="method_call"', "rhel-8-3", "rhel-8-3-distropkg", "ubuntu-stable", "ubuntu-2004", "debian-testing", "debian-stable", "centos-8-stream")
    def testBasicLibvirtUserUnprivileged(self):
        user = self.createUser(user_group='libvirt')
        self._testBasic(user, False)

    def testBasicWheelUserUnprivileged(self):
        user = self.createUser(user_group='wheel')
        self._testBasic(user, False, True)
        self.allow_authorize_journal_messages()

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

        args = self.startVm("subVmTest1")

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

        if expect_empty_list:
            b.wait_in_text("#virtual-machines-listing thead tr td", "No VM is running")
            return
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")
        b.wait_in_text("#vm-subVmTest1-vcpus-count", "1")

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

        def get_usage(selector):
            i = 0
            content = b.text(selector)
            while content[i].isdigit() or content[i] == ".":
                i += 1
            return float(content[:i])

        # switch to and check Usage
        b.click("#vm-subVmTest1-usage")
        b.wait_in_text("tbody.pf-m-expanded .ct-listing-panel-body td:nth-child(1) .usage-donut-caption", "256 MiB")
        b.wait_present("#chart-donut-0 .donut-title-big-pf")
        b.wait(lambda: get_usage("#chart-donut-0") > 0.0)
        b.wait_in_text("tbody.pf-m-expanded .ct-listing-panel-body td:nth-child(2) .usage-donut-caption", "1 vCPU")
        # CPU usage cannot be nonzero with blank image, so just ensure it's a percentage
        b.wait_present("#chart-donut-1 .donut-title-big-pf")
        self.assertLessEqual(get_usage("#chart-donut-1"), 100.0)

        # suspend/resume
        m.execute("virsh suspend subVmTest1")
        b.wait_in_text("#vm-subVmTest1-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-state", "running")

        # Wait for the system to completely start
        wait(lambda: "login as 'cirros' user." in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

        # Send Non-Maskable Interrupt (no change in VM state is expected)
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-sendNMI")

        b.wait(lambda: "NMI received" in self.machine.execute("cat {0}".format(args["logfile"])))

        # pause
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-pause")
        b.wait_in_text("#vm-subVmTest1-state", "paused")

        # resume
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-resume")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # reboot
        self.machine.execute("echo '' > {0}".format(args["logfile"]))
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-reboot")
        wait(lambda: "reboot: Power down" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # force reboot
        self.machine.execute("echo '' > {0}".format(args["logfile"]))
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceReboot")
        wait(lambda: "Initializing cgroup subsys" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # shut off
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # continue shut off validation - usage should drop to zero
        b.wait(lambda: get_usage("#chart-donut-0") == 0.0)
        b.wait(lambda: get_usage("#chart-donut-1") == 0.0)

        # start another one, should appear automatically
        self.startVm("subVmTest2")
        self.toggleVmRow("subVmTest2")
        b.wait_in_text("#vm-subVmTest2-state", "running")
        b.wait_in_text("#vm-subVmTest2-vcpus-count", "1")
        b.wait_in_text("#vm-subVmTest2-boot-order", "disk,network")

        if authorized:
            # restart libvirtd
            m.execute("systemctl stop libvirtd.service")
            b.wait_in_text(".pf-c-empty-state", "Virtualization Service (libvirt) is Not Active")
            m.execute("systemctl start libvirtd.service")
            # HACK: https://launchpad.net/bugs/1802005
            if m.image == "ubuntu-stable":
                m.execute("until test -e /run/libvirt/libvirt-sock; do sleep 1; done")
                m.execute("chmod o+rwx /run/libvirt/libvirt-sock")
            b.wait_in_text("body", "Virtual Machines")
            self.waitVmRow("subVmTest1")
            self.waitVmRow("subVmTest2")
            b.wait_in_text("#vm-subVmTest1-state", "shut off")
            b.wait_in_text("#vm-subVmTest2-state", "running")
            self.toggleVmRow("subVmTest2")

        # stop second VM, event handling should still work
        b.click("#vm-subVmTest2-action-kebab button")
        b.click("#vm-subVmTest2-forceOff")
        b.wait_in_text("#vm-subVmTest2-state", "shut off")

        # test VM error messages
        b.click("#vm-subVmTest2-run")
        b.click("#vm-subVmTest2-run") # make use of slow processing - the button is still present; will cause error
        # triangle by status
        b.wait_present("tbody tr[data-row-id=vm-subVmTest2-system] td span span.pficon-warning-triangle-o.machines-status-alert")
        # inline notification with error
        b.wait_in_text("div.pf-c-alert.pf-m-danger .pf-c-alert__title", "VM subVmTest2 failed to start")

        message = "domain is already running"

        b.wait_in_text("button.alert-link.more-button", "show more") # more/less button
        b.click("button.alert-link.more-button")
        b.wait_in_text(".pf-c-alert__description", message)
        b.wait_in_text("button.alert-link.more-button", "show less")

        b.click("div.pf-c-alert.pf-m-danger button.pf-c-button") # close button
        # inline notification is gone
        b.wait_not_present("div.pf-c-alert.pf-m-danger")
        # triangle by status is gone
        b.wait_not_present("tbody tr[data-row-id=vm-subVmTest2-system] td span span.pficon-warning-triangle-o.machines-status-alert")

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

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

            row_classes = b.attr("#virtual-machines-listing .ct-table > tbody:nth-of-type({0})".format(index), "class")
            expanded = row_classes and 'pf-m-expanded' in row_classes
            if not expanded:
                self.toggleVmRow(name)

            b.click("#vm-{0}-run".format(name))

        # Try to run subVmTest1 - it will fail because of inactive default network
        tryRunDomain(1, 'subVmTest1')
        b.wait_in_text(".toast-notifications-list-pf div:nth-child(1) h4", "VM subVmTest1 failed to start")

        # Try to run subVmTest2
        tryRunDomain(2, 'subVmTest2')
        b.wait_in_text(".toast-notifications-list-pf div:nth-child(2) h4", "VM subVmTest2 failed to start")

        # Delete the first notification and check notifications list again
        b.focus(".toast-notifications-list-pf")
        b.click(".toast-notifications-list-pf div:nth-child(1) button.pf-c-button")
        b.wait_not_present(".toast-notifications-list-pf div:nth-child(2) h4")
        b.wait_in_text(".toast-notifications-list-pf div:nth-child(1) h4", "VM subVmTest2 failed to start")

        # Add one more notification
        tryRunDomain(3, 'subVmTest3')
        b.wait_in_text(".toast-notifications-list-pf div:nth-child(1) h4", "VM subVmTest2 failed to start")
        b.wait_in_text(".toast-notifications-list-pf div:nth-child(2) h4", "VM subVmTest3 failed to start")

        # Delete the last notification
        b.focus(".toast-notifications-list-pf")
        b.click(".toast-notifications-list-pf div:nth-child(2) button.pf-c-button")
        b.wait_not_present(".toast-notifications-list-pf div:nth-child(2) h4")
        b.wait_in_text(".toast-notifications-list-pf div:nth-child(1) h4", "VM subVmTest2 failed to start")

    def wait_for_disk_stats(self, name, target):
        b = self.browser
        try:
            with b.wait_timeout(10):
                b.wait_present("#vm-{0}-disks-{1}-used".format(name, target)) # wait for disk statistics to show up
        except Error as ex:
            if not ex.msg.startswith('timeout'):
                raise
            # stats did not show up, check if user message showed up
            print("Libvirt version does not support disk statistics")
            b.wait_present("#vm-{0}-disks-notification".format(name))

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

        libvirtServiceName = "libvirtd.service"

        def checkLibvirtEnabled():
            try:
                m.execute("systemctl -q is-enabled {0}".format(libvirtServiceName))
                return True
            except subprocess.CalledProcessError:  # return code != 0
                return False

        self.startVm("subVmTest1")
        self.login_and_go("/machines")

        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        # newer libvirtd versions use socket activation
        # we should test that separately, but here we test only using the service unit
        if m.image not in ["debian-stable", "ubuntu-stable", "rhel-8-3", "rhel-8-3-distropkg", "centos-8-stream"]:
            m.execute("systemctl stop libvirtd-ro.socket libvirtd.socket libvirtd-admin.socket")
            self.addCleanup(m.execute, "systemctl start libvirtd-ro.socket libvirtd.socket libvirtd-admin.socket")

        def hack_libvirtd_crash():
            # work around libvirtd crashing when stopped too quickly; https://bugzilla.redhat.com/show_bug.cgi?id=1828207
            m.execute("virsh domifaddr 1")

        m.execute("systemctl disable {0}".format(libvirtServiceName))
        hack_libvirtd_crash()
        m.execute("systemctl stop {0}".format(libvirtServiceName))

        b.wait_in_text(".pf-c-empty-state", "Virtualization Service (libvirt) is Not Active")
        b.wait_present("#enable-libvirt:checked")
        b.click(".pf-c-empty-state button.pf-m-primary")  # Start libvirt
        b.wait(lambda: checkLibvirtEnabled())
        # HACK: https://launchpad.net/bugs/1802005
        if m.image == "ubuntu-stable":
            m.execute("until test -e /run/libvirt/libvirt-sock; do sleep 1; done")
            m.execute("chmod o+rwx /run/libvirt/libvirt-sock")
        b.wait_in_text("body", "Virtual Machines")
        with b.wait_timeout(15):
            b.wait_not_present(".pf-c-empty-state")
            self.waitVmRow("subVmTest1")

        hack_libvirtd_crash()
        m.execute("systemctl stop {0}".format(libvirtServiceName))
        b.wait_in_text(".pf-c-empty-state", "Virtualization Service (libvirt) is Not Active")
        b.wait_present("#enable-libvirt:checked")
        b.click("#enable-libvirt") # uncheck it ; ; TODO: fix this, do not assume initial state of the checkbox
        b.click(".pf-c-empty-state button.pf-m-primary")  # Start libvirt
        b.wait(lambda: not checkLibvirtEnabled())
        # HACK: https://launchpad.net/bugs/1802005
        if m.image == "ubuntu-stable":
            m.execute("until test -e /run/libvirt/libvirt-sock; do sleep 1; done")
            m.execute("chmod o+rwx /run/libvirt/libvirt-sock")
        b.wait_in_text("body", "Virtual Machines")
        with b.wait_timeout(15):
            b.wait_not_present(".pf-c-empty-state")
            self.waitVmRow("subVmTest1")

        m.execute("systemctl enable {0}".format(libvirtServiceName))
        hack_libvirtd_crash()
        m.execute("systemctl stop {0}".format(libvirtServiceName))

        b.wait_in_text(".pf-c-empty-state", "Virtualization Service (libvirt) is Not Active")
        b.wait_present("#enable-libvirt:checked")

        b.click(".pf-c-empty-state button.pf-m-link")  # Troubleshoot
        b.leave_page()
        url_location = "/system/services#/{0}".format(libvirtServiceName)
        b.wait(lambda: url_location in b.eval_js("window.location.href"))

        # Make sure that unprivileged users can see the VM list when libvirtd is not running
        m.execute("systemctl stop libvirtd.service")
        m.execute("useradd nonadmin; echo nonadmin:foobar | chpasswd")
        self.login_and_go("/machines", user="nonadmin", superuser=False)
        b.wait_in_text("body", "Virtual Machines")
        b.wait_in_text("#virtual-machines-listing thead tr td", "No VM is running")
        b.logout()

        self.allow_authorize_journal_messages()

        # Might happen when user is logged out
        self.allow_journal_messages("connection unexpectedly closed by peer")

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

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

        # Test basic disk properties
        b.wait_in_text("#vm-subVmTest1-disks-vda-bus", "virtio")

        b.wait_in_text("#vm-subVmTest1-disks-vda-device", "disk")

        b.wait_in_text("#vm-subVmTest1-disks-vda-source-file", "/var/lib/libvirt/images/subVmTest1-2.img")

        # Test domstats
        self.wait_for_disk_stats("subVmTest1", "vda")
        if b.is_present("#vm-subVmTest1-disks-vda-used"):
            b.wait_in_text("#vm-subVmTest1-disks-vda-used", "0.0")

        # Test add disk by external action
        m.execute("qemu-img create -f raw /var/lib/libvirt/images/image3.img 128M")
        # attach to the virtio bus instead of ide
        m.execute("virsh attach-disk subVmTest1 /var/lib/libvirt/images/image3.img vdc")

        b.wait_present("#vm-subVmTest1-disks-vda-used")

        b.wait_in_text("#vm-subVmTest1-disks-vda-bus", "virtio")

        b.wait_in_text("#vm-subVmTest1-disks-vdc-bus", "virtio")
        b.wait_in_text("#vm-subVmTest1-disks-vdc-device", "disk")
        b.wait_in_text("#vm-subVmTest1-disks-vdc-source-file", "/var/lib/libvirt/images/image3.img")

        self.wait_for_disk_stats("subVmTest1", "vdc")
        if b.is_present("#vm-subVmTest1-disks-vdc-used"):
            b.wait_in_text("#vm-subVmTest1-disks-vdc-used", "0.00")
            b.wait_in_text("#vm-subVmTest1-disks-vdc-capacity", "0.13") # 128 MB

        # Test remove disk - by external action
        m.execute("virsh detach-disk subVmTest1 vdc")
        print("Restarting vm-subVmTest1, might take a while")
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceReboot")

        b.wait_present("#vm-subVmTest1-disks-vda-device")
        b.wait_not_present("#vm-subVmTest1-disks-vdc-device")

    # Test Add Disk via dialog
    @timeout(900)
    def testAddDisk(self):
        b = self.browser
        m = self.machine
        toggleVmRow = self.toggleVmRow

        dev = self.add_ram_disk()

        class VMAddDiskDialog(object):
            def __init__(
                self, test_obj, pool_name=None, volume_name=None,
                vm_name='subVmTest1',
                volume_size=1, volume_size_unit='GiB',
                use_existing_volume=False,
                expected_target='vda', permanent=False, cache_mode=None,
                bus_type='virtio', verify=True, pool_type=None,
                volume_format=None,
                persistent_vm=True,
            ):
                print(pool_name, volume_name)
                self.test_obj = test_obj
                self.vm_name = vm_name
                self.pool_name = pool_name
                self.use_existing_volume = use_existing_volume
                self.volume_name = volume_name
                self.volume_size = volume_size
                self.volume_size_unit = volume_size_unit
                self.expected_target = expected_target
                self.permanent = permanent
                self.cache_mode = cache_mode
                self.bus_type = bus_type
                self.verify = verify
                self.pool_type = pool_type
                self.volume_format = volume_format
                self.persistent_vm = persistent_vm

            def execute(self):
                self.open()
                self.fill()
                if self.verify:
                    self.add_disk()
                    self.verify_disk_added()

            def open(self):
                b.click("#vm-{0}-disks-adddisk".format(self.vm_name)) # button
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Add Disk")

                b.wait_present("label:contains(Create New)")
                if self.use_existing_volume:
                    b.click("label:contains(Use Existing)")

                return self

            def fill(self):
                if not self.use_existing_volume:
                    # Choose storage pool
                    if not self.pool_type or self.pool_type not in ['iscsi', 'iscsi-direct']:
                        b.select_from_dropdown("#vm-{0}-disks-adddisk-new-select-pool".format(self.vm_name), self.pool_name)
                    else:
                        b.click("#vm-{0}-disks-adddisk-new-select-pool".format(self.vm_name))
                        b.wait_present(".modal-dialog option[data-value={0}]:disabled".format(self.pool_name))
                        return self

                    # Insert name for the new volume
                    b.set_input_text("#vm-{0}-disks-adddisk-new-name".format(self.vm_name), self.volume_name)
                    # Insert size for the new volume
                    b.set_input_text("#vm-{0}-disks-adddisk-new-size".format(self.vm_name), str(self.volume_size))
                    b.select_from_dropdown("#vm-{0}-disks-adddisk-new-unit".format(self.vm_name), self.volume_size_unit)

                    if self.volume_format:
                        b.select_from_dropdown("#vm-{0}-disks-adddisk-new-format".format(self.vm_name), self.volume_format)

                    # Configure persistency - by default the check box in unchecked for running VMs
                    if self.permanent:
                        b.click("#vm-{0}-disks-adddisk-permanent".format(self.vm_name))
                else:
                    # Choose storage pool
                    b.select_from_dropdown("#vm-{0}-disks-adddisk-existing-select-pool".format(self.vm_name), self.pool_name)
                    # Select from the available volumes
                    b.select_from_dropdown("#vm-{0}-disks-adddisk-existing-select-volume".format(self.vm_name), self.volume_name)

                    # Configure persistency - by default the check box in unchecked for running VMs
                    if self.permanent:
                        b.click("#vm-{0}-disks-adddisk-permanent".format(self.vm_name))

                # Check non-persistent VM cannot have permanent disk attached
                if not self.persistent_vm:
                    b.wait_not_present("#vm-{0}-disks-adddisk-new-permanent".format(self.vm_name))

                # Configure performance options
                if self.cache_mode:
                    b.click("div.modal-dialog button:contains(Show Additional Options)")
                    b.select_from_dropdown("div.modal-dialog #cache-mode", self.cache_mode)
                    b.click("div.modal-dialog button:contains(Hide Additional Options)")
                else:
                    b.wait_not_present("#div.modal-dialog #cache-mode")

                # Configure bus type
                if self.bus_type != "virtio":
                    b.click("div.modal-dialog button:contains(Show Additional Options)")
                    b.select_from_dropdown("div.modal-dialog #bus-type", self.bus_type)
                    b.click("div.modal-dialog button:contains(Hide Additional Options)")
                else:
                    b.wait_not_present("#div.modal-dialog #cache-mode")

                return self

            def add_disk(self):
                b.click(".modal-footer button:contains(Add)")
                b.wait_not_present("vm-{0}-disks-adddisk-dialog-modal-window".format(self.vm_name))

                return self

            def verify_disk_added(self):
                b.wait_in_text("#vm-{0}-disks-{1}-bus".format(self.vm_name, self.expected_target), self.bus_type)
                b.wait_in_text("#vm-{0}-disks-{1}-device".format(self.vm_name, self.expected_target), "disk")

                # Check volume was added to pool's volume list
                if not self.use_existing_volume:
                    b.click(".cards-pf .card-pf-title span:contains(Storage Pool)")

                    self.test_obj.waitPoolRow(self.pool_name)
                    self.test_obj.togglePoolRow(self.pool_name)

                    b.wait_present("#pool-{0}-system-storage-volumes".format(self.pool_name))
                    b.click("#pool-{0}-system-storage-volumes".format(self.pool_name)) # open the "Storage Volumes" subtab
                    b.wait_present("#pool-{0}-system-volume-{1}-name".format(self.pool_name, self.volume_name))

                    b.click(".machines-listing-breadcrumb li a:contains(Virtual Machines)")
                    toggleVmRow("subVmTest1")
                    b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

                # Detect volume format
                detect_format_cmd = "virsh vol-dumpxml {0} {1} | xmllint --xpath '{2}' -"

                b.wait_in_text('#vm-{0}-disks-{1}-source-volume'.format(self.vm_name, self.expected_target), self.volume_name)
                if self.cache_mode:
                    b.wait_in_text("#vm-{0}-disks-{1}-cache".format(self.vm_name, self.expected_target), self.cache_mode)
                # Guess by the name of the pool it's format to avoid passing more parameters
                if self.pool_type == 'iscsi':
                    expected_format = 'unknown'
                else:
                    expected_format = 'qcow2'

                if self.pool_type == 'disk':
                    expected_format = 'none'

                # Unknown pool format isn't present in xml anymore
                if expected_format == "unknown" and m.execute("virsh --version") >= "5.6.0":
                    m.execute(detect_format_cmd.format(self.volume_name, self.pool_name, "/volume/target") + " | grep -qv format")
                else:
                    self.test_obj.assertEqual(
                        m.execute(detect_format_cmd.format(self.volume_name, self.pool_name, "/volume/target/format")).rstrip(),
                        '<format type="{0}"/>'.format(self.volume_format or expected_format)
                    )
                return self

        used_targets = ['vda']

        def get_next_free_target():
            i = 0
            while ("vd" + chr(97 + i) in used_targets):
                i += 1

            used_targets.append("vd" + chr(97 + i))
            return "vd" + chr(97 + i)

        def release_target(target):
            used_targets.remove(target)

        # prepare libvirt storage pools
        v1 = os.path.join(self.vm_tmpdir, "vm_one")
        v2 = os.path.join(self.vm_tmpdir, "vm_two")
        default_tmp = os.path.join(self.vm_tmpdir, "default_tmp")
        m.execute("mkdir --mode 777 {0} {1} {2}".format(v1, v2, default_tmp))
        m.execute("virsh pool-define-as default_tmp --type dir --target {0} && virsh pool-start default_tmp".format(default_tmp))
        m.execute("virsh pool-define-as myPoolOne --type dir --target {0} && virsh pool-start myPoolOne".format(v1))
        m.execute("virsh pool-define-as myPoolTwo --type dir --target {0} && virsh pool-start myPoolTwo".format(v2))

        m.execute("virsh vol-create-as default_tmp defaultVol --capacity 1G --format raw")
        m.execute("virsh vol-create-as myPoolTwo mydiskofpooltwo_temporary --capacity 1G --format qcow2")
        m.execute("virsh vol-create-as myPoolTwo mydiskofpooltwo_permanent --capacity 1G --format qcow2")
        wait(lambda: "mydiskofpooltwo_permanent" in m.execute("virsh vol-list myPoolTwo"))

        # Prepare a local NFS pool
        self.restore_file("/etc/exports")
        nfs_pool = os.path.join(self.vm_tmpdir, "nfs_pool")
        mnt_exports = os.path.join(self.vm_tmpdir, "mnt_exports")
        m.execute("mkdir {0} {1} && echo '{1} 127.0.0.1/24(rw,sync,no_root_squash,no_subtree_check,fsid=0)' > /etc/exports".format(nfs_pool, mnt_exports))
        m.execute("systemctl restart nfs-server")
        m.execute("virsh pool-define-as nfs-pool --type netfs --target {0} --source-host 127.0.0.1 --source-path {1} && virsh pool-start nfs-pool".format(nfs_pool, mnt_exports))
        # And create a volume on it in order to test use existing volume dialog
        m.execute("virsh vol-create-as --pool nfs-pool --name nfs-volume-0 --capacity 1M --format qcow2")

        # Prepare an iscsi pool
        # Debian images' -cloud kernel don't have target-cli-mod kmod
        # Ubuntu 19.10 is affected by https://bugzilla.redhat.com/show_bug.cgi?id=1659195
        if "debian" not in m.image and m.image != "ubuntu-stable":
            # Preparations for testing ISCSI pools

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

            m.execute("virsh pool-define-as iscsi-pool --type iscsi --target /dev/disk/by-path --source-host 127.0.0.1 --source-dev {0} && virsh pool-start iscsi-pool".format(target_iqn))
            wait(lambda: "unit:0:0:0" in self.machine.execute("virsh pool-refresh iscsi-pool && virsh vol-list iscsi-pool"), delay=3)

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")
        b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

        VMAddDiskDialog(
            self,
            pool_name='myPoolOne',
            volume_name='mydiskofpoolone_temporary',
            use_existing_volume=False,
            volume_size=2048,
            volume_size_unit='MiB',
            permanent=False,
            expected_target=get_next_free_target(),
        ).execute()

        VMAddDiskDialog(
            self,
            pool_name='myPoolOne',
            volume_name='mydiskofpoolone_permanent',
            use_existing_volume=False,
            volume_size=2,
            permanent=True,
            cache_mode='writeback',
            expected_target=get_next_free_target(),
        ).execute()

        VMAddDiskDialog(
            self,
            pool_name='myPoolOne',
            use_existing_volume=True,
        ).open()
        b.select_from_dropdown("#vm-subVmTest1-disks-adddisk-existing-select-pool", "myPoolOne")
        # since both disks are already attached
        b.wait_attr("#vm-subVmTest1-disks-adddisk-existing-select-volume", "disabled", "")
        b.wait_in_text("#vm-subVmTest1-disks-adddisk-existing-select-volume", "The pool is empty")
        b.click("#vm-subVmTest1-disks-adddisk-dialog-cancel")
        b.wait_not_present("#vm-subVmTest1-test-disks-adddisk-dialog-modal-window")

        VMAddDiskDialog(
            self,
            pool_name='myPoolTwo',
            volume_name='mydiskofpooltwo_permanent',
            volume_size=2,
            permanent=True,
            use_existing_volume=True,
            expected_target=get_next_free_target(),
        ).execute()

        # check the autoselected options
        # default_tmp pool should be autoselected since it's the first in alphabetical order
        # defaultVol volume should be autoselected since it's the only volume in default_tmp pool
        VMAddDiskDialog(
            self,
            pool_name='default_tmp',
            volume_name='defaultVol',
            use_existing_volume=True,
            expected_target=get_next_free_target(),
            volume_format='raw',
        ).open().add_disk().verify_disk_added()

        VMAddDiskDialog(
            self,
            pool_name='nfs-pool',
            volume_name='nfs-volume-0',
            use_existing_volume=True,
            volume_size=1,
            volume_size_unit='MiB',
            expected_target=get_next_free_target(),
        ).execute()

        VMAddDiskDialog(
            self,
            pool_name='nfs-pool',
            volume_name='nfs-volume-1',
            volume_size=1,
            volume_size_unit='MiB',
            expected_target=get_next_free_target(),
        ).execute()

        if "debian" not in m.image and "ubuntu" not in m.image:
            # ISCSI driver does not support virStorageVolCreate API
            VMAddDiskDialog(
                self,
                pool_name='iscsi-pool',
                pool_type='iscsi',
                verify=False
            ).execute()

            VMAddDiskDialog(
                self,
                pool_name='iscsi-pool',
                pool_type='iscsi',
                volume_name='unit:0:0:0',
                expected_target=get_next_free_target(),
                use_existing_volume='True',
            ).execute()

        VMAddDiskDialog(
            self,
            pool_name='myPoolOne',
            volume_name='scsi_bus_disk',
            use_existing_volume=False,
            bus_type='scsi',
            expected_target='sda',
        ).execute()

        VMAddDiskDialog(
            self,
            pool_name='myPoolOne',
            volume_name='usb_bus_disk',
            use_existing_volume=False,
            bus_type='usb',
            expected_target='sdb',
        ).execute()

        # shut off
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # check if the just added non-permanent disks are gone
        b.wait_not_present("#vm-subVmTest1-disks-vdb-device")
        b.wait_not_present("#vm-subVmTest1-disks-vde-device")
        release_target("vdb")
        release_target("vde")
        b.wait_present("#vm-subVmTest1-disks-vdc-device")
        b.wait_present("#vm-subVmTest1-disks-vdd-device")

        # testing sata disk after VM shutoff because sata disk cannot be hotplugged
        VMAddDiskDialog(
            self,
            pool_name='myPoolOne',
            volume_name='sata_bus_disk',
            use_existing_volume=False,
            bus_type='sata',
            expected_target='sda',
        ).execute()

        # Apparmor on debian and ubuntu may prevent access to /dev/sdb1 when starting VM,
        # https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/1677398
        if "debian" not in m.image and "ubuntu" not in m.image:
            # Run VM
            b.click("#vm-subVmTest1-run")
            b.wait_in_text("#vm-subVmTest1-state", "running")
            # Test disk attachment to non-persistent VM
            m.execute("virsh undefine subVmTest1")
            VMAddDiskDialog(
                self,
                pool_name='myPoolOne',
                volume_name='non-peristent-vm-disk',
                permanent=False,
                persistent_vm=False,
                expected_target=get_next_free_target(),
            ).execute()

        # Undefine all Storage Pools and  confirm that the Add Disk dialog is disabled
        active_pools = filter(lambda pool: pool != '', m.execute("virsh pool-list --name").split('\n'))
        print(active_pools)
        for pool in active_pools:
            m.execute("virsh pool-destroy {0}".format(pool))
        b.wait_in_text("#card-pf-storage-pools .card-pf-aggregate-status-notification:nth-of-type(1)", "0")
        inactive_pools = filter(lambda pool: pool != '', m.execute("virsh pool-list --inactive --name").split('\n'))
        for pool in inactive_pools:
            m.execute("virsh pool-undefine {0}".format(pool))
        b.wait_in_text("#card-pf-storage-pools .card-pf-aggregate-status-notification:nth-of-type(2)", "0")
        b.click("#vm-subVmTest1-disks-adddisk") # radio button label in modal dialog
        b.wait_present("#vm-subVmTest1-disks-adddisk-dialog-add:disabled")
        b.click("label:contains(Use Existing)")
        b.wait_present("#vm-subVmTest1-disks-adddisk-dialog-add:disabled")
        b.click(".modal-footer button:contains(Cancel)")

        # Make sure that trying to inspect the Disks tab will just show the fields that are available when a pool is inactive
        b.reload()
        b.enter_page('/machines')
        b.wait_in_text("body", "Virtual Machines")
        self.toggleVmRow("subVmTest1")
        b.click("#vm-subVmTest1-disks") # open the "Disks" subtab
        # Check that usage information can't be fetched since the pool is inactive
        b.wait_not_present("#vm-subVmTest1-disks-vdd-used")

        cmds = [
            "virsh pool-define-as pool-disk disk - - %s - /tmp/poolDiskImages" % dev,
            "virsh pool-build pool-disk --overwrite",
            "virsh pool-start pool-disk",
        ]
        self.machine.execute(" && ".join(cmds))
        partition = os.path.basename(dev) + "1"
        VMAddDiskDialog(
            self,
            pool_name='pool-disk',
            pool_type='disk',
            volume_name=partition,
            volume_size=10,
            volume_size_unit='MiB',
            expected_target=get_next_free_target(),
        ).execute()

        # avoid error noise about resources getting cleaned up
        b.logout()

        # AppArmor doesn't like the non-standard path for our storage pools
        if m.image in ["debian-testing", "ubuntu-stable"]:
            self.allow_journal_messages('.* type=1400 .* apparmor="DENIED" operation="open" profile="libvirt.* name="%s.*' % self.vm_tmpdir)

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

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Wait for the dynamic IP address to be assigned before logging in
        # If the IP will change or get assigned after fetching the domain data the user will not see any
        # changes until they refresh the page, since there is not any signal associated with this change
        wait(lambda: "1" in self.machine.execute("virsh domifaddr subVmTest1  | grep 192.168.122. | wc -l"), delay=3)
        b.click("#vm-subVmTest1-networks") # open the "Networks" subtab

        b.wait_in_text("#vm-subVmTest1-network-1-type", "network")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "default")

        b.wait_in_text("#vm-subVmTest1-network-1-ipaddress", "192.168.122.")

        b.wait_in_text("#vm-subVmTest1-network-1-state", "up")

        # Test add network
        m.execute("virsh attach-interface --domain subVmTest1 --type network --source default --model virtio --mac 52:54:00:4b:73:5f --config --live")

        b.wait_in_text("#vm-subVmTest1-network-2-type", "network")
        b.wait_in_text("#vm-subVmTest1-network-2-source", "default")
        b.wait_in_text("#vm-subVmTest1-network-2-model", "virtio")
        b.wait_in_text("#vm-subVmTest1-network-2-mac", "52:54:00:4b:73:5f")

        b.wait_in_text("#vm-subVmTest1-network-2-state", "up")

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

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")
        self.toggleVmRow("subVmTest1")

        b.wait_in_text("#vm-subVmTest1-state", "running")

        b.click("#vm-subVmTest1-vcpus-count") # open VCPU modal detail window

        b.wait_present(".modal-body")

        # Test basic vCPU properties
        b.wait_val("#machines-vcpu-count-field", "1")
        b.wait_val("#machines-vcpu-max-field", "1")

        # Set new values
        b.set_input_text("#machines-vcpu-max-field", "4")
        b.set_input_text("#machines-vcpu-count-field", "3")

        # Set new socket value
        b.wait_val("#socketsSelect", "4")
        b.set_val("#socketsSelect", "2")
        b.wait_val("#coresSelect", "1")
        b.wait_val("#threadsSelect", "2")

        # Save
        b.click("#machines-vcpu-modal-dialog-apply")
        b.wait_not_present(".modal-body")

        # Make sure warning next to vcpus appears
        b.wait_present("#vcpus-tooltip")

        # Shut off VM for applying changes after save
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # Make sure warning is gone after shut off
        b.wait_not_present("#vcpus-tooltip")

        # Check changes
        b.wait_in_text("#vm-subVmTest1-vcpus-count", "3")

        # Check after boot
        # Run VM
        b.click("#vm-subVmTest1-run")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Check VCPU count
        b.wait_in_text("#vm-subVmTest1-vcpus-count", "3")

        # Open dialog window
        b.click("#vm-subVmTest1-vcpus-count")
        b.wait_present(".modal-body")

        # Check basic values
        b.wait_val("#machines-vcpu-count-field", "3")
        b.wait_val("#machines-vcpu-max-field", "4")

        # Check sockets, cores and threads
        b.wait_val("#socketsSelect", "2")
        b.wait_val("#coresSelect", "1")
        b.wait_val("#threadsSelect", "2")

        b.click("#machines-vcpu-modal-dialog-cancel")
        b.wait_not_present("#machines-vcpu-modal-dialog")

        # Shut off VM
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # Open dialog
        b.click("#vm-subVmTest1-vcpus-count")

        b.wait_present(".modal-body")

        b.set_input_text("#machines-vcpu-count-field", "2")

        # Set new socket value
        b.set_val("#coresSelect", "2")
        b.wait_val("#socketsSelect", "2")
        b.wait_val("#threadsSelect", "1")

        # Save
        b.click("#machines-vcpu-modal-dialog-apply")
        b.wait_not_present("#machines-vcpu-modal-dialog")

        wait(lambda: m.execute(
            "virsh dumpxml subVmTest1 | tee /tmp/subVmTest1.xml | xmllint --xpath '/domain/cpu/topology[@sockets=\"2\"][@threads=\"1\"][@cores=\"2\"]' -"))

        # Run VM - this ensures that the internal state is updated before we move on.
        # We need this here because we can't wait for UI updates after we open the modal dialog.
        b.click("#vm-subVmTest1-run")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Wait for the VCPUs link to get new values before opening the dialog
        b.wait_visible("#vm-subVmTest1-vcpus-count")
        b.wait_in_text("#vm-subVmTest1-vcpus-count", "2")

        # Open dialog
        b.click("#vm-subVmTest1-vcpus-count")

        b.wait_present(".modal-body")

        # Set new socket value
        b.wait_val("#coresSelect", "2")
        b.wait_val("#socketsSelect", "2")
        b.wait_val("#threadsSelect", "1")

        b.wait_in_text("#vm-subVmTest1-vcpus-count", "2")

        # Check value of sockets, threads and cores from VM dumpxml
        m.execute(
            "virsh dumpxml subVmTest1 | xmllint --xpath '/domain/cpu/topology[@sockets=\"2\"][@threads=\"1\"][@cores=\"2\"]' -")

        # non-persistent VM doesn't have configurable vcpu
        m.execute("virsh undefine subVmTest1")
        b.wait_present("button#vm-subVmTest1-vcpus-count:disabled")

    def testExternalConsole(self):
        b = self.browser

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running") # running or paused

        b.click("#vm-subVmTest1-consoles") # open the "Console" subtab

        # since VNC is not defined for this VM, the view for "Desktop Viewer" is rendered by default
        b.wait_in_text("#vm-subVmTest1-consoles-manual-address", "127.0.0.1")
        b.wait_in_text("#vm-subVmTest1-consoles-manual-port-spice", "5900")

        b.click("#vm-subVmTest1-consoles-launch") # "Launch Remote Viewer" button
        b.wait_present("#dynamically-generated-file") # is .vv file generated for download?
        self.assertEqual(b.attr("#dynamically-generated-file", "href"),
                         u"data:application/x-virt-viewer,%5Bvirt-viewer%5D%0Atype%3Dspice%0Ahost%3D127.0.0.1%0Aport%3D5900%0Adelete-this-file%3D1%0Afullscreen%3D0%0A")

        # Check "More Information"
        b.click('.machines-desktop-viewer-block .link-button')
        b.wait_in_text('.machines-desktop-more-info-container',
                       'Clicking "Launch Remote Viewer" will download')

    def testInlineConsole(self, urlroot=""):
        b = self.browser

        args = self.startVm("subVmTest1", graphics='vnc')

        if urlroot != "":
            self.machine.execute('mkdir -p /etc/cockpit/ && echo "[WebService]\nUrlRoot=%s" > /etc/cockpit/cockpit.conf' % urlroot)

        self.login_and_go("/machines", urlroot=urlroot)
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running") # running or paused

        b.click("#vm-subVmTest1-consoles") # open the "Console" subtab

        # since VNC is defined for this VM, the view for "In-Browser Viewer" is rendered by default
        b.wait_present(".toolbar-pf-results canvas")

        # make sure the log file is full - then empty it and reboot the VM - the log file should fill up again
        wait(lambda: "login as 'cirros' user." in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)
        self.machine.execute("echo '' > {0}".format(args["logfile"]))
        b.click("#{0}-system-vnc-sendkey button".format("subVmTest1"))
        b.click("#ctrl-alt-Delete")
        wait(lambda: "Requesting system reboot" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

    def testInlineConsoleWithUrlRoot(self, urlroot=""):
        self.testInlineConsole(urlroot="/webcon")

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

        name = "subVmTest1"
        img2 = "/var/lib/libvirt/images/{0}-2.img".format(name)

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

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow(name)

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

        self.toggleVmRow("subVmTest1")

        def addDisk(volName, poolName):
            # Virsh does not offer some option to create disks of type volume
            # We have to do this from cockpit UI
            b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

            b.click("#vm-subVmTest1-disks-adddisk") # button
            b.wait_present("#vm-subVmTest1-disks-adddisk-dialog-modal-window")
            b.wait_present("label:contains(Create New)") # radio button label in the modal dialog

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

            b.click("#vm-subVmTest1-disks-adddisk-dialog-add")
            b.wait_not_present("#vm-subVmTest1-disks-adddisk-dialog-modal-window")

            b.wait_present("#vm-subVmTest1-disks-vdb-source-volume")
            b.wait_present("#vm-subVmTest1-disks-vdb-source-pool")

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

        addDisk(secondDiskVolName, poolName)

        b.click("#vm-{0}-action-kebab button".format(name))
        b.click("#vm-{0}-delete".format(name))

        b.wait_present("#vm-{0}-delete-modal-dialog div:contains(The VM is running)".format(name))
        b.wait_present("#vm-{1}-delete-modal-dialog ul li:first-child #disk-source-file:contains({0})".format(img2, name))
        # virsh attach-disk does not create disks of type volume
        b.wait_present("#vm-{1}-delete-modal-dialog #disk-source-volume:contains({0})".format(secondDiskVolName, name))
        b.wait_present("#vm-{1}-delete-modal-dialog #disk-source-pool:contains({0})".format(poolName, name))
        b.click("#vm-{0}-delete-modal-dialog button:contains(Delete)".format(name))
        b.wait_not_present("#vm-{0}-delete-modal-dialog".format(name))

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

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

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

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

        self.toggleVmRow(name)

        # Make sure that the VM booted normally before attempting to suspend it
        wait(lambda: "Linux version" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

        self.machine.execute("virsh -c qemu:///system suspend {0}".format(name))
        b.wait_in_text("#vm-{0}-state".format(name), "paused")
        b.click("#vm-{0}-action-kebab button".format(name))
        b.click("#vm-{0}-delete".format(name))
        self.browser.reload()
        self.waitVmRow(name, 'system', False)

        # Try to delete a transient VM
        name = "transient-VM"
        args = self.startVm(name)
        m.execute("virsh undefine {0}".format(name))
        b.reload()
        self.browser.enter_page('/machines')
        self.toggleVmRow(name)
        b.click("#vm-{0}-action-kebab button".format(name))
        b.wait_present("#vm-{0}-delete a.pf-m-disabled".format(name))
        b.click("#vm-{0}-forceOff".format(name))
        self.waitVmRow(name, 'system', False)
        b.wait_not_present(".toast-notifications.list-pf div.pf-c-alert")

    def testSerialConsole(self):
        b = self.browser
        name = "vmWithSerialConsole"

        self.startVm(name, graphics='vnc', ptyconsole=True)

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow(name)

        self.toggleVmRow(name)
        b.wait_in_text("#vm-{0}-state".format(name), "running") # running or paused

        b.click("#vm-{0}-consoles".format(name)) # open the "Console" subtab

        b.set_val("#console-type-select", "serial-browser")
        b.wait_in_text("#{0}-terminal .xterm-accessibility-tree > div:nth-child(1)".format(name), "Connected to domain")

        b.click("#{0}-serialconsole-disconnect".format(name))
        b.wait_text("#{0}-terminal".format(name), "Disconnected from serial console. Click the Connect button.")

        b.click("#{0}-serialconsole-connect".format(name))
        b.wait_in_text("#{0}-terminal .xterm-accessibility-tree > div:nth-child(1)".format(name), "Connected to domain")

        # disconnecting the serial console closes the pty channel
        self.allow_journal_messages("connection unexpectedly closed by peer",
                                    ".*Connection reset by peer")
        self.allow_browser_errors("Disconnection timed out.")

    # This test contains basic form validation of the Create VM dialog
    # None of the sub-tests will actually call virt-install
    def testCreateBasicValidation(self):
        runner = TestMachines.CreateVmRunner(self)
        config = TestMachines.TestCreateConfig

        # Add an extra network interface that should appear in the PXE source dropdown
        iface = "eth42"
        self.add_veth(iface)

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        # test just the DIALOG CREATION and cancel
        print("    *\n    * validation errors and ui info/warn messages expected:\n    * ")
        runner.cancelDialogTest(TestMachines.VmDialog(self, sourceType='file',
                                                      location=config.NOVELL_MOCKUP_ISO_PATH,
                                                      memory_size=128, memory_size_unit='MiB',
                                                      storage_size=12500, storage_size_unit='GiB',
                                                      start_vm=True))

        runner.cancelDialogTest(TestMachines.VmDialog(self, sourceType='url',
                                                      location=config.VALID_URL,
                                                      memory_size=256, memory_size_unit='MiB',
                                                      os_name=config.FEDORA_28,
                                                      start_vm=False))

        # check if older os are filtered
        runner.checkFilteredOsTest(TestMachines.VmDialog(self, os_name=config.REDHAT_RHEL_4_7_FILTERED_OS))

        runner.checkFilteredOsTest(TestMachines.VmDialog(self, os_name=config.MANDRIVA_2011_FILTERED_OS))

        runner.checkFilteredOsTest(TestMachines.VmDialog(self, os_name=config.MAGEIA_3_FILTERED_OS))

        # try to CREATE WITH DIALOG ERROR
        # name
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, "", storage_size=1), {"Name": "Name must not be empty"})

        # location
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, sourceType='url',
                                                            location="invalid/url",
                                                            os_name=config.FEDORA_28), {"Source": "Source should start with"})

        # memory
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, memory_size=0, os_name=None), {"Memory": "Memory must not be 0"})

        # storage
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, storage_size=0), {"Size": "Storage size must not be 0"})

        # start vm
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, storage_size=1,
                                                            os_name=config.FEDORA_28, start_vm=True),
                                      {"Source": "Installation Source must not be empty"})

        # disallow empty OS
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, sourceType='url', location=config.VALID_URL,
                                                                   storage_size=100, storage_size_unit='MiB',
                                                                   start_vm=False, os_name=None),
                                      {"Operating System": "You need to select the most closely matching Operating System"})

        # When switching from PXE mode to anything else make sure that the source input is empty
        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, storage_size=1,
                                                                   sourceType='pxe',
                                                                   location="Host Device {0}: macvtap".format(iface),
                                                                   sourceTypeSecondChoice='url',
                                                                   start_vm=False),
                                      {"Source": "Installation Source must not be empty"})

    def testCreateDownloadAnOS(self):
        runner = TestMachines.CreateVmRunner(self)
        config = TestMachines.TestCreateConfig

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        # try to CREATE few machines
        if self.machine.image in ['debian-stable', 'debian-testing', 'ubuntu-stable', 'centos-8-stream']:
            self.browser.wait_not_present('select option[data-value="Download an OS"]')
        else:
            runner.createDownloadAnOSTest(TestMachines.VmDialog(self, sourceType='downloadOS',
                                                                expected_memory_size=256,
                                                                expected_storage_size=256,
                                                                os_name=config.FEDORA_28,
                                                                os_short_id=config.FEDORA_28_SHORTID,
                                                                start_vm=True))

            runner.createDownloadAnOSTest(TestMachines.VmDialog(self, sourceType='downloadOS',
                                                                is_unattended=True, profile="Workstation",
                                                                user_password="catsaremybestfr13nds", root_password="dogsaremybestfr13nds",
                                                                storage_size=246, storage_size_unit='MiB',
                                                                os_name=config.FEDORA_28,
                                                                os_short_id=config.FEDORA_28_SHORTID))

            # Don't create user account
            runner.createDownloadAnOSTest(TestMachines.VmDialog(self, sourceType='downloadOS',
                                                                is_unattended=True, profile="Server",
                                                                root_password="catsaremybestfr13nds",
                                                                storage_size=256, storage_size_unit='MiB',
                                                                os_name=config.FEDORA_28,
                                                                os_short_id=config.FEDORA_28_SHORTID))

            # Don't create root account
            runner.createDownloadAnOSTest(TestMachines.VmDialog(self, sourceType='downloadOS',
                                                                is_unattended=True, profile="Workstation",
                                                                user_password="catsaremybestfr13nds",
                                                                storage_size=256, storage_size_unit='MiB',
                                                                os_name=config.FEDORA_28,
                                                                os_short_id=config.FEDORA_28_SHORTID))


            # name already used from a VM that is currently being created
            # https://bugzilla.redhat.com/show_bug.cgi?id=1780451
            # downloadOS option exists only in virt-install >= 2.2.1 which is the reason we have the condition for the OSes list below
            if self.machine.image in ['debian-stable', 'debian-testing', 'ubuntu-stable', 'centos-8-stream']:
                self.browser.wait_not_present('select option[data-value="Download an OS"]')
            else:
                runner.createDownloadAnOSTest(TestMachines.VmDialog(self, name='existing-name', sourceType='downloadOS',
                                                                    expected_memory_size=256,
                                                                    expected_storage_size=256,
                                                                    os_name=config.FEDORA_28,
                                                                    os_short_id=config.FEDORA_28_SHORTID,
                                                                    start_vm=True, delete=False))

                runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, "existing-name", storage_size=1,
                                                                           check_script_finished=False, env_is_empty=False), {"Name": "already exists"})

    def testCreatePXE(self):
        runner = TestMachines.CreateVmRunner(self)

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        # test PXE Source
        # check that the pxe booting is not available on session connection
        runner.checkPXENotAvailableSessionTest(TestMachines.VmDialog(self, name='pxe-guest',
                                                                     sourceType='pxe',
                                                                     storage_pool="No Storage",
                                                                     connection="session"))

        self.machine.execute("virsh net-destroy default && virsh net-undefine default")

        # Set up the PXE server configuration files
        cmds = [
            "mkdir -p /var/lib/libvirt/pxe-config",
            "echo \"{0}\" > /var/lib/libvirt/pxe-config/pxe.cfg".format(PXE_SERVER_CFG),
            "chmod 666 /var/lib/libvirt/pxe-config/pxe.cfg"
        ]
        self.machine.execute(" && ".join(cmds))

        # Define and start a NAT network with tftp server configuration
        cmds = [
            "echo \"{0}\" > /tmp/pxe-nat.xml".format(NETWORK_XML_PXE),
            "virsh net-define /tmp/pxe-nat.xml",
            "virsh net-start pxe-nat"
        ]
        self.machine.execute(" && ".join(cmds))

        # Add an extra network interface that should appear in the PXE source dropdown
        iface = "eth42"
        self.add_veth(iface)

        # We don't handle events for networks yet, so reload the page to refresh the state
        self.browser.reload()
        self.browser.enter_page('/machines')
        self.browser.wait_in_text("body", "Virtual Machines")

        # First create the PXE VM but do not start it. We 'll need to tweak a bit the XML
        # to have serial console at bios and also redirect serial console to a file
        runner.createTest(TestMachines.VmDialog(self, name='pxe-guest', sourceType='pxe',
                                                location="Virtual Network pxe-nat: NAT",
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                start_vm=True, delete=False))

        # We don't want to use start_vm == False because if we get a seperate install phase
        # virt-install will overwrite our changes.
        self.machine.execute("virsh destroy pxe-guest")

        # Remove all serial ports and consoles first and tehn add a console of type file
        # virt-xml tool does not allow to remove both serial and console devices at once
        # https://bugzilla.redhat.com/show_bug.cgi?id=1685541
        # So use python xml parsing to change the domain XML.
        domainXML = self.machine.execute("virsh dumpxml pxe-guest")
        root = ET.fromstring(domainXML)

        # Find the parent element of each "console" element, using XPATH
        for p in root.findall('.//console/..'):
            # Find each console element
            for element in p.findall('console'):
                # Remove the console element from its parent element
                p.remove(element)

        # Find the parent element of each "serial" element, using XPATH
        for p in root.findall('.//serial/..'):
            # Find each serial element
            for element in p.findall('serial'):
                # Remove the serial element from its parent element
                p.remove(element)

        # Set useserial attribute for bios os element
        bios = ET.SubElement(root.find('os'), 'bios')
        bios.set('useserial', 'yes')

        # Add a serial console of type file
        console = ET.fromstring(self.machine.execute("virt-xml --build --console file,path=/tmp/serial.txt,target_type=serial"))
        devices = root.find('devices')
        devices.append(console)

        # Redefine the domain with the new XML
        xmlstr = ET.tostring(root, encoding='unicode', method='xml')

        self.machine.execute("echo \'{0}\' > /tmp/domain.xml && virsh define --file /tmp/domain.xml".format(xmlstr))

        self.machine.execute("virsh start pxe-guest")

        # The file is full of ANSI control characters in between every letter, filter them out
        wait(lambda: self.machine.execute(r"sed 's,\x1B\[[0-9;]*[a-zA-Z],,g' /tmp/serial.txt | grep 'Rebooting in 60'"), delay=3)

        self.machine.execute("virsh destroy pxe-guest && virsh undefine pxe-guest")

        # Check that host network devices are appearing in the options for PXE boot sources
        runner.createTest(TestMachines.VmDialog(self, sourceType='pxe',
                                                location="Host Device {0}: macvtap".format(iface),
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                start_vm=False))

    def testCreateThenInstall(self):
        runner = TestMachines.CreateVmRunner(self)
        config = TestMachines.TestCreateConfig

        dev = self.add_ram_disk()
        cmds = [
            "virsh pool-define-as poolDisk disk - - {0} - {1}".format(dev, os.path.join(self.vm_tmpdir, "poolDiskImages")),
            "virsh pool-build poolDisk --overwrite",
            "virsh pool-start poolDisk",
            "virsh vol-create-as poolDisk sda1 1024"
        ]
        self.machine.execute(" && ".join(cmds))

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        # Test create VM with disk of type "block"
        # Check choosing existing volume as destination storage
        runner.createThenInstallTest(TestMachines.VmDialog(self, sourceType='file',
                                                           location=config.NOVELL_MOCKUP_ISO_PATH,
                                                           memory_size=256, memory_size_unit='MiB',
                                                           storage_pool="poolDisk",
                                                           storage_volume="sda1"))

        if "debian" not in self.machine.image and "ubuntu" not in self.machine.image:
            # Test create VM with disk of type "network"
            target_iqn = "iqn.2019-09.cockpit.lan"
            self.prepareStorageDeviceOnISCSI(target_iqn)

            cmds = [
                "virsh pool-define-as --name poolIscsi --type iscsi --source-host 127.0.0.1 --source-dev {0} --target /dev/disk/by-path/".format(target_iqn),
                "virsh pool-build poolIscsi",
                "virsh pool-start poolIscsi",
                "virsh pool-refresh poolIscsi", # pool-start takes too long, libvirt's pool-refresh might not catch all volumes, so we do pool-refresh separately
            ]
            self.machine.execute(" && ".join(cmds))
            self.addCleanup(self.machine.execute, "virsh pool-destroy poolIscsi; virsh pool-delete pool-delete; virsh pool-undefine poolIscsi")

            self.browser.reload()
            self.browser.enter_page('/machines')
            self.browser.wait_in_text("body", "Virtual Machines")

            # Check choosing existing volume as destination storage
            runner.createThenInstallTest(TestMachines.VmDialog(self, sourceType='file',
                                                               location=config.NOVELL_MOCKUP_ISO_PATH,
                                                               memory_size=256, memory_size_unit='MiB',
                                                               storage_pool="poolIscsi",
                                                               storage_volume="unit:0:0:0"))

    def testCreateFileSource(self):
        runner = TestMachines.CreateVmRunner(self)
        config = TestMachines.TestCreateConfig

        # Undefine the default storage pool for the first test
        self.machine.execute("virsh pool-destroy default; virsh pool-undefine default")

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        # Check that when there is no storage pool defined a VM can still be created
        runner.createTest(TestMachines.VmDialog(self, sourceType='file',
                                                location=config.NOVELL_MOCKUP_ISO_PATH,
                                                storage_pool="No Storage",
                                                start_vm=True))

        self.browser.switch_to_top()
        self.browser.wait_not_visible("#navbar-oops")

        # define again the default storage pool for system connection
        # we need so that the UI will know the remaining available space when we use that pool's path
        cmds = [
                "virsh pool-define-as default --type dir --target /var/lib/libvirt/images",
                "virsh pool-start default"
                ]
        self.machine.execute(" && ".join(cmds))

        self.browser.reload()
        self.browser.enter_page('/machines')
        self.browser.wait_in_text("body", "Virtual Machines")

        runner.createTest(TestMachines.VmDialog(self, sourceType='file',
                                                location=config.NOVELL_MOCKUP_ISO_PATH,
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                start_vm=False,
                                                connection='session'))

        # Try setting the storage size to value bigger than it's available
        # The dialog should auto-adjust it to match the pool's available space
        runner.createTest(TestMachines.VmDialog(self, sourceType='file',
                                                location=config.NOVELL_MOCKUP_ISO_PATH,
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_size=100000, storage_size_unit='GiB',
                                                start_vm=False))

        # Try setting the memory to value bigger than it's available on the OS
        # The dialog should auto-adjust it to match the OS'es total memory
        runner.createTest(TestMachines.VmDialog(self, sourceType='file',
                                                location=config.NOVELL_MOCKUP_ISO_PATH,
                                                memory_size=100000, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                start_vm=False))
        cmds = [
            "mkdir -p /var/lib/libvirt/pools/tmp\ pool; chmod a+rwx /var/lib/libvirt/pools/tmp\ pool",
            "virsh pool-define-as tmp\ pool --type dir --target /var/lib/libvirt/pools/tmp\ pool",
            "virsh pool-start tmp\ pool",
            "qemu-img create -f qcow2 /var/lib/libvirt/pools/tmp\ pool/vmTmpDestination.qcow2 128M",
            "virsh pool-refresh tmp\ pool"
        ]
        self.machine.execute(" && ".join(cmds))

        self.browser.reload()
        self.browser.enter_page('/machines')
        self.browser.wait_in_text("body", "Virtual Machines")

        # Check choosing existing volume as destination storage
        runner.createTest(TestMachines.VmDialog(self, sourceType='file',
                                                location=config.NOVELL_MOCKUP_ISO_PATH,
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_pool="tmp pool",
                                                storage_volume="vmTmpDestination.qcow2",
                                                start_vm=True,))

        # Check "No Storage" option (only define VM)
        runner.createTest(TestMachines.VmDialog(self, sourceType='file',
                                                location=config.NOVELL_MOCKUP_ISO_PATH,
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                start_vm=True,))

    def testCreateImportDisk(self):
        runner = TestMachines.CreateVmRunner(self)
        config = TestMachines.TestCreateConfig

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        runner.createTest(TestMachines.VmDialog(self, sourceType='disk_image',
                                                location=config.VALID_DISK_IMAGE_PATH,
                                                memory_size=256, memory_size_unit='MiB',
                                                start_vm=False))

        # Recreate the image the previous test just deleted to reuse it
        self.machine.execute("qemu-img create {0} 500M".format(TestMachines.TestCreateConfig.VALID_DISK_IMAGE_PATH))

        # Unload KVM module, otherwise we get errors getting the nested VMs
        # to start properly.
        # This is applicable for all tests that we want to really successfully run a nested VM.
        # in order to allow the rest of the tests to run faster with QEMU KVM
        # Stop pmcd service if available which is invoking pmdakvm and is keeping KVM module used
        self.machine.execute("(systemctl stop pmcd || true) && modprobe -r kvm_intel && modprobe -r kvm_amd && modprobe -r kvm")

        runner.createTest(TestMachines.VmDialog(self, sourceType='disk_image',
                                                location=config.VALID_DISK_IMAGE_PATH,
                                                memory_size=256, memory_size_unit='MiB',
                                                start_vm=True))

    def testCreateUrlSource(self):
        runner = TestMachines.CreateVmRunner(self)
        config = TestMachines.TestCreateConfig

        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")

        runner.checkEnvIsEmpty()

        runner.createTest(TestMachines.VmDialog(self, sourceType='url',
                                                location=config.VALID_URL,
                                                storage_size=1))

        # name already exists
        runner.createTest(TestMachines.VmDialog(self, name='existing-name', sourceType='url',
                                                location=config.VALID_URL, storage_size=1,
                                                delete=False))

        runner.checkDialogFormValidationTest(TestMachines.VmDialog(self, "existing-name", storage_size=1,
                                                                   env_is_empty=False), {"Name": "already exists"})

        self.machine.execute("virsh undefine existing-name")


        runner.createTest(TestMachines.VmDialog(self, sourceType='url',
                                                location=config.VALID_URL,
                                                memory_size=512, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                os_name=config.MICROSOFT_SERVER_2016))

        runner.createTest(TestMachines.VmDialog(self, sourceType='url',
                                                location=config.VALID_URL,
                                                memory_size=512, memory_size_unit='MiB',
                                                storage_pool="No Storage",
                                                os_name=config.MICROSOFT_SERVER_2016,
                                                start_vm=False))

        runner.createTest(TestMachines.VmDialog(self, sourceType='url',
                                                location=config.VALID_URL,
                                                memory_size=256, memory_size_unit='MiB',
                                                storage_size=100, storage_size_unit='MiB',
                                                start_vm=False))

        virtInstallVersion = self.machine.execute("virt-install --version")
        if virtInstallVersion >= "2":
            # Test detection of ISO file in URL
            runner.createTest(TestMachines.VmDialog(self, sourceType='url',
                                                    location=config.ISO_URL,
                                                    memory_size=256, memory_size_unit='MiB',
                                                    storage_pool="No Storage",
                                                    start_vm=True))

            # This functionality works on debian only because of extra dep.
            # Check error is returned if dependency is missing
            if self.machine.image.startswith("debian"):
                # remove package
                self.machine.execute("dpkg -P qemu-block-extra")
                runner.checkDialogErrorTest(TestMachines.VmDialog(self, sourceType='url',
                                                                  location=config.ISO_URL,
                                                                  memory_size=256, memory_size_unit='MiB',
                                                                  storage_pool="No Storage",
                                                                  start_vm=True), ["qemu", "protocol"])

    def testDisabledCreate(self):
        self.login_and_go("/machines")
        self.browser.wait_in_text("body", "Virtual Machines")
        self.browser.wait_visible("#create-new-vm:not(:disabled)")
        self.browser.wait_attr("#create-new-vm", "testdata", None)

        virt_install_bin = self.machine.execute("which virt-install").strip()
        self.machine.execute('mount -o bind /dev/null {0}'.format(virt_install_bin))
        self.addCleanup(self.machine.execute, "umount {0}".format(virt_install_bin))

        self.browser.reload()
        self.browser.enter_page('/machines')
        self.browser.wait_visible("#create-new-vm:disabled")
        # There are many reasons why the button would be disabled, so check if it's correct one
        self.browser.wait_attr("#create-new-vm", "testdata", "disabledVirtInstall")

    class TestCreateConfig:
        VALID_URL = 'http://mirror.i3d.net/pub/centos/7/os/x86_64/'
        VALID_DISK_IMAGE_PATH = '/var/lib/libvirt/images/example.img'
        NOVELL_MOCKUP_ISO_PATH = '/var/lib/libvirt/novell.iso'
        NOT_EXISTENT_PATH = '/tmp/not-existent.iso'
        ISO_URL = 'https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/28/Server/x86_64/os/images/boot.iso'

        MICROSOFT_SERVER_2016 = 'Microsoft Windows Server 2016'

        # LINUX can be filtered if 3 years old
        REDHAT_RHEL_4_7_FILTERED_OS = 'Red Hat Enterprise Linux 4.9'

        FEDORA_28 = 'Fedora 28'
        FEDORA_28_SHORTID = 'fedora28'

        CIRROS = 'CirrOS'

        MANDRIVA_2011_FILTERED_OS = 'Mandriva Linux 2011'

        MAGEIA_3_FILTERED_OS = 'Mageia 3'

    class VmDialog:
        vmId = 0

        def __init__(self, test_obj, name=None,
                     sourceType='file', sourceTypeSecondChoice=None, location='',
                     memory_size=256, memory_size_unit='MiB',
                     expected_memory_size=None,
                     storage_size=None, storage_size_unit='GiB',
                     expected_storage_size=None,
                     os_name='CirrOS',
                     os_short_id=None,
                     is_unattended=None,
                     profile=None,
                     root_password=None,
                     user_password=None,
                     storage_pool='Create New Volume', storage_volume='',
                     start_vm=False,
                     delete=True,
                     env_is_empty=True,
                     check_script_finished=True,
                     connection=None):

            TestMachines.VmDialog.vmId += 1 # This variable is static - don't use self here

            if name is None:
                self.name = 'subVmTestCreate' + str(TestMachines.VmDialog.vmId)
            else:
                self.name = name

            self.browser = test_obj.browser
            self.machine = test_obj.machine
            self.assertTrue = test_obj.assertTrue
            self.assertIn = test_obj.assertIn

            self.sourceType = sourceType
            self.sourceTypeSecondChoice = sourceTypeSecondChoice
            self.location = location
            self.memory_size = memory_size
            self.memory_size_unit = memory_size_unit
            self.expected_memory_size = expected_memory_size
            self.storage_size = storage_size
            self.storage_size_unit = storage_size_unit
            self.expected_storage_size = expected_storage_size
            self.os_name = os_name
            self.os_short_id = os_short_id
            self.is_unattended = is_unattended
            self.profile = profile
            self.root_password = root_password
            self.user_password = user_password
            self.start_vm = start_vm
            self.storage_pool = storage_pool
            self.storage_volume = storage_volume
            self.delete = delete
            self.env_is_empty = env_is_empty
            self.check_script_finished = check_script_finished
            self.connection = connection
            if self.connection:
                self.connectionText = connection.capitalize()

        def open(self):
            b = self.browser

            if self.sourceType == 'disk_image':
                b.click("#import-vm-disk")
            else:
                b.click("#create-new-vm")

            b.wait_present("#create-vm-dialog")
            if self.sourceType == 'disk_image':
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Import A Virtual Machine")
            else:
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Create New Virtual Machine")

            if self.os_name is not None:
                # check if there is os present in osinfo-query because it can be filtered out in the UI
                query_result = '{0}'.format(self.os_name)
                # throws exception if grep fails
                self.machine.execute(
                    "osinfo-query os --fields=name | tail -n +3 | sed -e 's/\s*|\s*/|/g; s/^\s*//g; s/\s*$//g' | grep '{0}'".format(
                        query_result))

            return self

        def checkOsFiltered(self):
            b = self.browser

            b.focus("label[for=os-select] + div > div > div > input")
            b.key_press(self.os_name)
            try:
                with b.wait_timeout(5):
                    b.wait_in_text("#os-select li a", "No matches found")
                return self
            except AssertionError:
                # os found which is not ok
                raise AssertionError("{0} was not filtered".format(self.os_name))

        def checkPXENotAvailableSession(self):
            self.browser.click("#connection label:contains('{0}')".format(self.connectionText))
            self.browser.wait_present("#source-type option[value*='{0}']:disabled".format(self.sourceType))
            return self

        def createAndVerifyVirtInstallArgs(self):
            self.browser.click(".modal-footer button:contains(Create)")
            self.browser.wait_not_present("#create-vm-dialog")

            virt_install_cmd = "ps aux | grep 'virt\-install\ \-\-connect'"
            wait(lambda: self.machine.execute(virt_install_cmd), delay=3)
            virt_install_cmd_out = self.machine.execute(virt_install_cmd)
            self.assertIn("--install os={}".format(self.os_short_id), virt_install_cmd_out)
            if self.is_unattended:
                self.assertIn("profile={0}".format('desktop' if self.profile == 'Workstation' else 'jeos'), virt_install_cmd_out)
                if self.root_password:
                    root_password_file = virt_install_cmd_out.split("admin-password-file=", 1)[1].split(",")[0]
                    self.assertIn(self.machine.execute("cat {0}".format(root_password_file)).rstrip(), self.root_password)
                if self.user_password:
                    user_password_file = virt_install_cmd_out.split("user-password-file=", 1)[1].split(",")[0]
                    self.assertIn(self.machine.execute("cat {0}".format(user_password_file)).rstrip(), self.user_password)

        def fill(self):
            def getSourceTypeLabel(sourceType):
                if sourceType == 'file':
                    expected_source_type = 'Local Install Media'
                elif sourceType == 'disk_image':
                    expected_source_type = 'Existing Disk Image'
                elif sourceType == 'pxe':
                    expected_source_type = 'Network Boot (PXE)'
                elif sourceType == 'downloadOS':
                    expected_source_type = 'Download an OS'
                else:
                    expected_source_type = 'URL'

                return expected_source_type

            b = self.browser
            b.set_input_text("#vm-name", self.name)

            if self.sourceType != 'disk_image':
                b.select_from_dropdown("#source-type", getSourceTypeLabel(self.sourceType))
            else:
                b.wait_not_present("#source-type")
            if self.sourceType == 'file':
                b.set_file_autocomplete_val("source-file", self.location)
            elif self.sourceType == 'disk_image':
                b.set_file_autocomplete_val("source-disk", self.location)
            elif self.sourceType == 'pxe':
                b.select_from_dropdown("#network-select", self.location)
            elif self.sourceType == 'url':
                b.set_input_text("#source-url", self.location)

            if self.sourceTypeSecondChoice:
                b.select_from_dropdown("#source-type", getSourceTypeLabel(self.sourceTypeSecondChoice))

            if self.os_name:
                b.focus("label:contains('Operating System') + div > div > div > input")
                b.key_press(self.os_name)
                b.key_press("\t")

            if self.sourceType != 'disk_image':
                if not self.expected_storage_size:
                    b.wait_visible("#storage-pool-select")
                    b.select_from_dropdown("#storage-pool-select", self.storage_pool)

                    if self.storage_pool == 'Create New Volume' or self.storage_pool == 'No Storage':
                        b.wait_not_present("#storage-volume-select")
                    else:
                        b.wait_visible("#storage-volume-select")
                        b.select_from_dropdown("#storage-volume-select", self.storage_volume)

                    if self.storage_pool != 'Create New Volume':
                        b.wait_not_present("#storage-size")
                    else:
                        b.select_from_dropdown("#storage-size-unit-select", self.storage_size_unit)
                        if self.storage_size is not None:
                            b.set_input_text("#storage-size", str(self.storage_size), value_check=False)
                            b.blur("#storage-size")
                            # helpblock will be missing if available storage size could not be calculated (no default storage pool found)
                            # test images sometimes may not have default storage pool defined for session connection
                            if self.connection != "session":
                                space_available = int(b.text("#storage-size-slider ~ b"))
                                # Write the final storage size back to self so that other function can read it
                                self.storage_size = min(self.storage_size, space_available)
                                b.wait_val("#storage-size", self.storage_size)
                else:
                    b.wait_val("#storage-size", self.expected_storage_size)

            # First select the unit so that UI will auto-adjust the memory input
            # value according to the available total memory on the host
            if not self.expected_memory_size:
                b.select_from_dropdown("#memory-size-unit-select", self.memory_size_unit)
                b.set_input_text("#memory-size", str(self.memory_size), value_check=True)
                b.blur('#memory-size')
                host_total_memory = int(b.text("#memory-size-slider ~ b"))
                # Write the final memory back to self so that other function can read it
                self.memory_size = min(self.memory_size, host_total_memory)
                b.wait_val("#memory-size", self.memory_size)
            else:
                b.wait_val("#memory-size", self.expected_memory_size)

            # check minimum memory is correctly set in the slider - the following are fake data
            if self.os_name in [TestMachines.TestCreateConfig.CIRROS, TestMachines.TestCreateConfig.FEDORA_28]:
                b.wait_attr("#memory-size-slider  div[role=slider].hide", "aria-valuemin", "128")

            b.wait_visible("#start-vm")
            if not self.start_vm:
                b.click("#start-vm") # TODO: fix this, do not assume initial state of the checkbox
            # b.set_checked("#start-vm", self.start_vm)

            if (self.connection):
                b.click("#connection label:contains('{0}')".format(self.connectionText))

            if self.is_unattended:
                b.click("#unattended-installation")
                if self.profile:
                    b.select_from_dropdown("#profile-select", self.profile)
                if self.user_password:
                    b.set_input_text("#user-password", self.user_password)
                if self.root_password:
                    b.set_input_text("#root-password", self.root_password)

            return self

        def cancel(self, force=False):
            b = self.browser
            if b.is_present("#create-vm-dialog"):
                b.click(".modal-footer button:contains(Cancel)")
                b.wait_not_present("#create-vm-dialog")
            elif force:
                raise Exception("There is no dialog to cancel")
            return self

        def createAndExpectInlineValidationErrors(self, errors):
            b = self.browser

            if self.sourceType == 'disk_image':
                b.click(".modal-footer button:contains(Import)")
            else:
                b.click(".modal-footer button:contains(Create)")

            for error, error_msg in errors.items():
                error_location = ".modal-body label:contains('{0}') + div.form-group.has-error span.help-block".format(error)
                b.wait_visible(error_location)
                if (error_msg):
                    b.wait_in_text(error_location, error_msg)

            if self.sourceType == 'disk_image':
                b.wait_present(".modal-footer button:contains(Import):disabled")
            else:
                b.wait_present(".modal-footer button:contains(Create):disabled")

            return self

        def createAndExpectError(self, errors):
            b = self.browser

            def waitForError(errors, error_location):
                for retry in range(0, 60):
                    error_message = b.text(error_location)
                    if any(error in error_message for error in errors):
                        break
                    time.sleep(5)
                else:
                    raise Error("Retry limit exceeded: None of [%s] is part of the error message '%s'" % (
                        ', '.join(errors), b.text(error_location)))

            b.click(".modal-footer button:contains(Create)")

            error_location = ".modal-footer div.pf-c-alert"

            b.wait_present(".modal-footer .spinner")
            b.wait_not_present(".modal-footer .spinner")
            try:
                with b.wait_timeout(10):
                    b.wait_present(error_location)
                    b.wait_in_text("button.alert-link.more-button", "show more")
                    b.click("button.alert-link.more-button")
                    waitForError(errors, error_location)

                # dialog can complete if the error was not returned immediately
            except Error:
                if b.is_present("#create-vm-dialog"):
                    raise
                else:
                    # then error should be shown in the notification area
                    error_location = ".toast-notifications-list-pf div.pf-c-alert"
                    with b.wait_timeout(20):
                        b.wait_present(error_location)
                        b.wait_in_text("button.alert-link.more-button", "show more")
                        b.click("button.alert-link.more-button")
                        waitForError(errors, error_location)

            # Close the notificaton
            b.click(".toast-notifications-list-pf div.pf-c-alert button.pf-c-button")
            b.wait_not_present(".toast-notifications-list-pf div.pf-c-alert")

            return self

    class CreateVmRunner:

        def __init__(self, test_obj):
            self.browser = test_obj.browser
            self.machine = test_obj.machine
            self.assertTrue = test_obj.assertTrue
            self.test_obj = test_obj

            self.machine.execute("touch {0}".format(TestMachines.TestCreateConfig.NOVELL_MOCKUP_ISO_PATH))
            self.machine.execute("qemu-img create {0} 500M".format(TestMachines.TestCreateConfig.VALID_DISK_IMAGE_PATH))
            test_obj.addCleanup(test_obj.machine.execute, "rm -f {0} {1}".format(
                TestMachines.TestCreateConfig.NOVELL_MOCKUP_ISO_PATH,
                TestMachines.TestCreateConfig.VALID_DISK_IMAGE_PATH))

            self._fakeOsDBInfo()
            self._fakeFedoraTree()

            # console for try INSTALL
            self.test_obj.allow_journal_messages('.*connection.*')
            self.test_obj.allow_journal_messages('.*Connection.*')
            self.test_obj.allow_journal_messages('.*session closed.*')

            self.test_obj.allow_browser_errors("Failed when connecting: Connection closed")
            self.test_obj.allow_browser_errors("Tried changing state of a disconnected RFB object")

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

            # See https://bugzilla.redhat.com/show_bug.cgi?id=1406979, this is a WONTFIX:
            # It suggests configure auditd to dontaudit these messages since selinux can't
            # offer whitelisting this directory for qemu process
            self.test_obj.allow_journal_messages('audit: type=1400 audit(.*): avc:  denied .*for .* comm="qemu-.* dev="proc" .*')

            # define default storage pool for system connection
            # we need so that the UI will know the remaining available space when we use that pool's path
            cmds = [
                "virsh pool-define-as default --type dir --target /var/lib/libvirt/images",
                "virsh pool-start default"
            ]
            self.machine.execute(" && ".join(cmds))

        def _setupMockFileServer(self):
            self.machine.upload(["verify/files/min-openssl-config.cnf", "verify/files/mock-range-server.py"], self.test_obj.vm_tmpdir)
            cmds = [
                # Generate certificate for https server
                "cd {0}".format(self.test_obj.vm_tmpdir),
                "openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -subj '/CN=archive.fedoraproject.org' -nodes -config {0}/min-openssl-config.cnf".format(self.test_obj.vm_tmpdir),
                "cat cert.pem key.pem > server.pem"
            ]

            if self.machine.image.startswith("ubuntu") or self.machine.image.startswith("debian"):
                cmds += [
                    "cp {0}/cert.pem /usr/local/share/ca-certificates/cert.crt".format(self.test_obj.vm_tmpdir),
                    "update-ca-certificates"
                ]
            else:
                cmds += [
                    "cp {0}/cert.pem /etc/pki/ca-trust/source/anchors/cert.pem".format(self.test_obj.vm_tmpdir),
                    "update-ca-trust"
                ]
            self.machine.execute(" && ".join(cmds))

            # Run https server with range option support. QEMU uses range option
            # see: https://lists.gnu.org/archive/html/qemu-devel/2013-06/msg02661.html
            # or
            # https://github.com/qemu/qemu/blob/master/block/curl.c
            #
            # and on certain distribution supports only https (not http)
            # see: block-drv-ro-whitelist option in qemu-kvm.spec for certain distribution
            server = self.machine.spawn("cd /var/lib/libvirt && exec python3 {0}/mock-range-server.py {0}/server.pem".format(self.test_obj.vm_tmpdir), "httpsserver")
            self.test_obj.addCleanup(self.machine.execute, "kill {0}".format(server))

        def _fakeOsDBInfo(self):
            # Fake the osinfo-db data in order that it will allow spawn the installation - of course we don't expect it to succeed -
            # we just need to check that the VM was spawned
            fedora_28_xml = self.machine.execute("cat /usr/share/osinfo/os/fedoraproject.org/fedora-28.xml")
            root = ET.fromstring(fedora_28_xml)
            root.find('os').find('resources').find('minimum').find('ram').text = '134217750'
            root.find('os').find('resources').find('minimum').find('storage').text = '134217750'
            root.find('os').find('resources').find('recommended').find('ram').text = '268435500'
            root.find('os').find('resources').find('recommended').find('storage').text = '268435500'
            if self.machine.image not in ["debian-stable"]:
                root.find('os').find('eol-date').text = '9999-12-31'
            new_fedora_28_xml = ET.tostring(root)
            self.machine.execute("echo \'{0}\' > {1}/fedora-28.xml".format(str(new_fedora_28_xml, 'utf-8'), self.test_obj.vm_tmpdir))
            self.machine.execute("mount -o bind  {0}/fedora-28.xml /usr/share/osinfo/os/fedoraproject.org/fedora-28.xml".format(self.test_obj.vm_tmpdir))
            self.test_obj.addCleanup(self.machine.execute, "umount /usr/share/osinfo/os/fedoraproject.org/fedora-28.xml || true")

        def _fakeFedoraTree(self):
            self._setupMockFileServer()
            distro_tree_path = "/var/lib/libvirt/pub/archive/fedora/linux/releases/28/Server/x86_64/os/"
            self.machine.execute("mkdir -p {0}".format(distro_tree_path))
            self.machine.upload(["verify/files/fakefedoratree/.treeinfo", "verify/files/fakefedoratree/images"], distro_tree_path)
            # borrow the kernel executable for the test server
            self.machine.execute("cp /boot/vmlinuz-{0} {1}images/pxeboot/vmlinuz".format(self.machine.execute("uname -r").rstrip(), distro_tree_path))
            self.machine.execute("chown -R 777 /var/lib/libvirt/pub")
            self.test_obj.restore_file("/etc/hosts")
            self.machine.execute('echo "127.0.0.1 archive.fedoraproject.org" >> /etc/hosts')

        def _assertVmStates(self, name, before, after):
            b = self.browser
            selector = "#vm-{0}-state".format(name)

            b.wait_in_text(selector, before)

            # Make sure that the initial state goes away and then try to check what the new state is
            # because we might end up checking the text for the new state the momment it dissapears
            def waitStateWentAway(selector, state):
                try:
                    b.wait_text_not(selector, state)
                    return True
                except Error:
                    return False

            wait(lambda: waitStateWentAway(selector, before), delay=3)
            b.wait_in_text(selector, after)

        def _create(self, dialog):
            b = self.browser
            if dialog.sourceType == 'disk_image':
                b.click(".modal-footer button:contains(Import)")
            else:
                b.click(".modal-footer button:contains(Create)")
            init_state = "creating VM installation" if dialog.start_vm else "creating VM"
            second_state = "running" if dialog.start_vm else "shut off"

            self._assertVmStates(dialog.name, init_state, second_state)
            b.wait_not_present("#create-vm-dialog")

        def _tryCreate(self, dialog):
            b = self.browser
            name = dialog.name

            dialog.open() \
                .fill()
            self._create(dialog)

            # successfully created
            self.test_obj.waitVmRow(name, dialog.connection or "system")

            if dialog.start_vm:
                # wait for console tab to open
                b.wait_present("#vm-{0}-consoles.pf-m-current".format(name))
            else:
                # wait for Overview tab to open
                b.wait_present("#vm-{0}-memory-count".format(name))
                b.wait_present("#vm-{0}-vcpus-count".format(name))

            self._assertCorrectConfiguration(dialog)

            return self

        def _tryCreateThenInstall(self, dialog):
            b = self.browser
            dialog.start_vm = False
            name = dialog.name

            dialog.open() \
                .fill()
            self._create(dialog)

            self.test_obj.waitVmRow(name)
            b.click("#vm-{0}-install".format(name))
            b.wait_present("#vm-{0}-consoles.pf-m-current".format(name))

            self._assertCorrectConfiguration(dialog)

            # unfinished install script runs indefinitelly, so we need to force it off
            b.click("#vm-{0}-action-kebab button".format(name))
            b.click("#vm-{0}-forceOff".format(name))
            # https://bugzilla.redhat.com/show_bug.cgi?id=1818089
            # After shutting it off virt-install will restart the domain and exit because of the above bug
            b.wait_in_text("#vm-{0}-state".format(name), "shut off")

            return self

        def _deleteVm(self, dialog):
            b = self.browser
            vm_delete_button = "#vm-{0}-delete".format(dialog.name)

            b.click("#vm-{0}-action-kebab button".format(dialog.name))
            b.click(vm_delete_button)
            b.wait_present("#vm-{0}-delete-modal-dialog".format(dialog.name))
            b.click("#vm-{0}-delete-modal-dialog button:contains(Delete)".format(dialog.name))
            b.wait_not_present("#vm-{0}-delete-modal-dialog".format(dialog.name))
            self.test_obj.waitVmRow(dialog.name, "system", False)

            return self

        def _commandNotRunning(self, command):
            try:
                self.machine.execute("pgrep -fc {0}".format(command))
                return False
            except subprocess.CalledProcessError as e:
                return hasattr(e, 'returncode') and e.returncode == 1

        def _assertCorrectConfiguration(self, dialog):
            b = self.browser
            name = dialog.name

            # check memory
            b.click("#vm-{0}-usage".format(name))
            # adjust to how cockpit_format_bytes() formats sizes > 1024 MiB -- this depends on how much RAM
            # the host has (less or more than 1 GiB), and thus needs to be dynamic
            if dialog.memory_size >= 1024 and dialog.memory_size_unit == "MiB":
                memory_text = "%.2f GiB" % (dialog.memory_size/1024)
            else:
                memory_text = "{0} {1}".format(dialog.memory_size, dialog.memory_size_unit)
            b.wait_in_text("tbody.pf-m-expanded .ct-listing-panel-body td:nth-child(1) .usage-donut-caption", memory_text)

            # check disks
            b.click("#vm-{0}-disks".format(name)) # open the "Disks" subtab

            # Test disk got imported/created
            if dialog.sourceType == 'disk_image':
                if b.is_present("#vm-{0}-disks-vda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-vda-source-file".format(name), dialog.location)
                elif b.is_present("#vm-{0}-disks-hda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-hda-source-file".format(name), dialog.location)
                else:
                    raise AssertionError("Unknown disk device")
            # New volume was created or existing volume was already chosen as destination
            elif (dialog.storage_size is not None and dialog.storage_size > 0) or dialog.storage_pool not in ["No Storage", "Create New Volume"]:
                if b.is_present("#vm-{0}-disks-vda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-vda-device".format(name), "disk")
                elif b.is_present("#vm-{0}-disks-hda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-hda-device".format(name), "disk")
                elif b.is_present("#vm-{0}-disks-sda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-sda-device".format(name), "disk")
                else:
                    raise AssertionError("Unknown disk device")
            elif dialog.start_vm and (((dialog.storage_pool == 'No Storage' or dialog.storage_size == 0) and dialog.sourceType == 'file') or dialog.sourceType == 'url'):
                if b.is_present("#vm-{0}-disks-sda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-sda-device".format(name), "cdrom")
                elif b.is_present("#vm-{0}-disks-hda-device".format(name)):
                    b.wait_in_text("#vm-{0}-disks-hda-device".format(name), "cdrom")
                else:
                    raise AssertionError("Unknown disk device")
            else:
                b.wait_in_text(".ct-table-empty", "No disks defined")
                b.click("#vm-{0}-disks-adddisk".format(name))
                b.click("#vm-{0}-disks-adddisk-dialog-cancel".format(name))
            return self

        def _assertScriptFinished(self):
            with self.browser.wait_timeout(20):
                self.browser.wait(functools.partial(self._commandNotRunning, "virt-install"))

            return self

        def _assertDomainDefined(self, name, connection):
            listCmd = ""
            if connection == "session":
                listCmd = "runuser -l admin -c 'virsh -c qemu:///session list --persistent --all'"
            else:
                # When creating VMs from the UI default connection is the system
                # In this case don't use runuser -l admin because we get errors 'authentication unavailable'
                listCmd = "virsh -c qemu:///system list --persistent --all"

            wait(lambda: name in self.machine.execute(listCmd))

            return self

        def _assertOsInfoQueryFinished(self):
            with self.browser.wait_timeout(10):
                self.browser.wait(functools.partial(self._commandNotRunning, "osinfo-query"))
            return self

        def checkEnvIsEmpty(self):
            b = self.browser
            b.wait_in_text("#virtual-machines-listing thead tr td", "No VM is running")
            # wait for the vm and disks to be deleted
            self.machine.execute("until test -z $(virsh list --all --name); do sleep 1; done")
            self.machine.execute("until test -z $(ls /home/admin/.local/share/libvirt/images/ 2>/dev/null); do sleep 1; done")

            if b.is_present(".toast-notifications-list-pf div.pf-c-alert .pf-c-button"):
                b.click(".toast-notifications-list-pf div.pf-c-alert .pf-c-button")

            b.wait_not_present(".toast-notifications-list-pf div.pf-c-alert")

            return self

        # Many of testCreate* tests use these helpers - let's keep them here to avoid repetition
        def checkDialogFormValidationTest(self, dialog, errors):
            dialog.open() \
                .fill() \
                .createAndExpectInlineValidationErrors(errors) \
                .cancel(True)
            if dialog.check_script_finished:
                self._assertScriptFinished()
            if dialog.env_is_empty:
                self.checkEnvIsEmpty()

        def createTest(self, dialog):
            self._tryCreate(dialog) \

            # When not booting the actual OS from either existing image or PXE
            # configure virt-install to wait for the installation to complete.
            # Thus we should only check that virt-install exited when using existing disk images.
            if dialog.sourceType == 'disk_image' or dialog.sourceType == 'pxe':
                self._assertScriptFinished() \
                    ._assertDomainDefined(dialog.name, dialog.connection)

            if dialog.delete:
                self._deleteVm(dialog) \
                      .checkEnvIsEmpty()

        def cancelDialogTest(self, dialog):
            dialog.open() \
                .fill() \
                .cancel(True)
            self._assertScriptFinished() \
                .checkEnvIsEmpty()

        def checkFilteredOsTest(self, dialog):
            dialog.open() \
                .checkOsFiltered() \
                .cancel(True)
            self._assertScriptFinished() \
                .checkEnvIsEmpty()

        def createDownloadAnOSTest(self, dialog):
            dialog.open() \
                .fill() \
                .createAndVerifyVirtInstallArgs()
            if dialog.delete:
                self._deleteVm(dialog) \
                      .checkEnvIsEmpty()

        def checkPXENotAvailableSessionTest(self, dialog):
            dialog.open() \
                .checkPXENotAvailableSession() \
                .cancel(True)
            self._assertScriptFinished() \
                .checkEnvIsEmpty()

        def createThenInstallTest(self,dialog):
            self._tryCreateThenInstall(dialog) \
                  ._assertScriptFinished() \
                  ._assertDomainDefined(dialog.name, dialog.connection) \
                  ._deleteVm(dialog) \
                  .checkEnvIsEmpty()

        def checkDialogErrorTest(self, dialog, errors):
            dialog.open() \
                .fill() \
                .createAndExpectError(errors) \
                .cancel(False)
            self._assertScriptFinished() \
                .checkEnvIsEmpty()

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

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")


        # set checkbox state and check state of checkbox
        b.set_checked("#vm-subVmTest1-autostart-checkbox", True) # don't know the initial state of checkbox, so set it to checked
        b.wait_present("#vm-subVmTest1-autostart-checkbox:checked")
        # check virsh state
        autostartState = m.execute("virsh dominfo subVmTest1 | grep 'Autostart:' | awk '{print $2}'").strip()
        self.assertEqual(autostartState, "enable")

        # change checkbox state and check state of checkbox
        b.click("#vm-subVmTest1-autostart-checkbox")
        b.wait_not_present("#vm-subVmTest1-autostart-checkbox:checked")
        # check virsh state
        autostartState = m.execute("virsh dominfo subVmTest1 | grep 'Autostart:' | awk '{print $2}'").strip()
        self.assertEqual(autostartState, "disable")

        # change checkbox state and check state of checkbox
        b.click("#vm-subVmTest1-autostart-checkbox")
        b.wait_present("#vm-subVmTest1-autostart-checkbox:checked")
        # check virsh state
        autostartState = m.execute("virsh dominfo subVmTest1 | grep 'Autostart:' | awk '{print $2}'").strip()
        self.assertEqual(autostartState, "enable")

        # non-persistent VM doesn't have autostart
        m.execute("virsh undefine subVmTest1")
        b.wait_not_present("#vm-subVmTest1-autostart-checkbox")

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

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Wait for the edit button
        bootOrder = b.text("#vm-subVmTest1-boot-order")

        # Open dialog
        b.click("#vm-subVmTest1-boot-order")
        b.wait_present("#vm-subVmTest1-order-modal-window")
        # Make sure the footer warning does not appear
        b.wait_not_present("#vm-subVmTest1-order-modal-min-message")
        # Move first device down and check whetever succeeded
        row = b.text("#vm-subVmTest1-order-modal-device-row-1 .list-view-pf-additional-info")
        b.click("#vm-subVmTest1-order-modal-device-row-0 #vm-subVmTest1-order-modal-down")
        b.wait_in_text("#vm-subVmTest1-order-modal-device-row-0 .list-view-pf-additional-info", row)
        # Make sure the footer warning does appear
        b.wait_present("#vm-subVmTest1-order-modal-min-message")

        # Save
        b.click("#vm-subVmTest1-order-modal-save")
        b.wait_not_present("#vm-subVmTest1-order-modal-window")
        # Make sure warning next to boot order appears
        b.wait_present("#boot-order-tooltip")

        # Shut off domain and check changes are applied
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # Check boot order has changed and no warning is shown
        b.wait_not_in_text("#vm-subVmTest1-boot-order", bootOrder)
        b.wait_not_present("#boot-order-tooltip")

        bootOrder = b.text("#vm-subVmTest1-boot-order")

        # Open dialog
        b.click("#vm-subVmTest1-boot-order")
        b.wait_present("#vm-subVmTest1-order-modal-window")
        # Make sure the footer warning does not appear
        b.wait_not_present("#vm-subVmTest1-order-modal-min-message")
        # Unselect second device
        b.click("#vm-subVmTest1-order-modal-device-row-1 input")
        # Make sure the footer warning still does not appear, since machine is shut down
        b.wait_not_present("#vm-subVmTest1-order-modal-min-message")

        # Save
        b.click("#vm-subVmTest1-order-modal-save")
        b.wait_not_present("#vm-subVmTest1-order-modal-window")

        # Check boot order has changed and no warning is shown
        b.wait_not_in_text("#vm-subVmTest1-boot-order", bootOrder)
        b.wait_not_present("#boot-order-tooltip")

        # Run VM
        b.click("#vm-subVmTest1-run")
        b.wait_in_text("#vm-subVmTest1-state", "running")
        # Non-persistent VM doesn't have configurable boot order
        m.execute("virsh undefine subVmTest1")
        b.wait_present("button#vm-subVmTest1-boot-order:disabled")

    # Check various configuration changes made before VM installation persist after installation
    def testConfigureBeforeInstall(self):
        b = self.browser
        m = self.machine

        # define default storage pool for system connection
        cmds = [
            "virsh pool-define-as default --type dir --target /var/lib/libvirt/images",
            "virsh pool-start default"
        ]
        m.execute(" && ".join(cmds))
        m.execute("touch {0}".format(TestMachines.TestCreateConfig.NOVELL_MOCKUP_ISO_PATH))

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        dialog = TestMachines.VmDialog(self,sourceType='file',
                                       name="VmNotInstalled",
                                       location=TestMachines.TestCreateConfig.NOVELL_MOCKUP_ISO_PATH,
                                       memory_size=256, memory_size_unit='MiB',
                                       storage_size=256, storage_size_unit='MiB',
                                       start_vm=False)
        dialog.open() \
                .fill() \

        b.click(".modal-footer button:contains(Create)")
        b.wait_not_present("#create-vm-dialog")

        self.waitVmRow("VmNotInstalled")

        # Change autostart
        autostart = not b.get_checked("#vm-VmNotInstalled-autostart-checkbox")
        b.set_checked("#vm-VmNotInstalled-autostart-checkbox", autostart)

        # Change memory settings
        b.click("#vm-VmNotInstalled-memory-count")
        b.wait_present("#vm-memory-modal")
        b.set_input_text("#vm-VmNotInstalled-memory-modal-max-memory", "400")
        b.blur("#vm-VmNotInstalled-memory-modal-max-memory")
        b.set_input_text("#vm-VmNotInstalled-memory-modal-memory", "300")
        b.blur("#vm-VmNotInstalled-memory-modal-memory")
        b.click("#vm-VmNotInstalled-memory-modal-save")
        b.wait_not_present(".modal-body")

        # Change vCPUs setting
        b.click("#vm-VmNotInstalled-vcpus-count")
        b.wait_present(".modal-body")
        b.set_input_text("#machines-vcpu-max-field", "8")
        b.blur("#machines-vcpu-max-field")
        b.set_input_text("#machines-vcpu-count-field", "2")
        b.blur("#machines-vcpu-count-field")
        b.select_from_dropdown("#socketsSelect", "2")
        b.select_from_dropdown("#coresSelect", "2")
        b.select_from_dropdown("#threadsSelect", "2")
        b.click("#machines-vcpu-modal-dialog-apply")
        b.wait_not_present(".modal-body")

        # Change Boot Order setting
        bootOrder = b.text("#vm-VmNotInstalled-boot-order")
        b.click("#vm-VmNotInstalled-boot-order")
        b.wait_present(".modal-body")
        b.set_checked("#vm-VmNotInstalled-order-modal-device-row-1-checkbox", True)
        b.click("#vm-VmNotInstalled-order-modal-device-row-0 #vm-VmNotInstalled-order-modal-down")
        b.click("#vm-VmNotInstalled-order-modal-save")
        b.wait_not_present(".modal-body")

        # Attach some interface
        m.execute("virsh attach-interface --persistent VmNotInstalled bridge virbr0")

        # Change the os boot firmware configuration
        supports_firmware_config = m.image in ['fedora-32', 'fedora-31', 'fedora-testing', 'rhel-8-3', 'rhel-8-3-distropkg', 'debian-testing', 'ubuntu-stable', 'ubuntu-2004']
        if supports_firmware_config:
            b.wait_in_text("#vm-VmNotInstalled-firmware", "BIOS")
            b.click("#vm-VmNotInstalled-firmware")
            b.wait_present(".modal-body")
            b.select_from_dropdown("select", "UEFI")
            b.click("#firmware-dialog-apply")
            b.wait_not_present(".modal-body")
            b.wait_in_text("#vm-VmNotInstalled-firmware", "UEFI")

            # Temporarily delete the OVMF binary and check the firmware options again
            if "fedora" in m.image or "rhel" in m.image:
                ovmf_path = "/usr/share/edk2"
            elif "debian" in m.image or "ubuntu" in m.image:
                ovmf_path = "/usr/share/OVMF"
            else:
                raise AssertionError("Unhandled distro for OVMF path")

            m.execute("mount -t tmpfs tmpfs " + ovmf_path)
            self.addCleanup(m.execute, "umount {0} || true".format(ovmf_path))

            # Reload for the new configuration to be read
            b.reload()
            b.enter_page('/machines')
            self.toggleVmRow("VmNotInstalled")

            # HACK: Capabilities are not updated dynamically
            # https://bugzilla.redhat.com/show_bug.cgi?id=1807198
            def hack_broken_caps():
                if m.image in ["fedora-32", "rhel-8-3", "rhel-8-3-distropkg", "ubuntu-2004", "debian-testing"]:
                    m.execute("systemctl restart libvirtd")
                    # We don't get events for shut off VMs so reload the page
                    b.reload()
                    b.enter_page('/machines')
                    self.toggleVmRow("VmNotInstalled")

            hack_broken_caps()

            b.mouse("#vm-VmNotInstalled-firmware-tooltip", "mouseenter")
            b.wait_in_text(".pf-c-tooltip", "Libvirt did not detect any UEFI/OVMF firmware image installed on the host")
            b.mouse("#vm-VmNotInstalled-firmware-tooltip", "mouseleave")
            b.wait_not_present("#missing-uefi-images")
            m.execute("umount " + ovmf_path)

            hack_broken_caps()
        else:
            b.wait_not_present("#vm-VmNotInstalled-firmware")

        # Install the VM
        b.click("#vm-VmNotInstalled-install")

        # Wait for virt-install to define the VM and then stop it - otherwise we get 'domain is ready being removed' error
        wait(lambda: "VmNotInstalled" in m.execute("virsh list --persistent"), delay=3)
        logfile = "/var/log/libvirt/qemu/VmNotInstalled.log"
        m.execute("> {0}".format(logfile)) # clear logfile
        b.wait_present("#vm-VmNotInstalled-consoles.pf-m-current")
        b.click("#vm-VmNotInstalled-action-kebab button")
        b.click("#vm-VmNotInstalled-forceOff")
        # https://bugzilla.redhat.com/show_bug.cgi?id=1818089
        # After shutting it off virt-install will restart the domain and exit because of the above bug
        # We need to wait till it's restarted and shut if off again in order to edit the offline VM configuration
        wait(lambda: "char device" in self.machine.execute("cat {0}".format(logfile)), delay=3)
        b.click("#vm-VmNotInstalled-action-kebab button")
        b.click("#vm-VmNotInstalled-forceOff")
        b.wait_in_text("#vm-VmNotInstalled-state", "shut off")

        # Check configuration changes survived installation
        b.click("#vm-VmNotInstalled-overview")

        # Check memory settings have persisted
        b.click("#vm-VmNotInstalled-memory-count")
        b.wait_present("#vm-VmNotInstalled-memory-modal-memory")
        b.wait_val("#vm-VmNotInstalled-memory-modal-max-memory", "400")
        b.wait_val("#vm-VmNotInstalled-memory-modal-memory", "300")
        b.click("#vm-VmNotInstalled-memory-modal-cancel")
        b.wait_not_present(".modal-body")

        # Check vCPU settings have persisted
        b.click("#vm-VmNotInstalled-vcpus-count")
        b.wait_present(".modal-body")
        b.wait_val("#machines-vcpu-max-field", "8")
        b.wait_val("#machines-vcpu-count-field", "2")
        b.wait_val("#socketsSelect", "2")
        b.wait_val("#coresSelect", "2")
        b.wait_val("#threadsSelect", "2")
        b.click("#machines-vcpu-modal-dialog-cancel")
        b.wait_not_present(".modal-body")

        # Check changed boot order have persisted
        b.wait_text_not("#vm-VmNotInstalled-boot-order", bootOrder)

        # Check firmware changes persisted
        if supports_firmware_config:
            b.wait_in_text("#vm-VmNotInstalled-firmware", "UEFI")
        else:
            b.wait_not_present("#vm-VmNotInstalled-firmware")

        # Check autostart have persisted
        self.assertEqual(b.get_checked("#vm-VmNotInstalled-autostart-checkbox"), autostart)

        # Check new vNIC have persisted
        b.click("#vm-VmNotInstalled-networks")
        b.wait_present("#vm-VmNotInstalled-network-1-type")

        self.allow_browser_errors("Failed when connecting: Connection closed")
        self.allow_browser_errors("Tried changing state of a disconnected RFB object")
        self.allow_journal_messages(".* couldn't shutdown fd: Transport endpoint is not connected")

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

        self.startVm("subVmTest1")

        # prepare libvirt storage pools
        p1 = os.path.join(self.vm_tmpdir, "vm_one")
        m.execute("mkdir --mode 777 {0}".format(p1))
        m.execute("virsh pool-create-as myPoolOne --type dir --target {0}".format(p1))
        m.execute("virsh vol-create-as myPoolOne mydisk --capacity 100M --format raw") # raw support shareable
        m.execute("virsh vol-create-as myPoolOne mydisk2 --capacity 100M --format raw")
        m.execute("virsh vol-create-as myPoolOne mydisk3 --capacity 100M --format qcow2")
        wait(lambda: "mydisk" in m.execute("virsh vol-list myPoolOne"))
        wait(lambda: "mydisk2" in m.execute("virsh vol-list myPoolOne"))
        wait(lambda: "mydisk3" in m.execute("virsh vol-list myPoolOne"))

        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydisk --target vde --targetbus virtio --persistent".format(p1))
        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydisk2 --target vdf --targetbus virtio".format(p1))
        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydisk3 --target vdg --targetbus virtio --subdriver qcow2 --persistent".format(p1))

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")
        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")
        b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

        # Test non-persistent disks are not configurable
        b.wait_not_present("#vm-subVmTest1-disks-vdf-edit")

        # Test qcow2 disk has only readonly attribute configurable
        b.click("#vm-subVmTest1-disks-vdg-edit")
        b.wait_present("#vm-subVmTest1-disks-vdg-edit-dialog")
        b.wait_present("#vm-subVmTest1-disks-vdg-edit-readonly")
        b.wait_present("#vm-subVmTest1-disks-vdg-edit-writable")
        b.wait_not_present("#vm-subVmTest1-disks-vdg-edit-writable-shareable")
        b.click("#vm-subVmTest1-disks-vdg-edit-dialog-cancel")
        b.wait_not_present("#vm-subVmTest1-disks-vdg-edit-dialog")

        # Test configuration of readonly and shareable attributes
        b.click("#vm-subVmTest1-disks-vde-edit")
        b.wait_present("#vm-subVmTest1-disks-vde-edit-dialog")

        # Changing readonly with running VM
        b.click("#vm-subVmTest1-disks-vde-edit-readonly")

        # Tooltip in dialog should show
        b.wait_present("#vm-subVmTest1-disks-vde-edit-idle-message")

        # Save changes
        b.click("#vm-subVmTest1-disks-vde-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-disks-vde-edit-dialog")
        # See tooltip present in disk listing table
        b.wait_present("#vm-subVmTest1-disks-vde-access-tooltip")

        # Shut off VM and see state has changed
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")
        # Check change has been applied after shutoff
        b.wait_in_text("#vm-subVmTest1-disks-vde-access", "Read-only")
        # See tooltip no longer present in disk listing table
        b.wait_not_present("#vm-subVmTest1-disks-vde-access-tooltip")

        # Test configuration of readonly and shareable attributes for Shut off VM
        b.click("#vm-subVmTest1-disks-vde-edit")
        b.wait_present("#vm-subVmTest1-disks-vde-edit-dialog")

        # Changing readonly
        b.click("#vm-subVmTest1-disks-vde-edit-writable-shareable")
        # Tooltip in dialog should not be present
        b.wait_not_present("#vm-subVmTest1-disks-vde-edit-idle-message")

        # Close dialog
        b.click("#vm-subVmTest1-disks-vde-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-disks-vde-edit-dialog")
        b.wait_in_text("#vm-subVmTest1-disks-vde-access", "Writeable and shared")
        b.wait_not_present("#vm-subVmTest1-disks-vde-access-tooltip")

        b.wait_in_text("#vm-subVmTest1-disks-vde-bus", "virtio")
        # Change bus type to scsi
        b.click("#vm-subVmTest1-disks-vde-edit")
        b.wait_present("#vm-subVmTest1-disks-vde-edit-dialog")
        b.select_from_dropdown("#vm-subVmTest1-disks-vde-edit-bus-type", "scsi")

        # Close dialog
        b.click("#vm-subVmTest1-disks-vde-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-disks-vde-edit-dialog")
        # Target has changed from vdX to sdX
        b.wait_in_text("#vm-subVmTest1-disks-sda-bus", "scsi")

        # Start Vm
        b.click("#vm-subVmTest1-run")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Test disk's bus cannot be changed on running VM
        b.click("#vm-subVmTest1-disks-sda-edit")
        b.wait_present("#vm-subVmTest1-disks-sda-edit-dialog")
        b.wait_present("#vm-subVmTest1-disks-sda-edit-bus-type:disabled")

        # Disks on non-persistent VM cannot be edited
        m.execute("virsh undefine subVmTest1")
        b.wait_not_present("#vm-subVmTest1-disks-vde-edit")

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

        args = self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        b.click("#vm-subVmTest1-overview") # open the "Overview" subtab

        # Wait for the edit link
        b.click("#vm-subVmTest1-memory-count")

        # Change memory
        b.wait_present("#vm-subVmTest1-memory-modal-memory")

        b.wait_attr("#vm-subVmTest1-memory-modal-memory-slider  div[role=slider].hide", "aria-valuemin", "128")
        b.wait_attr("#vm-subVmTest1-memory-modal-max-memory-slider  div[role=slider].hide", "aria-valuemin", "128")

        # VM initially should have 256MiB
        current_memory = int(b.attr("#vm-subVmTest1-memory-modal-memory", "value"))
        self.assertEqual(current_memory, 256)
        b.wait_attr("#vm-subVmTest1-memory-modal-max-memory", "disabled", "")

        # Check memory hotunplugging
        # The balloon driver needs to be loaded to descrease memory
        wait(lambda: "Linux version" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

        b.set_input_text("#vm-subVmTest1-memory-modal-memory", str(current_memory - 10))
        b.blur("#vm-subVmTest1-memory-modal-memory")
        # Save the memory settings
        b.click("#vm-subVmTest1-memory-modal-save")
        b.wait_not_present("#vm-memory-modal")

        b.wait_in_text("#vm-subVmTest1-memory-count", "{0} MiB".format(current_memory - 10))

        # Shut off domain and check changes are  still there
        b.click("#vm-subVmTest1-action-kebab button")
        b.wait_visible("#vm-subVmTest1-forceOff")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")
        b.wait_in_text("#vm-subVmTest1-memory-count", "{0} MiB".format(current_memory - 10))

        # Click for the edit link
        b.click("#vm-subVmTest1-memory-count")

        # Verify that limiting max memory in offline VMs bellow memory will decrease memory as well
        b.set_input_text("#vm-subVmTest1-memory-modal-max-memory", str(current_memory - 20))
        b.blur("#vm-subVmTest1-memory-modal-max-memory")
        self.assertEqual(int(b.attr("#vm-subVmTest1-memory-modal-memory", "value")), current_memory - 20)

        # Verify that increasing current memory in offline VMs above max memory will increase max memory as well
        b.set_input_text("#vm-subVmTest1-memory-modal-memory", str(current_memory))
        b.blur("#vm-subVmTest1-memory-modal-memory")
        b.wait_attr("#vm-subVmTest1-memory-modal-max-memory", "value", str(current_memory))

        # Verify that unit conversions work
        b.select_from_dropdown("#vm-subVmTest1-memory-modal-memory-unit-select", "GiB")
        b.wait_attr("#vm-subVmTest1-memory-modal-memory", "value", "0")

        # Run VM
        b.click("#vm-subVmTest1-run")
        b.wait_in_text("#vm-subVmTest1-state", "running")
        # Non-persistent VM doesn't have configurable memory
        m.execute("virsh undefine subVmTest1")
        b.wait_present("button#vm-subVmTest1-memory-count:disabled")

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

        # prepare libvirt storage pools
        p1 = os.path.join(self.vm_tmpdir, "vm_one")
        m.execute("mkdir --mode 777 {0}".format(p1))
        m.execute("virsh pool-create-as myPoolOne --type dir --target {0}".format(p1))
        m.execute("virsh vol-create-as myPoolOne mydiskofpoolone_1 --capacity 1G --format qcow2")
        m.execute("virsh vol-create-as myPoolOne mydiskofpoolone_2 --capacity 1G --format qcow2")
        m.execute("virsh vol-create-as myPoolOne mydiskofpoolone_3 --capacity 1M --format qcow2")
        wait(lambda: "mydiskofpoolone_1" in m.execute("virsh vol-list myPoolOne"))
        wait(lambda: "mydiskofpoolone_2" in m.execute("virsh vol-list myPoolOne"))
        wait(lambda: "mydiskofpoolone_3" in m.execute("virsh vol-list myPoolOne"))

        args = self.startVm("subVmTest1")

        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydiskofpoolone_1 --target vdc --targetbus virtio".format(p1))
        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydiskofpoolone_2 --target vdd --targetbus virtio --persistent".format(p1))
        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydiskofpoolone_3 --target vde --targetbus virtio --persistent".format(p1))

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        # Wait for the login promt before we try detaching disks - we need the OS to be fully responsive
        wait(lambda: "login as 'cirros' user" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

        # Test detaching non permanent disk of a running domain
        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        b.click("#vm-subVmTest1-disks") # open the "Disks" subtab
        b.wait_present("#vm-subVmTest1-disks-vdc-device")
        b.click("#delete-subVmTest1-disk-vdc")
        b.wait_present(".modal-dialog")
        b.click(".modal-footer button:contains(Remove)")
        b.wait_present(".modal-footer .spinner")
        # When live-detaching disks the guest OS needs to cooperate so that we can
        # see the disk getting detached in the UI.
        # Wait until we see the login prompt before attempting operation that need
        # the OS for fully respond
        with b.wait_timeout(180):
            b.wait_not_present("#vm-subVmTest1-disks-vdc-device")
        b.wait_not_present(".modal-dialog")

        # Test that detaching disk of a running domain will affect the
        # inactive configuration as well
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")
        b.wait_not_present("#vm-subVmTest1-disks-vdc-device")

        # Test detaching permanent disk of a stopped domain
        b.click("#vm-subVmTest1-disks") # open the "Disks" subtab
        b.wait_present("#vm-subVmTest1-disks-vdd-device")
        b.click("#delete-subVmTest1-disk-vdd")
        b.wait_present(".modal-dialog")
        b.click(".modal-footer button:contains(Remove)")
        b.wait_not_present("#vm-subVmTest1-disks-vdd-device")
        b.wait_not_present(".modal-dialog")

        # Test detaching disk of a paused domain
        m.execute("> {0}".format(args["logfile"])) # clear logfile
        m.execute("virsh start subVmTest1")
        # Make sure that the VM booted normally before attempting to suspend it
        wait(lambda: "Linux version" in m.execute("cat {0}".format(args["logfile"])), delay=3)
        m.execute("virsh suspend subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "paused")
        b.wait_attr("#delete-subVmTest1-disk-vde", "disabled", "")
        m.execute("virsh resume subVmTest1")
        wait(lambda: "login as 'cirros' user." in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

        # Test detaching of disk on non-persistent VM
        m.execute("virsh undefine subVmTest1")
        m.execute("virsh attach-disk --domain subVmTest1 --source {0}/mydiskofpoolone_1 --target vdc --targetbus virtio".format(p1))
        b.wait_present("#vm-subVmTest1-disks-vdc-device")
        b.click("#delete-subVmTest1-disk-vdc")
        b.wait_present(".modal-dialog")
        b.click(".modal-footer button:contains(Remove)")
        b.wait_not_present("#vm-subVmTest1-disks-vdc-device")
        b.wait_not_present(".modal-dialog")

    def testMultipleSettings(self):
        b = self.browser

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Change Boot Order setting
        bootOrder = b.text("#vm-subVmTest1-boot-order")
        b.click("#vm-subVmTest1-boot-order") # Open dialog
        b.wait_present(".modal-body")
        b.click("#vm-subVmTest1-order-modal-device-row-0 #vm-subVmTest1-order-modal-down") # Change order
        b.click("#vm-subVmTest1-order-modal-save") # Save
        b.wait_not_present(".modal-body")

        # Change vCPUs setting
        b.click("#vm-subVmTest1-vcpus-count") # Open dialog
        b.wait_present(".modal-body")
        b.set_input_text("#machines-vcpu-max-field", "3") # Change values
        b.set_input_text("#machines-vcpu-count-field", "3")
        b.click("#machines-vcpu-modal-dialog-apply") # Save
        b.wait_not_present(".modal-body")

        # Shut off domain
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # Check both changes have been applied
        b.wait_not_in_text("#vm-subVmTest1-boot-order", bootOrder)
        b.wait_in_text("#vm-subVmTest1-vcpus-count", "3")

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

        self.startVm("subVmTest1")

        # Create dummy network
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml && virsh net-start test_network".format(TEST_NETWORK_XML))

        # Create a second bridge to LAN NIC, virbr0 does not make sense but let's use it for test purposes
        m.execute("virsh attach-interface --persistent subVmTest1 bridge virbr0")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        self.toggleVmRow("subVmTest1")
        b.wait_in_text("#vm-subVmTest1-state", "running")

        # Make sure that the Networks are loaded into the global state
        b.wait_in_text("#card-pf-networks .card-pf-aggregate-status-count", "2")

        b.click("#vm-subVmTest1-networks") # open the "Networks" subtab

        # Wait for the edit button
        b.click("#vm-subVmTest1-network-1-edit-dialog")

        # Make sure the footer warning does not appear until we change something
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-idle-message")

        # Cancel dialog
        b.click("#vm-subVmTest1-network-1-edit-dialog-cancel")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-modal-window")

        # Fetch current NIC model type
        current_model_type = b.text("#vm-subVmTest1-network-1-model")

        # Reopen dialog modal
        b.click("#vm-subVmTest1-network-1-edit-dialog")

        # Change network model type of a running domain
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-model", "e1000e", substring=True)
        # Wait for the footer warning to appear
        b.wait_present("#vm-subVmTest1-network-1-edit-dialog-idle-message")
        # Change network type and source of a running domain
        b.wait_in_text("#vm-subVmTest1-network-1-select-type", "network")
        b.wait_in_text("#vm-subVmTest1-network-1-select-source", "default")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-source", "test_network")
        # Save the network settings
        b.click("#vm-subVmTest1-network-1-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-modal-window")
        # Wait for the tooltips to appear next to the elements we changed
        b.wait_in_text("#vm-subVmTest1-network-1-model", current_model_type)
        b.wait_present("#vm-subVmTest1-network-1-model-tooltip")
        b.wait_in_text("#vm-subVmTest1-network-1-type", 'network')
        b.wait_in_text("#vm-subVmTest1-network-1-source", 'default')
        b.wait_present("#vm-subVmTest1-network-1-source-tooltip")

        # Shut off domain and check changes are applied
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")
        b.wait_in_text("#vm-subVmTest1-network-1-model", "e1000e")
        b.wait_not_present("#vm-subVmTest1-network-1-model-tooltip")
        b.wait_in_text("#vm-subVmTest1-network-1-type", "network")
        b.wait_not_present("#vm-subVmTest1-network-1-type-tooltip")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "test_network")
        b.wait_not_present("#vm-subVmTest1-network-1-source-tooltip")

        # Remove the network interface
        m.execute("virsh detach-interface --persistent --type network --domain subVmTest1")

        # We don't get events for shut off VMs so reload the page
        b.reload()
        b.enter_page('/machines')
        b.wait_in_text("body", "Virtual Machines")
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-title-link", "Networks")
        b.wait_in_text("#card-pf-networks .card-pf-aggregate-status-count", "2")
        self.waitVmRow("subVmTest1")
        self.toggleVmRow("subVmTest1")
        b.click("#vm-subVmTest1-networks") # open the "Networks" subtab

        # Change network type and source from the bridge NIC
        b.wait_in_text("#vm-subVmTest1-network-1-type", "bridge")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "virbr0")

        # Change interface type to direct
        # Open the modal dialog
        b.click("#vm-subVmTest1-network-1-edit-dialog")

        b.wait_in_text("#vm-subVmTest1-network-1-select-type", "Bridge to LAN")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "Direct attachment")
        source = b.val("#vm-subVmTest1-network-1-select-source")

        # Save the network settings
        b.click("#vm-subVmTest1-network-1-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-modal-window")

        b.wait_in_text("#vm-subVmTest1-network-1-type", "direct")
        b.wait_in_text("#vm-subVmTest1-network-1-source", source)

        # Change interface type to bridge
        # Open the modal dialog
        b.click("#vm-subVmTest1-network-1-edit-dialog")

        b.wait_in_text("#vm-subVmTest1-network-1-select-type", "Direct attachment")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "Bridge to LAN")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-source", "virbr0")

        # Save the network settings
        b.click("#vm-subVmTest1-network-1-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-modal-window")

        b.wait_in_text("#vm-subVmTest1-network-1-type", "bridge")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "virbr0")

        b.click("#vm-subVmTest1-network-1-edit-dialog")
        b.wait_in_text("#vm-subVmTest1-network-1-select-source", "virbr0")
        b.click("#vm-subVmTest1-network-1-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-modal-window")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "virbr0")

        # Change interface type to network
        # Open the modal dialog
        b.click("#vm-subVmTest1-network-1-edit-dialog")

        b.wait_in_text("#vm-subVmTest1-network-1-select-type", "Direct attachment")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "Virtual network")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-source", "test_network")

        # Save the network settings
        b.click("#vm-subVmTest1-network-1-edit-dialog-save")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog-modal-window")

        b.wait_in_text("#vm-subVmTest1-network-1-type", "network")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "test_network")

        # Remove all Virtual Networks and confirm that trying to choose
        # Virtual Networks type for a NIC disables the save button
        m.execute("virsh net-dumpxml default > /tmp/net-default.xml")
        m.execute("virsh net-dumpxml test_network > /tmp/net-test-network.xml")
        m.execute("virsh net-destroy test_network && virsh net-destroy default")
        self.addCleanup(m.execute, "virsh net-define /tmp/net-default.xml && virsh net-autostart default")

        b.wait_in_text("#card-pf-networks .card-pf-aggregate-status-notification:nth-of-type(1)", "0")
        m.execute("virsh net-undefine test_network && virsh net-undefine default")
        b.wait_in_text("#card-pf-networks .card-pf-aggregate-status-notification:nth-of-type(2)", "0")
        b.wait_in_text("#card-pf-networks .card-pf-aggregate-status-count", "0")

        # Remove the network interface
        m.execute("virsh detach-interface --persistent --type network --domain subVmTest1")

        # Start the VM in order that the UI picks up the new interface, since we don't get events of shut off domains
        m.execute("virsh start subVmTest1")

        # Create a second bridge to LAN NIC
        m.execute("ip link add name br1 type bridge && virsh attach-interface --current subVmTest1 bridge br1")
        self.addCleanup(m.execute, "ip link delete br1")

        # Open the modal dialog
        b.click("#vm-subVmTest1-network-1-edit-dialog")

        # And ensure that the network sources dropdown is disabled
        b.wait_in_text("#vm-subVmTest1-network-1-select-type", "Bridge to LAN")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "Virtual network")
        b.wait_present("#vm-subVmTest1-network-1-edit-dialog-save:disabled")
        b.click(".modal-footer button:contains(Cancel)")

        # Ensure that when the source of a NIC was removed we can still change it

        # Redefine deleted networks and attach an interface with source a deleted network
        m.execute("virsh net-define /tmp/net-default.xml && virsh net-start default && virsh attach-interface --persistent --type network --source default --domain subVmTest1")
        m.execute("virsh net-destroy default && virsh net-undefine default")
        m.execute("virsh net-define /tmp/net-test-network.xml && virsh net-start test_network")

        # First shut of the VM otherwise the interface will not be the same in the live and config XML https://www.redhat.com/archives/libvir-list/2019-August/msg01034.html
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # Try to edit the interface changing the source to a non deleted network
        b.click("#vm-subVmTest1-network-1-edit-dialog")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-source", "test_network")
        b.click("#vm-subVmTest1-network-1-edit-dialog-save")
        b.wait_in_text("#vm-subVmTest1-network-1-source", "test_network")

        # Test detaching of disk on non-persistent VM
        m.execute("virsh undefine subVmTest1")
        b.wait_not_present("#vm-subVmTest1-network-1-edit-dialog")

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

        # Create dummy network
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml".format(TEST_NETWORK2_XML))

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

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        # Click on Networks card
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-title-link", "Network")
        b.click(".cards-pf .card-pf-title span:contains(Network)")

        # Check that all networks are there
        b.wait_in_text("body", "Networks")
        self.waitNetworkRow("test_network2", connectionName)

        # Expand row for first network
        self.toggleNetworkRow("test_network2", connectionName)

        b.wait_present("#network-test_network2-{0}-autostart-checkbox".format(connectionName))

        # set checkbox state and check state of checkbox
        b.set_checked("#network-test_network2-{0}-autostart-checkbox".format(connectionName), True) # don't know the initial state of checkbox, so set it to checked
        b.wait_present("#network-test_network2-{0}-autostart-checkbox:checked".format(connectionName))
        # check virsh state
        autostartState = m.execute("virsh net-info test_network2 | grep 'Autostart:' | awk '{print $2}'").strip()
        self.assertEqual(autostartState, "yes")

        # change checkbox state and check state of checkbox
        b.click("#network-test_network2-{0}-autostart-checkbox".format(connectionName))
        b.wait_present("#network-test_network2-{0}-autostart-checkbox:not(:checked)".format(connectionName))
        # check virsh state
        autostartState = m.execute("virsh net-info test_network2 | grep 'Autostart:' | awk '{print $2}'").strip()
        self.assertEqual(autostartState, "no")

        # change checkbox state and check state of checkbox
        b.click("#network-test_network2-{0}-autostart-checkbox".format(connectionName))
        b.wait_present("#network-test_network2-{0}-autostart-checkbox:checked".format(connectionName))
        # check virsh state
        autostartState = m.execute("virsh net-info test_network2 | grep 'Autostart:' | awk '{print $2}'").strip()
        self.assertEqual(autostartState, "yes")

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

        # Create dummy network
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml".format(TEST_NETWORK2_XML))

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

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        # Click on Networks card
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-title-link", "Network")
        b.click(".cards-pf .card-pf-title span:contains(Network)")

        # Check that all networks are there
        b.wait_in_text("body", "Networks")
        self.waitNetworkRow("test_network2", connectionName)

        # Expand row for first network
        self.toggleNetworkRow("test_network2", connectionName)

        # activate network
        b.wait_present("#activate-network-test_network2-{0}".format(connectionName))
        b.click("#activate-network-test_network2-{0}".format(connectionName))
        b.wait_in_text("#network-test_network2-{0}-state".format(connectionName), "active")
        # check virsh state
        wait(lambda: "yes" == m.execute("virsh net-info test_network2 | grep 'Active:' | awk '{print $2}'").strip(), tries=5)

        # deactivate network
        b.wait_present("#deactivate-network-test_network2-{0}".format(connectionName))
        b.click("#deactivate-network-test_network2-{0}".format(connectionName))
        b.wait_in_text("#network-test_network2-{0}-state".format(connectionName), "inactive")
        b.wait_present("#activate-network-test_network2-{0}".format(connectionName))
        # check virsh state
        wait(lambda: "no" == m.execute("virsh net-info test_network2 | grep 'Active:' | awk '{print $2}'").strip(), tries=5)

        # Delete an inactive network
        b.click('#delete-network-test_network2-{0}'.format(connectionName))
        b.click(".modal-footer button:contains(Delete)")
        self.waitNetworkRow("test_network2", connectionName, False)

        # Delete an active network
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml && virsh net-start test_network2".format(TEST_NETWORK2_XML))
        b.wait_in_text("#network-test_network2-{0}-state".format(connectionName), "active")
        self.toggleNetworkRow("test_network2", connectionName)
        b.click('#delete-network-test_network2-{0}'.format(connectionName))
        b.click(".modal-footer button:contains(Delete)")
        self.waitNetworkRow("test_network2", connectionName, False)

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

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

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-title-link", "Network")
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-aggregate-status-count", "1")

        # Create dummy network
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml".format(TEST_NETWORK2_XML))
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-title-link", "Networks")
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-aggregate-status-count", "2")
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml".format(TEST_NETWORK3_XML))
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-create /tmp/xml".format(TEST_NETWORK4_XML))

        # Click on Networks card
        b.click(".cards-pf .card-pf-title span:contains(Networks)")

        # Check that all networks are there
        b.wait_in_text("body", "Networks")
        self.waitNetworkRow("test_network2", connectionName)
        self.waitNetworkRow("test_network3", connectionName)

        # Check headers of networks
        b.wait_in_text("#network-test_network2-{0}-name".format(connectionName), "test_network2")
        b.wait_in_text("#network-test_network2-{0}-device".format(connectionName), "virbr1")
        b.wait_in_text("#network-test_network2-{0}-forwarding".format(connectionName), "None (Isolated Network)")
        b.wait_in_text("#network-test_network3-{0}-name".format(connectionName), "test_network3")
        b.wait_in_text("#network-test_network3-{0}-device".format(connectionName), "br0")
        b.wait_in_text("#network-test_network3-{0}-forwarding".format(connectionName), "Bridge")

        # Expand row for first network
        self.toggleNetworkRow("test_network2", connectionName)

        # Check overview network properties are present
        b.wait_in_text("#network-test_network2-{0}-persistent".format(connectionName), "yes")
        b.wait_present("#network-test_network2-{0}-autostart-checkbox:not(:checked)".format(connectionName))
        b.wait_in_text("#network-test_network2-{0}-ipv4-address".format(connectionName), "192.168.100.1")
        b.wait_in_text("#network-test_network2-{0}-ipv4-netmask".format(connectionName), "255.255.255.0")
        b.wait_in_text("#network-test_network2-{0}-ipv4-dhcp-range".format(connectionName), "192.168.100.128 - 192.168.100.170")
        b.wait_in_text("#network-test_network2-{0}-ipv4-dhcp-host-0".format(connectionName), "Name: paul, MAC: 00:16:3E:5D:C7:9E, IP: 192.168.122.254")
        b.wait_in_text("#network-test_network2-{0}-ipv6-address".format(connectionName), "fd00:e81d:a6d7:55::1")
        b.wait_in_text("#network-test_network2-{0}-ipv6-prefix".format(connectionName), "64")
        b.wait_in_text("#network-test_network2-{0}-ipv6-dhcp-range".format(connectionName), "fd00:e81d:a6d7:55::100 - fd00:e81d:a6d7:55::1ff")
        b.wait_in_text("#network-test_network2-{0}-ipv6-dhcp-host-0".format(connectionName), "Name: simon, IP: 2001:db8:ca2:2:3::1")
        b.wait_in_text("#network-test_network2-{0}-ipv6-dhcp-host-1".format(connectionName), "ID: 0:1:0:1:18:aa:62:fe:0:16:3e:44:55:66, IP: 2001:db8:ca2:2:3::2")

        # Close expanded row for this pool
        self.toggleNetworkRow("test_network2", connectionName)

        # Expand row for second network
        self.toggleNetworkRow("test_network3", connectionName)

        # Check overview network properties are present
        b.wait_in_text("#network-test_network3-{0}-persistent".format(connectionName), "yes")
        b.wait_present("#network-test_network3-{0}-autostart-checkbox:not(:checked)".format(connectionName))

        # Check overview network properties are not present
        b.wait_not_present("#network-test_network3-{0}-ipv4-address".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv4-netmask".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv4-dhcp-range".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv4-dhcp-host-0".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv6-address".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv6-prefix".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv6-dhcp-range".format(connectionName))
        b.wait_not_present("#network-test_network3-{0}-ipv6-dhcp-host-0".format(connectionName))

        # Transient network
        self.toggleNetworkRow("test_network4", connectionName)
        b.wait_in_text("#network-test_network4-{0}-persistent".format(connectionName), "no")
        b.wait_not_present("#network-test_network4-{0}-autostart-checkbox".format(connectionName)) # Transient network shouldn't have autostart option
        b.wait_present('#delete-network-test_network4-{0}:disabled'.format(connectionName)) # Transient network cannot be deleted
        b.click('#deactivate-network-test_network4-{0}'.format(connectionName)) # Deactivate transient network
        self.waitNetworkRow("test_network4", connectionName, False) # Check it's not present after deactivation

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

        self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")
        self.waitVmRow("subVmTest1")

        b.wait_in_text("#card-pf-storage-pools .card-pf-aggregate-status-count", "1")
        b.wait_in_text(".cards-pf #card-pf-storage-pools .card-pf-title-link", "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("mkdir --mode 777 {0} {1} {2}".format(p1, p2, p3))
        m.execute("virsh pool-define-as myPoolOne --type dir --target {0}; virsh pool-start myPoolOne".format(p1))
        m.execute("virsh pool-define-as myPoolTwo --type dir --target {0}; virsh pool-start myPoolTwo".format(p2))
        m.execute("virsh pool-create-as myPoolThree --type dir --target {0}".format(p3)) # Transient pool

        b.wait_in_text("#card-pf-storage-pools .card-pf-aggregate-status-count", "3")
        b.wait_in_text(".cards-pf #card-pf-storage-pools .card-pf-title-link", "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")
        wait(lambda: all(volume in m.execute("virsh vol-list myPoolTwo") for volume in ["VolumeOne", "VolumeTwo", "VolumeThree"]))
        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("echo {0} > /tmp/disk.xml; virsh attach-device --config --file /tmp/disk.xml subVmTest1".format(diskXML))

        # Shuf off the VM in order to allow deleting volumes that are used as
        # disks later
        self.toggleVmRow("subVmTest1")
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        # Click on Storage Pools card
        b.click(".cards-pf .card-pf-title span: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)

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

        # Check storage volumes of a pool
        b.wait_present("#pool-myPoolOne-{0}-storage-volumes".format(connectionName))
        b.click("#pool-myPoolOne-{0}-storage-volumes".format(connectionName)) # open the "Storage Volumes" subtab
        b.wait_in_text("tr[data-row-id=pool-myPoolOne-{0}] + tr table td".format(connectionName),
                       "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)
        b.wait_present("#pool-myPoolTwo-{0}-storage-volumes".format(connectionName))
        b.click("#pool-myPoolTwo-{0}-storage-volumes".format(connectionName)) # open the "Storage Volumes" subtab
        b.wait_present("#pool-myPoolTwo-{0}-volume-VolumeOne-name".format(connectionName))
        b.wait_in_text("#pool-myPoolTwo-{0}-volume-VolumeOne-name".format(connectionName), "VolumeOne")
        b.wait_in_text("#pool-myPoolTwo-{0}-volume-VolumeTwo-name".format(connectionName), "VolumeTwo")
        b.wait_in_text("#pool-myPoolTwo-{0}-volume-VolumeThree-name".format(connectionName), "VolumeThree")
        b.wait_not_present("#storage-volumes-delete")

        # Delete a volume from terminal and verify that refresh worked by reloading the browser page
        m.execute("rm -f {0}".format(os.path.join(p2, "VolumeThree")))
        b.wait_present("#pool-myPoolTwo-{0}-volume-VolumeThree-name".format(connectionName))
        b.reload()
        b.enter_page('/machines')
        self.togglePoolRow("myPoolTwo", connectionName)
        b.click("#pool-myPoolTwo-{0}-storage-volumes".format(connectionName)) # open the "Storage Volumes" subtab
        b.wait_not_present("#pool-myPoolTwo-{0}-volume-VolumeThree-name".format(connectionName))

        # Delete Storage Volume that is not used by any VM
        b.wait_present("#pool-myPoolTwo-{0}-volume-VolumeTwo-name".format(connectionName))
        b.click("tbody input[aria-labelledby=simple-node1]")
        b.wait_in_text("#storage-volumes-delete", "Delete 1 volume")
        b.click("#storage-volumes-delete")
        b.wait_not_present("#pool-myPoolTwo-{0}-volume-VolumeTwo-name".format(connectionName))

        # Try to Delete Storage Volume which is used by a VM
        b.wait_present("#pool-myPoolTwo-{0}-volume-VolumeOne-name".format(connectionName))
        b.click("tbody input[aria-labelledby=simple-node0]")
        b.wait_present("#storage-volumes-delete:disabled")
        b.wait_in_text("#storage-volumes-delete", "Delete 1 volume")

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

        # Try deactivating and activating a pool
        b.click("#deactivate-pool-myPoolOne-{0}".format(connectionName))
        b.wait_in_text("#pool-myPoolOne-{0}-state".format(connectionName), "inactive")
        b.click("#activate-pool-myPoolOne-{0}".format(connectionName))
        b.wait_in_text("#pool-myPoolOne-{0}-state".format(connectionName), "active")

        # See deletion of Pool is disabled because this pool is referenced by VM's disk
        b.wait_present("#delete-pool-myPoolTwo-{0}:disabled".format(connectionName))

        # Detach disk so pool can be deleted
        m.execute("virsh detach-disk --domain subVmTest1 --target vdc --config")
        b.reload()
        b.enter_page('/machines')
        self.togglePoolRow("myPoolTwo", connectionName)
        b.click("#pool-myPoolTwo-{0}-storage-volumes".format(connectionName)) # open the "Storage Volumes" subtab

        # Backup pool XML to redefine right after
        m.execute("virsh pool-dumpxml myPoolTwo > /tmp/myPoolTwo.xml")

        b.click("#deactivate-pool-myPoolTwo-{0}".format(connectionName))

        # Delete an inactive Pool. It's volumes won't be deleted
        b.wait_present("#delete-pool-myPoolTwo-{0}".format(connectionName))
        b.click("#delete-pool-myPoolTwo-{0}".format(connectionName))
        b.wait_in_text("div.modal-dialog div.modal-body div.ct-form", "Its content will not be deleted.")
        b.click('div.modal-footer button:contains("Delete")')
        b.wait_not_present("div.modal-dialog")
        b.wait_not_present("#pool-myPoolTwo-{0}-storage-volumes-list".format(connectionName))
        self.assertNotEqual(m.execute("ls -A {0}".format(p2)), "")

        # Redefine the deleted Pool
        m.execute("virsh pool-define /tmp/myPoolTwo.xml")
        self.waitPoolRow("myPoolTwo", connectionName)

        # Activate the Pool
        self.togglePoolRow("myPoolTwo", connectionName)
        b.click("#activate-pool-myPoolTwo-{0}".format(connectionName))

        # Delete and active Pool and also its volumes
        b.wait_present("#delete-pool-myPoolTwo-{0}".format(connectionName))
        b.click("#delete-pool-myPoolTwo-{0}".format(connectionName))
        b.wait_present("div.modal-dialog")
        b.set_checked("input#storage-pool-delete-volumes", True)
        b.click('div.modal-footer button:contains("Delete")')
        b.wait_not_present("div.modal-dialog")
        b.wait_not_present("#pool-myPoolTwo-{0}-storage-volumes-list".format(connectionName))
        self.assertEqual(m.execute("ls -A {0}".format(p2)), "")
        self.waitPoolRow("myPoolTwo", connectionName, False)

        # Recreate the myPoolTwo to test Used By column
        m.execute("virsh pool-define-as myPoolTwo --type dir --target {0}; virsh pool-start myPoolTwo".format(p2))
        m.execute("virsh vol-create-as myPoolTwo VolumeOne --capacity 1G --format qcow2 && virsh pool-refresh myPoolTwo")

        wait(lambda: "VolumeOne" in m.execute("virsh vol-list myPoolTwo"))
        diskXML = """'<disk type="volume" device="disk">
          <driver name="qemu"/>
          <source pool="myPoolTwo" volume="VolumeOne"/>
          <target dev="vdd" bus="virtio"/>
        </disk>'""".replace("\n", "")

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

        # 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)
        b.wait_present("#pool-myPoolTwo-{0}-storage-volumes".format(connectionName))
        b.click("#pool-myPoolTwo-{0}-storage-volumes".format(connectionName)) # open the "Storage Volumes" subtab

        b.wait_present("#pool-myPoolTwo-{0}-volume-VolumeOne-usedby".format(connectionName))
        b.wait_in_text("#pool-myPoolTwo-{0}-volume-VolumeOne-usedby".format(connectionName), "subVmTest1")

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

        # Check activate button when pool activation is pending
        m.execute("virsh pool-define-as mypool --type netfs --source-host 127.0.0.10 --source-path /mnt/pool --target /mnt/pool")
        self.togglePoolRow("mypool", connectionName)
        b.click("#activate-pool-mypool-system")
        b.wait_present("#activate-pool-mypool-system:disabled")

        # Transient network
        self.togglePoolRow("myPoolThree", connectionName)
        b.wait_not_present("#pool-myPoolThree-{0}-autostart".format(connectionName)) # Transient pool shouldn't have autostart option
        b.wait_present('#delete-pool-myPoolThree-{0}:disabled'.format(connectionName)) # Transient pool cannot be deleted
        b.click('#deactivate-pool-myPoolThree-{0}'.format(connectionName)) # Deactivate transient pool
        # Check it's not present after deactivation
        self.waitPoolRow("myPoolThree", connectionName, False)

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

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        # Click on Networks card
        b.wait_in_text(".cards-pf #card-pf-networks .card-pf-title-link", "Network")
        b.click(".cards-pf .card-pf-title span:contains(Network)")

        class NetworkCreateDialog(object):
            def __init__(
                self, test_obj, name, forward_mode=None, ip_conf=None, ipv4_address=None, ipv4_netmask=None, ipv6_address=None, ipv6_prefix=None, device=None,
                ipv4_dhcp_start=None, ipv4_dhcp_end=None, ipv6_dhcp_start=None, ipv6_dhcp_end=None, xfail=False, xfail_error=None, xfail_objects=None,
                remove=True, activate=False
            ):
                self.test_obj = test_obj
                self.name = name
                self.forward_mode = forward_mode
                self.device = device
                self.ip_conf = ip_conf
                self.ipv4_address = ipv4_address
                self.ipv4_netmask = ipv4_netmask
                self.ipv6_address = ipv6_address
                self.ipv6_prefix = ipv6_prefix
                self.ipv4_dhcp_start = ipv4_dhcp_start
                self.ipv4_dhcp_end = ipv4_dhcp_end
                self.ipv6_dhcp_start = ipv6_dhcp_start
                self.ipv6_dhcp_end = ipv6_dhcp_end
                self.xfail = xfail
                self.xfail_objects = xfail_objects
                self.xfail_error = xfail_error
                self.remove = remove
                self.activate = activate

            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-network")
                b.wait_present("#create-network-dialog")
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Create Virtual Network")

            def fill(self):
                b.set_input_text("#create-network-name", self.name)

                if self.forward_mode:
                    b.set_val("#create-network-forward-mode", self.forward_mode)

                if self.device:
                    b.select_from_dropdown("#create-network-device", self.device)

                if self.ip_conf:
                    b.select_from_dropdown("#create-network-ip-configuration", self.ip_conf)

                    if "4" in self.ip_conf:
                        b.set_input_text("#network-ipv4-address", self.ipv4_address)
                        b.set_input_text("#network-ipv4-netmask", self.ipv4_netmask)
                        if self.ipv4_dhcp_start is not None and self.ipv4_dhcp_end is not None:
                            b.set_checked("#network-ipv4-dhcp", True)
                            b.set_input_text("#network-ipv4-dhcp-range-start", self.ipv4_dhcp_start)
                            b.set_input_text("#network-ipv4-dhcp-range-end", self.ipv4_dhcp_end)

                    if "6" in self.ip_conf:
                        b.set_input_text("#network-ipv6-address", self.ipv6_address)
                        b.set_input_text("#network-ipv6-prefix", self.ipv6_prefix)
                        if self.ipv6_dhcp_start is not None and self.ipv6_dhcp_end is not None:
                            b.set_checked("#network-ipv6-dhcp", True)
                            b.set_input_text("#network-ipv6-dhcp-range-start", self.ipv6_dhcp_start)
                            b.set_input_text("#network-ipv6-dhcp-range-end", self.ipv6_dhcp_end)

            def cancel(self):
                b.click(".modal-footer button:contains(Cancel)")
                b.wait_not_present("#create-network-dialog")

            def create(self):
                b.click(".modal-footer button:contains(Create)")

                if (self.xfail):
                    # Check incomplete dialog
                    if "name" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #create-network-name + span p", self.xfail_error)
                    if "ipv4_address" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv4-address + span p", self.xfail_error)
                    if "ipv4_netmask" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv4-netmask + span p", self.xfail_error)
                    if "ipv4_dhcp_start" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv4-dhcp-range-start + span p", self.xfail_error)
                    if "ipv4_dhcp_end" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv4-dhcp-range-end + span p", self.xfail_error)
                    if "ipv6_address" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv6-address + span p", self.xfail_error)
                    if "ipv6_prefix" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv6-prefix + span p", self.xfail_error)
                    if "ipv6_dhcp_start" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv6-dhcp-range-start + span p", self.xfail_error)
                    if "ipv6_dhcp_end" in self.xfail_objects:
                        b.wait_in_text("#create-network-dialog .modal-body form div.has-error #network-ipv6-dhcp-range-end + span p", self.xfail_error)
                    if "footer" in self.xfail_objects:
                        error_location = "#create-network-dialog .modal-footer .pf-m-danger"
                        b.wait_present(error_location)
                        error_message = b.text(error_location)
                        self.test_obj.assertIn(self.xfail_error, error_message)

                    self.cancel()
                else:
                    b.wait_not_present("#create-network-dialog")

            def verify_expected_error_on_head(self, error_message):
                b.wait_present("#app > .toast-notification-wrapper .pf-m-danger")
                # Click show more button
                b.click("#app > .toast-notification-wrapper .pf-m-danger .more-button")
                b.wait_in_text("#app > .toast-notification-wrapper div.pf-c-alert__description > p", error_message)
                # Close the danger alert
                b.click("#app > .toast-notification-wrapper div.pf-c-alert__action > button")
                b.wait_not_present("#app > .toast-notification-wrapper .pf-m-danger")

            def verify_dialog(self):
                # Check that the defined network is now visible
                b.wait_in_text("body", "Networks")

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

                self.test_obj.assertEqual(self.name, m.execute(xmllint_element.format(prop='name')).strip())
                if (self.forward_mode == "none"):
                    self.test_obj.assertEqual("", m.execute(xmllint_element.format(prop='forward/@mode')).strip())
                else:
                    self.test_obj.assertEqual(self.forward_mode, m.execute(xmllint_element.format(prop='forward/@mode')).strip())

                if self.device:
                    self.test_obj.assertEqual(self.device, m.execute(xmllint_element.format(prop='forward/interface/@dev')).strip())

                if (self.ip_conf != "None"):
                    if "4" in self.ip_conf:
                        self.test_obj.assertEqual(self.ipv4_address, m.execute(xmllint_element.format(prop='ip/@address')).strip())
                        self.test_obj.assertEqual(self.ipv4_netmask, m.execute(xmllint_element.format(prop='ip/@netmask')).strip())
                        if self.ipv4_dhcp_start and self.ipv4_dhcp_start:
                            self.test_obj.assertEqual(self.ipv4_dhcp_start, m.execute(xmllint_element.format(prop='ip/dhcp/range/@start')).strip())
                            self.test_obj.assertEqual(self.ipv4_dhcp_end, m.execute(xmllint_element.format(prop='ip/dhcp/range/@end')).strip())
                    if "6" in self.ip_conf:
                        self.test_obj.assertEqual(self.ipv6_address, m.execute(xmllint_element.format(prop='ip[starts-with(@family,"ipv6")]/@address')).strip())
                        self.test_obj.assertEqual(self.ipv6_prefix, m.execute(xmllint_element.format(prop='ip[starts-with(@family,"ipv6")]/@prefix')).strip())
                        if self.ipv6_dhcp_start and self.ipv6_dhcp_start:
                            self.test_obj.assertEqual(self.ipv6_dhcp_start, m.execute(xmllint_element.format(prop='ip[starts-with(@family,"ipv6")]/dhcp/range/@start')).strip())
                            self.test_obj.assertEqual(self.ipv6_dhcp_end, m.execute(xmllint_element.format(prop='ip[starts-with(@family,"ipv6")]/dhcp/range/@end')).strip())
                else:
                    self.test_obj.assertEqual("", m.execute(xmllint_element.format(prop='ip')).strip())

            def verify_overview(self):
                # Check basic network properties
                modes = {"nat": "NAT", "none": "None (Isolated Network)", "open": "Open", "route": "Routed", \
                "bridge": "Bridge", "private": "Private", "vepa": "VEPA", "passthrough": "Passthrough", "hostdev": "Hostdev"}
                connectionName = m.execute("virsh uri | head -1 | cut -d/ -f4").strip()

                b.wait_in_text("#network-{0}-{1}-forwarding".format(self.name, connectionName), modes[self.forward_mode])
                self.test_obj.toggleNetworkRow(self.name, connectionName)

                if self.activate:
                    b.click("#activate-network-{}-{}".format(self.name, connectionName))
                    # For checking danger alter, only set xfail_objects, and don't set xfail
                    if self.xfail_objects and 'danger_alert' in self.xfail_objects:
                        self.verify_expected_error_on_head(self.xfail_error)

                if self.ip_conf != "None":
                    if "4" in self.ip_conf:
                        b.wait_in_text("#network-{0}-{1}-ipv4-address".format(self.name, connectionName), self.ipv4_address)
                        b.wait_in_text("#network-{0}-{1}-ipv4-netmask".format(self.name, connectionName), self.ipv4_netmask)
                        if self.ipv4_dhcp_start and self.ipv4_dhcp_start:
                            b.wait_in_text("#network-{0}-{1}-ipv4-dhcp-range".format(self.name, connectionName), self.ipv4_dhcp_start + " - " + self.ipv4_dhcp_end)
                    if "6" in self.ip_conf:
                        b.wait_in_text("#network-{0}-{1}-ipv6-address".format(self.name, connectionName), self.ipv6_address)
                        b.wait_in_text("#network-{0}-{1}-ipv6-prefix".format(self.name, connectionName), self.ipv6_prefix)
                        if self.ipv6_dhcp_start and self.ipv6_dhcp_start:
                            b.wait_in_text("#network-{0}-{1}-ipv6-dhcp-range".format(self.name, connectionName), self.ipv6_dhcp_start + " - " + self.ipv6_dhcp_end)

                    if not "4" in self.ip_conf:
                        b.wait_not_present("#network-{0}-{1}-ipv4-address".format(self.name, connectionName))
                    if not "6" in self.ip_conf:
                        b.wait_not_present("#network-{0}-{1}-ipv6-address".format(self.name, connectionName))
                else:
                    b.wait_not_present("#network-{0}-{1}-ipv4-address".format(self.name, connectionName))
                    b.wait_not_present("#network-{0}-{1}-ipv6-address".format(self.name, connectionName))

            def cleanup(self):
                if self.activate and not self.xfail_objects:
                    m.execute("virsh net-destroy {}".format(self.name))
                m.execute("virsh net-undefine {0}".format(self.name))


        # Test various forward Modes
        duplicated_net = NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="nat",
            ip_conf="IPv4 only",
            ipv4_address="192.168.110.0",
            ipv4_netmask="255.255.255.0",
            remove=False,
            activate=True,
            )
        duplicated_net.execute()

        # XFail: Activate a network which has a same ipv4 address with last one
        NetworkCreateDialog(
            self,
            name="test_network_duplication",
            forward_mode="nat",
            ip_conf="IPv4 only",
            ipv4_address="192.168.110.0",
            ipv4_netmask="255.255.255.0",
            activate=True,
            xfail_objects='danger_alert',
            xfail_error="Network is already in use",
        ).execute()
        duplicated_net.cleanup()

        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="open",
            ip_conf="IPv6 only",
            ipv6_address="fd00:e81d:a6d7:55::100",
            ipv6_prefix="64",
        ).execute()

        # Compressed IPv6 addresses should work as well
        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="open",
            ip_conf="IPv6 only",
            ipv6_address="fec0::1",
            ipv6_prefix="48",
            ipv6_dhcp_start="fec0::1",
            ipv6_dhcp_end="fec0::10",
        ).execute()

        tmp = NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="none",
            ip_conf="None",
            remove=False,
        )
        tmp.execute()

        # Try footer error
        NetworkCreateDialog(
            self,
            name="test_network",
            xfail=True,
            xfail_objects=["footer"],
            xfail_error="network 'test_network' already exists",
        ).execute()

        tmp.cleanup()

        # Test full configuration
        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="nat",
            ip_conf="IPv4 and IPv6",
            ipv4_address="192.168.110.0",
            ipv4_netmask="255.255.255.0",
            ipv4_dhcp_start="192.168.110.130",
            ipv4_dhcp_end="192.168.110.170",
            ipv6_address="fd00:e81d:a6d7:55::100",
            ipv6_prefix="64",
            ipv6_dhcp_start="fd00:e81d:a6d7:55::105",
            ipv6_dhcp_end="fd00:e81d:a6d7:55::108",
        ).execute()

        # Check "... should not be empty" warnings
        NetworkCreateDialog(
            self,
            name="",
            forward_mode="open",
            ip_conf="IPv4 and IPv6",
            ipv4_address="",
            ipv4_netmask="",
            ipv4_dhcp_start="",
            ipv4_dhcp_end="",
            ipv6_address="",
            ipv6_prefix="",
            ipv6_dhcp_start="",
            ipv6_dhcp_end="",
            xfail=True,
            xfail_objects = ["name", "ipv4_address", "ipv4_netmask", "ipv4_dhcp_start", "ipv4_dhcp_end", \
            "ipv6_address", "ipv6_prefix", "ipv6_dhcp_start", "ipv6_dhcp_end"],
            xfail_error = "should not be empty",
        ).execute()

        # Check "Invalid..." (invalid IP format or prefix length)
        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="open",
            ip_conf="IPv4 and IPv6",
            ipv4_address="ABC.168.22.10",
            ipv4_netmask="99",
            ipv4_dhcp_start="300.2.1.1",
            ipv4_dhcp_end="168..1.1.1",
            ipv6_address="xz00:e81d:a6d7:55::100",
            ipv6_prefix="-1",
            ipv6_dhcp_start="fd00:e81d:a6d7:55:::100",
            ipv6_dhcp_end="fd00:e81d:a6d7:55::1:1:1:1:1:1:1:1",
            xfail=True,
            xfail_objects = ["ipv4_address", "ipv4_netmask", "ipv4_dhcp_start", "ipv4_dhcp_end", \
            "ipv6_address", "ipv6_prefix", "ipv6_dhcp_start", "ipv6_dhcp_end"],
            xfail_error = "Invalid",
        ).execute()

        # Check "Address not withing subnet"
        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="open",
            ip_conf="IPv4 and IPv6",
            ipv4_address="192.168.100.1",
            ipv4_netmask="64",
            ipv4_dhcp_start="192.168.101.1",
            ipv4_dhcp_end="191.168.100.1",
            ipv6_address="fd00:e81d:a6d7:55::100",
            ipv6_prefix="64",
            ipv6_dhcp_start="fd00:e81d:a6d7:54::100",
            ipv6_dhcp_end="ad00:e81d:a6d7:55::100",
            xfail=True,
            xfail_objects = ["ipv4_dhcp_start", "ipv4_dhcp_end", "ipv6_dhcp_start", "ipv6_dhcp_end"],
            xfail_error = "Address not within subnet",
        ).execute()

        # Test network devices
        device = getNetworkDevice(m)
        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="nat",
            device=device,
            ip_conf="IPv4 only",
            ipv4_address="192.168.110.0",
            ipv4_netmask="255.255.255.0",
        ).execute()

    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("mkdir {0} {1} {2}".format(p1, p2, mnt_exports))
        self.restore_file("/etc/exports")
        m.execute("echo '{0} 127.0.0.1/24(rw,sync,no_root_squash,no_subtree_check)' > /etc/exports".format(mnt_exports))
        m.execute("systemctl restart nfs-server")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        # Click on Storage Pools card
        b.wait_in_text(".cards-pf #card-pf-storage-pools .card-pf-title-link", "Storage Pools")
        b.click(".cards-pf .card-pf-title span:contains(Storage Pools)")

        class StoragePoolCreateDialog(object):
            def __init__(
                self, test_obj, name, pool_type=None, target=None, source={},
                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
                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_present("#create-storage-pool-dialog")
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Create Storage Pool")

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

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

                if self.target:
                    b.set_file_autocomplete_val("storage-pool-dialog-target", 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", 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(".modal-footer button:contains(Cancel)")
                b.wait_not_present("#create-storage-pool-dialog")

            def create(self):
                b.click(".modal-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_present("#create-storage-pool-dialog .modal-body form div.has-error #storage-pool-dialog-name")

                    if (not self.target):
                        b.wait_present("#create-storage-pool-dialog .modal-body label:contains(Target) + div.has-error")
                    # Check errors from backend
                    if self.xfail_error:
                        error_location = "#create-storage-pool-dialog .modal-footer .pf-m-danger"
                        b.wait_present(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 = "virsh -c qemu:///system pool-dumpxml {0}".format(self.name)
                xmllint_element = "{0} | xmllint --xpath 'string(//pool/{{prop}})' - 2>&1 || true".format(pool_xml)

                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("#pool-{0}-{1}-target-path".format(self.name, connectionName), self.target[:-1])
                b.wait_in_text("#pool-{0}-{1}-type".format(self.name, connectionName), self.pool_type)
                if "host" in self.source:
                    b.wait_in_text("#pool-{0}-{1}-host".format(self.name, connectionName), self.source["host"])
                if "source_path" in self.source:
                    b.wait_in_text("#pool-{0}-{1}-source-path".format(self.name, connectionName), self.source["source_path"])

                # Check on the volume tab of the storage pool
                b.click("#pool-{}-{}-storage-volumes".format(self.name, connectionName))
                if "iscsi" in self.pool_type:
                    b.wait_present('#{}-{}-create-volume-button:disabled'.format(self.name, connectionName))
                else:
                    b.wait_present('#{}-{}-create-volume-button:enabled'.format(self.name, connectionName))

            def cleanup(self):
                m.execute("virsh pool-undefine {0}".format(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()
        m.execute("parted %s mklabel gpt" % dev)

        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
        # Ubuntu 19.10 is affected by https://bugzilla.redhat.com/show_bug.cgi?id=1659195
        if "debian" not in m.image and m.image != "ubuntu-stable":
            # 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-path/",
                source={"host": "127.0.0.1", "source_path": target_iqn}
            ).execute()

            if "debian" not in m.image and "ubuntu" not in m.image and float(m.execute("virsh --version").strip()[:3]) >= 4.7:
                # iscsi-direct pool type is available since libvirt 4.7, but not 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_present("#create-storage-pool-dialog")
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Create Storage Pool")
                b.click("#storage-pool-dialog-type")
                b.wait_not_present("#storage-pool-dialog-type option[value*='isci-direct']")
                b.click(".modal-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("mkdir --mode 777 {0}".format(dir_pool))
        m.execute("virsh pool-define-as dir-pool --type dir --target {0} && virsh pool-start dir-pool".format(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("mkdir {0} {1} && echo '{1} 127.0.0.1/24(rw,sync,no_root_squash,no_subtree_check,fsid=0)' > /etc/exports".format(nfs_pool, mnt_exports))
        m.execute("systemctl restart nfs-server")
        m.execute("virsh pool-define-as nfs-pool --type netfs --target {0} --source-host 127.0.0.1 --source-path {1} && virsh pool-start nfs-pool".format(nfs_pool, mnt_exports))
        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()
        cmds = [
            "virsh pool-define-as disk-pool disk - - %s - /tmp/poolDiskImages" % dev,
            "virsh pool-build disk-pool --overwrite",
            "virsh pool-start disk-pool",
        ]
        self.machine.execute(" && ".join(cmds))

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        # Click on Storage Pools card
        b.wait_in_text(".cards-pf #card-pf-storage-pools .card-pf-title-link", "Storage Pools")
        b.click(".cards-pf .card-pf-title span:contains(Storage Pools)")

        class StorageVolumeCreateDialog(object):
            def __init__(
                self, test_obj, pool_name, vol_name, size="128", unit="MiB", format='qcow2',
                pool_type = "dir", xfail=False, 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.format = format
                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()
                self.close()

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

                b.click("#pool-{0}-system-storage-volumes".format(self.pool_name)) # open the "Storage Volumes" subtab

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

                b.wait_present("#create-volume-dialog-modal")
                b.wait_in_text(".modal-dialog .modal-header .modal-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.format:
                    b.select_from_dropdown("#create-volume-dialog-format", self.format)

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

            def create(self):
                b.click(".modal-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_present(".modal-footer div.spinner")
                        b.wait_present(".modal-footer button:contains(Create):disabled")
                    b.wait_not_present("#create-volume-dialog-modal")

            def verify(self):
                # Check that the defined volume is now visible
                b.wait_present("#pool-{0}-system-volume-{1}-name".format(self.pool_name, self.vol_name))

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

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

                if (self.format):
                    self.test_obj.assertEqual(self.format, 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):
                b.click("#pool-{0}-system-overview".format(self.pool_name)) # open the "Storage Volumes" subtab

                self.test_obj.togglePoolRow(self.pool_name)

        # Check volume creation for various pool types
        StorageVolumeCreateDialog(
            self,
            pool_name="dir-pool",
            vol_name="volume_of_dir_pool",
            size="256",
            unit="MiB",
            format="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",
            format="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="sda1", # Partition names must follow pattern of sda1, sda2...
            size="10", # Only 50MiB availabl on disk-pool
            unit="MiB",
            format="none",
            remove=True,
        ).execute()

        # Try raw format
        StorageVolumeCreateDialog(
            self,
            pool_name="dir-pool",
            vol_name="volume_of_dir_pool2",
            format="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="20",
            format="",
            remove=True,
        ).execute()

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

        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml && virsh net-start test_network".format(TEST_NETWORK_XML))

        args = self.startVm("subVmTest1")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual Machines")

        self.toggleVmRow("subVmTest1")
        b.click("#vm-subVmTest1-networks") # open the Network Interfaces subtab

        # Shut off domain
        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        class NICAddDialog(object):
            def __init__(
                # We have always have to specify mac and source_type to identify the device in xml and $virsh detach-interface
                self, test_obj, source_type="Direct attachment", source=None, model=None, nic_num=2,
                permanent=False, mac="52:54:00:a5:f8:c0", remove=True, persistent_vm=True
            ):
                self.test_obj = test_obj
                self.source_type = source_type
                self.source = source
                self.model = model
                self.permanent = permanent
                self.mac = mac
                self.remove = remove
                self.persistent_vm = persistent_vm
                self.nic_num = nic_num

            def execute(self):
                self.open()
                self.fill()
                self.create()
                self.verify()
                self.verify_overview()
                if self.remove:
                    self.cleanup()

            def open(self):
                b.click("#vm-subVmTest1-add-iface-button") # open the Network Interfaces subtab
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Add Virtual Network Interface")

            def fill(self):
                b.select_from_dropdown("#vm-subVmTest1-add-iface-select-type", self.source_type)
                if self.source:
                    b.select_from_dropdown("#vm-subVmTest1-add-iface-select-source", self.source)
                if self.model:
                    b.select_from_dropdown("#vm-subVmTest1-add-iface-select-model", self.model, substring=True)

                if self.mac:
                    b.click("#vm-subVmTest1-add-iface-set-mac")
                    b.set_input_text("#vm-subVmTest1-add-iface-mac", self.mac)

                if self.permanent:
                    b.click("#vm-subVmTest1-add-iface-permanent")

                if not self.persistent_vm:
                    b.wait_not_present("#vm-subVmTest1-add-iface-permanent")

            def cancel(self):
                b.click(".modal-footer button:contains(Cancel)")
                b.wait_not_present("#vm-subVmTest1-add-iface-dialog")

            def create(self):
                b.click(".modal-footer button:contains(Add)")

                b.wait_not_present("#vm-subVmTest1-add-iface-dialog")

            def verify(self):
                # Verify libvirt XML
                dom_xml = "virsh -c qemu:///system dumpxml --domain {0}".format("subVmTest1")
                mac_string = '"{0}"'.format(self.mac)
                xmllint_element = "{0} | xmllint --xpath 'string(//domain/devices/interface[starts-with(mac/@address,{1})]/{{prop}})' - 2>&1 || true".format(dom_xml, mac_string)

                if (self.source_type == "Virtual network"):
                    self.test_obj.assertEqual("network", m.execute(xmllint_element.format(prop='@type')).strip())
                    if self.source:
                        self.test_obj.assertEqual(self.source, m.execute(xmllint_element.format(prop='source/@network')).strip())
                elif (self.source_type == "Direct attachment"):
                    self.test_obj.assertEqual("direct", m.execute(xmllint_element.format(prop='@type')).strip())
                    if self.source:
                        self.test_obj.assertEqual(self.source, m.execute(xmllint_element.format(prop='source/@dev')).strip())

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

            def verify_overview(self):
                # The first NIC is default, our new NIC is second in row
                if (self.source_type == "Virtual network"):
                    b.wait_in_text("#vm-subVmTest1-network-{0}-type".format(self.nic_num), "network")
                elif (self.source_type == "Direct attachment"):
                    b.wait_in_text("#vm-subVmTest1-network-{0}-type".format(self.nic_num), "direct")
                if self.model:
                    b.wait_in_text("#vm-subVmTest1-network-{0}-model".format(self.nic_num), self.model)
                if self.source:
                    b.wait_in_text("#vm-subVmTest1-network-{0}-source".format(self.nic_num), self.source)
                if self.mac:
                    b.wait_in_text("#vm-subVmTest1-network-{0}-mac".format(self.nic_num), self.mac)

            def cleanup(self):
                if self.permanent:
                    source_type = "direct"
                    if (self.source_type == "Virtual network"):
                        source_type = "network"
                    if (self.source_type == "Bridge to LAN"):
                        source_type = "bridge"
                    m.execute("virsh detach-interface --mac {0} --domain subVmTest1 --type {1} --config".format(self.mac, source_type))

                    # we don't get any signal for interface detaching right now
                    b.reload()
                    b.enter_page('/machines')
                    b.wait_in_text("body", "Virtual Machines")
                    self.test_obj.toggleVmRow("subVmTest1")
                    b.click("#vm-subVmTest1-networks") # open the Network Interfaces subtab
                else:
                    b.click("#delete-vm-subVmTest1-iface-{0}".format(self.nic_num))
                    # Confirm in confirmation window
                    b.wait_in_text(".modal-dialog .modal-header .modal-title", "Delete Network Interface")
                    vm_state = b.text("#vm-subVmTest1-state")
                    b.click(".modal-footer button:contains(Delete)")
                    # On a running VM detaching NIC takes longer and we can see the spinner
                    if vm_state == "running":
                        b.wait_present(".modal-footer .spinner")

                    # Check NIC is no longer in list
                    b.wait_not_present("#vm-subVmTest1-network-{0}-mac".format(self.nic_num))
                    b.wait_not_present(".modal-dialog")

        # No NICs present
        b.wait_present("#vm-subVmTest1-add-iface-button") # open the Network Interfaces subtab

        NICAddDialog(
            self,
            source_type="Virtual network",
            source="test_network",
        ).execute()

        # Test Direct attachment
        NICAddDialog(
            self,
            source_type="Direct attachment",
        ).execute()

        # Test Bridge
        NICAddDialog(
            self,
            source_type="Bridge to LAN",
            source="virbr0",
        ).execute()

        # Test model
        NICAddDialog(
            self,
            model="e1000e",
        ).execute()

        # Start vm and wait until kernel is booted
        m.execute("> {0}".format(args["logfile"])) # clear logfile
        b.click("#vm-subVmTest1-run")
        b.wait_in_text("#vm-subVmTest1-state", "running")
        wait(lambda: "Linux version" in self.machine.execute("cat {0}".format(args["logfile"])), delay=3)

        # Test permanent attachment to running VM
        NICAddDialog(
            self,
            source_type="Virtual network",
            source="test_network",
            permanent=True,
        ).execute()

        # Test NIC attaching to non-persistent VM
        m.execute("virsh dumpxml --inactive subVmTest1 > /tmp/subVmTest1.xml; virsh undefine subVmTest1")
        NICAddDialog(
            self,
            source_type="Virtual network",
            source="test_network",
            mac="52:54:00:a5:f8:c1",
            nic_num=3,
            persistent_vm=False,
        ).execute()
        m.execute("virsh define /tmp/subVmTest1.xml")

        b.click("#vm-subVmTest1-action-kebab button")
        b.click("#vm-subVmTest1-forceOff")
        b.wait_in_text("#vm-subVmTest1-state", "shut off")

        NICAddDialog(
            self,
            remove=False,
        ).execute()

        NICAddDialog(
            self,
            remove=False,
        ).execute()

        # Now there are three NICs present - try to delete the second one and make sure that it's deleted and the dialog is not present anymore
        b.wait_present("#vm-subVmTest1-network-3-mac")
        b.click("#delete-vm-subVmTest1-iface-2")
        b.wait_in_text(".modal-dialog .modal-header .modal-title", "Delete Network Interface")
        b.click(".modal-footer button:contains(Delete)")
        # Check NIC is no longer in list
        b.wait_not_present("#vm-subVmTest1-network-3-mac")
        b.wait_not_present(".modal-dialog")

if __name__ == '__main__':
    test_main()
