#!/usr/bin/python3

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

import parent
from machineslib import *
from testlib import *
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

@skipImage("Atomic cannot run virtual machines", "fedora-coreos")
@nondestructive
@no_retry_when_changed
class TestMachinesNetworks(VirtualMachinesCase):

    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("#card-pf-networks .card-pf-title-link", "1 Network")

        # Create dummy network
        m.execute("echo \"{0}\" > /tmp/xml && virsh net-define /tmp/xml".format(TEST_NETWORK2_XML))
        b.wait_in_text("#card-pf-networks .card-pf-title-link", "2 Networks")
        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(".pf-c-card .pf-c-card__header button: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_visible("#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_visible("#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_visible('#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 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("#card-pf-networks .pf-c-card__header button", "Network")
        b.click(".pf-c-card .pf-c-card__header button: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_visible("#create-network-dialog")
                b.wait_in_text(".pf-c-modal-box .pf-c-modal-box__header .pf-c-modal-box__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(".pf-c-modal-box__footer button:contains(Cancel)")
                b.wait_not_present("#create-network-dialog")

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

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

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

            def verify_expected_error_on_head(self, error_message):
                b.click('#network-{0}-system-state button:contains("view more")'.format(self.name))
                b.wait_in_text(".pf-c-popover", error_message)
                b.click('#network-{0}-system-state button[aria-label=label-close-button]'.format(self.name))

            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.1",
            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.1",
            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.1",
            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 within 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.1",
            ipv4_netmask="255.255.255.0",
        ).execute()

        # Check "Address not within subnet"
        NetworkCreateDialog(
            self,
            name="test_network",
            forward_mode="open",
            ip_conf="IPv4 only",
            ipv4_address="192.168.100.1",
            ipv4_netmask="64",
            ipv4_dhcp_start="192.168.100.0",
            ipv4_dhcp_end="191.168.100.255",
            xfail=True,
            xfail_objects = ["ipv4_dhcp_start", "ipv4_dhcp_end"],
            xfail_error = "Address not within subnet",
        ).execute()

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

        self.createVm("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")

        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-title-link", "2 Networks")

        self.goToVmPage("subVmTest1")

        # 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")
        # Wait for the footer warning to appear
        b.wait_visible("#vm-subVmTest1-network-1-edit-dialog-idle-message")
        # Change network type and source of a running domain
        b.wait_val("#vm-subVmTest1-network-1-select-type", "network")
        b.wait_val("#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_visible("#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_visible("#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")

        self.goToMainPage()

        b.wait_in_text("#card-pf-networks .card-pf-title-link", "2 Networks")
        self.waitVmRow("subVmTest1")
        self.goToVmPage("subVmTest1")

        # 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_val("#vm-subVmTest1-network-1-select-type", "bridge")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "direct")
        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_val("#vm-subVmTest1-network-1-select-type", "direct")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "bridge")
        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_val("#vm-subVmTest1-network-1-select-type", "bridge")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "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")

        self.goToMainPage()

        b.wait_in_text("#card-pf-networks .active-resources:nth-of-type(1)", "0")
        m.execute("virsh net-undefine test_network && virsh net-undefine default")
        b.wait_in_text("#card-pf-networks .active-resources:nth-of-type(2)", "0")
        b.wait_in_text("#card-pf-networks .card-pf-title-link", "0 Networks")

        self.goToVmPage("subVmTest1")

        # 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_val("#vm-subVmTest1-network-1-select-type", "bridge")
        b.select_from_dropdown("#vm-subVmTest1-network-1-select-type", "network")
        b.wait_visible("#vm-subVmTest1-network-1-edit-dialog-save:disabled")
        b.click(".pf-c-modal-box__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-type", "network")
        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("#card-pf-networks .pf-c-card__header button", "Network")
        b.click(".pf-c-card .pf-c-card__header button: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_visible("#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_visible("#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_visible("#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_visible("#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.click(".pf-c-card .pf-c-card__header button: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_visible("#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_visible("#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_visible("#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(".pf-c-modal-box__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(".pf-c-modal-box__footer button:contains(Delete)")
        self.waitNetworkRow("test_network2", connectionName, False)


if __name__ == '__main__':
    test_main()
