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

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

import machineslib
import testlib
from machinesxmls import POOL_XML


class TestMachinesMigration(machineslib.VirtualMachinesCase):
    provision = {  # noqa: RUF012
        "machine1": {"address": "10.111.113.1/20", "dns": "10.111.112.100"},
        "machine2": {"address": "10.111.113.2/20", "dns": "10.111.112.100"},
    }

    def prepare(self, machine1, machine2, fail):
        machine1.execute("echo '10.111.113.2 machine2' >> /etc/hosts")
        machine1.execute("echo '10.111.113.1 machine1' >> /etc/hosts")
        machine2.execute("echo '10.111.113.2 machine2' >> /etc/hosts")
        machine2.execute("echo '10.111.113.1 machine1' >> /etc/hosts")
        machine2.execute("sed -i 's/127.0.1.1.*/127.0.1.1 machine2/' /etc/hosts")
        machine2.execute("hostnamectl set-hostname machine2")
        machine2.execute(f"systemctl restart {self.getLibvirtServiceName()}")

        self.startLibvirt(machine2)

        if fail != "authentication":
            # setup key authentication
            machine1.execute("mkdir -m 0700 -p /root/.ssh")
            machine1.execute("ssh-keygen -t rsa -N '\' -f /root/.ssh/id_rsa")
            machine1.execute("echo 'StrictHostKeyChecking no' > /root/.ssh/config")
            machine1PubKey = machine1.execute("cat /root/.ssh/id_rsa.pub")
            machine2.execute(f"echo '{machine1PubKey}' >> /root/.ssh/authorized_keys")

        if fail != "pool":
            # Setup the default storage pool on the destination host
            # This is normally automatically created when creating the first VM but in this case our dest VM was never populated
            xml = POOL_XML.format(path="/var/lib/libvirt/images")
            machine2.write("/tmp/xml", xml)
            machine2.execute("virsh pool-define /tmp/xml; virsh pool-start images")
        if fail == "volume":
            machine2.execute("virsh vol-create-as images subVmTest1-2.img --capacity 1M --format qcow2")

        if fail != "port_closed":
            # Plain migration: The source host VM opens a direct unencrypted TCP connection to the destination host for sending the migration data.
            # Unless a port is manually specified, libvirt will choose a migration port in the range 49152-49215, which will need to be open in the firewall on the remote host.
            # https://wiki.libvirt.org/page/FAQ
            machine2.execute("systemctl start firewalld")
            machine2.execute("firewall-cmd --permanent --add-port 49152-49215/tcp")
            machine2.execute("firewall-cmd --reload")

    def preCreateStorageOnDest(self, machine1, machine2):
        dev = self.add_ram_disk(2)
        disk_xml = '''
<disk type='block' device='disk'>
  <driver name='qemu' type='raw' cache='none'/>
  <source dev='{0}'/>
  <target dev='vda'/>
</disk>'''
        machine1.execute(f"echo \"{disk_xml.format(dev)}\" > /tmp/disk_xml")
        machine1.execute("virsh destroy subVmTest1")
        machine1.execute("""
            virt-xml --remove-device --disk 1 subVmTest1
            virsh attach-device --domain subVmTest1 --file /tmp/disk_xml --config
            virsh start subVmTest1""")

        orig_machine = self.machine
        self.machine = machine2
        self.add_ram_disk(2)
        self.machine = orig_machine

    def testSharedStorageMigration(self):
        self._testMigrationGeneric(False, False, None)

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testCopyStorageMigration(self):
        self._testMigrationGeneric(True, False, None)

    def testMoveTemporarilyMigration(self):
        self._testMigrationGeneric(False, True, None)

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationPoolNotFound(self):
        # Equivalent storage pool is not present on the destination
        self._testMigrationGeneric(True, False, "pool")

    # Only qemu 5.1.0 and above does checking for volumes compatibility during migration
    # See https://mail.gnu.org/archive/html/qemu-devel/2020-04/msg05559.html
    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationVolumesNotCompatible(self):
        # Different sized volume already exists on destination
        self._testMigrationGeneric(True, False, "volume")

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationTCPPortClosed(self):
        # Ports for TCP transfer are closed
        self._testMigrationGeneric(True, False, "port_closed")

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationAuthentication(self):
        # No key authentication is set up
        self._testMigrationGeneric(True, False, "authentication")

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationUriIncorrect(self):
        # Incorrect uri inputted
        self._testMigrationGeneric(True, False, "uri")

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationDomainUnknown(self):
        # Destination domain name is not defined in /etc/hosts and is not resolvable
        self._testMigrationGeneric(True, False, "domain")

    @testlib.skipImage('RHEL does not provide support for copy-storage migration', "rhel-8-*", "rhel-9-*", "centos-9-stream")
    def testFailMigrationStopLibvirtd(self):
        # Libvirtd.service is not working on the destination
        self._testMigrationGeneric(True, False, "libvirtd")

    def _testMigrationGeneric(self, copy_storage, temporary, fail):
        b = self.browser
        machine1 = self.machine
        machine2 = self.machines['machine2']

        self.createVm("subVmTest1")
        self.login_and_go("/machines")
        self.prepare(machine1, machine2, fail)

        if not copy_storage:
            self.preCreateStorageOnDest(machine1, machine2)

        self.waitVmRow("subVmTest1")
        self.goToVmPage("subVmTest1")

        self.performAction("subVmTest1", "migrate")
        b.wait_visible("#migrate-modal")

        if fail == "uri":
            # test failure for syntactically incorrect destination uri
            b.set_input_text("#dest-uri-input", "qemu+ssh://root@machine2/sistem")
        elif fail == "domain":
            # test failure for unknown domain
            b.set_input_text("#dest-uri-input", "qemu+ssh://root@someunknowndomain/system")
        else:
            b.set_input_text("#dest-uri-input", "qemu+ssh://root@machine2/system")

        b.set_checked("#temporary", temporary)

        if self.machine.image.startswith("rhel") or self.machine.image.startswith("centos-9"):
            with b.wait_timeout(120):
                b.wait_not_present("#copy")
        else:
            if copy_storage:
                b.click("#copy")
            else:
                b.click("#shared")

        if temporary and copy_storage:
            b.wait_visible(".footer-warning")

        # The VM does not exist in destination before migration happens
        self.assertNotIn("subVmTest1", machine2.execute("virsh list --all"))

        if fail == "libvirtd":
            machine2.execute(f"systemctl stop {self.getLibvirtServiceName()}.service")
            machine2.execute(f"systemctl stop {self.getLibvirtServiceName()}.socket")

        b.click("#migrate-button")
        if fail:
            with b.wait_timeout(120):
                b.wait_visible(".pf-v5-c-modal-box__body .pf-v5-c-alert .pf-v5-c-alert__title:contains('Migration failed')")

            if fail == "libvirtd":
                machine2.execute(f"systemctl start {self.getLibvirtServiceName()}.service")
                machine2.execute(f"systemctl start {self.getLibvirtServiceName()}.socket")

            testlib.wait(lambda: "subVmTest1" not in machine2.execute("virsh list --all"), delay=3)
            self.assertIn("subVmTest1", machine1.execute("virsh list --all"))
        else:
            with b.wait_timeout(120):
                b.wait_not_present("#migrate-modal")

            # Verify the state of the migrated VM in the destination host
            # The migrated VM should always exist on destination - domstate depends on the options of the dialog set
            testlib.wait(lambda: "subVmTest1" in machine2.execute("virsh list --all"), delay=3)
            testlib.wait(lambda: "running" in machine2.execute("virsh domstate subVmTest1"), delay=3)
            if temporary:
                # VM on the destination host should be transient
                self.assertIn("Persistent:     no", machine2.execute("virsh dominfo subVmTest1"))
            else:
                # VM on the destination host should be transient
                self.assertIn("Persistent:     yes", machine2.execute("virsh dominfo subVmTest1"))

            # Verify the state of the migrated VM in the source host
            if temporary:
                # VM on the source host should be still defined
                self.assertIn("shut off", machine1.execute("virsh domstate subVmTest1"))
            else:
                # VM should not exist on the source host
                self.assertNotIn("subVmTest1", machine1.execute("virsh list --all"))

            if not copy_storage:
                # Running VM on destination host has disk with a block storage which is available on both source and destination
                self.assertIn("disk type='block'", machine2.execute("virsh dumpxml subVmTest1"))

            if temporary:
                b.wait_visible("#vm-details")
            else:
                b.wait_not_present("#vm-details")
                b.wait_not_present("tbody tr[data-row-id=vm-subVmTest1-system]")


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