diff --git a/README.md b/README.md index 8284efd..8fa0fdf 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,20 @@ test attributes are available. Each attribute can be run via - `numa_hosts` - tests that check migrations between hosts with different NUMA configurations - `cpu_profiles` - - tests that run on hosts with different CPU profiles, including migration tests + - tests that run on hosts with different CPU profiles, including migration + tests - need to run an a CPU compatible with CPU profile used in the respective test +- `windows` + - tests run with Windows Server 2025 as guest OS + - the OS image is quite large so you might want to have a look at + `XDG_RUNTIME_DIR` (see above) +- `windows_cpu_profiles` + - tests run with Windows Server 2025 as guest OS on with additional CPU + profiles + - the OS image is quite large so you might want to have a look at + `XDG_RUNTIME_DIR` (see above) + - need to run an a CPU compatible with the CPU profile used in the respective + test ### Obtaining debug logs @@ -110,3 +122,4 @@ To directly access the Cloud Hypervisor VM, you can run ## More Documentation - [VM networks overview](./docs/networks.md) +- [Information on Windows Server 2025 image](./docs/windows_image.md) diff --git a/docs/windows_image.md b/docs/windows_image.md new file mode 100644 index 0000000..675a421 --- /dev/null +++ b/docs/windows_image.md @@ -0,0 +1,44 @@ +# Windows Server 2025 Images Provision + +* The image originates from a standard Windows Server 2025 installation +* An SSH server is running, it was activated by executing the command below + * The firewall is turned off as described in the basic setup + * As a side note, Windows does not support Post Quantum KEX algorithms. + +```powershell +Start-Service sshd +Set-Service -Name sshd -StartupType Automatic +``` + +* There exists a script that binds the IP address 192.186.1.2 to the MAC `52:54:00:e5:b8:01` + * This script can be found at `C:\bind-mac.ps1` + * It contains the following code: + +```powershell +$targetMac = "52-54-00-E5-B8-01" + +for ($i=0; $i -lt 10; $i++) { + $iface = Get-NetAdapter | Where-Object { + $_.MacAddress -eq $targetMac -and $_.Status -eq "Up" + } + if ($iface) { break } + Start-Sleep -Seconds 2 +} + +if ($iface) { + New-NetIPAddress -InterfaceIndex $iface.ifIndex ` + -IPAddress 192.168.1.2 -PrefixLength 24 ` + -DefaultGateway 192.168.1.1 -ErrorAction SilentlyContinue +} +``` + +* There is a service scheduled that runs the binding script. It was scheduled with the command below. + * Running the script on startup is necessary as we can guarantee this way that the interface with the correct MAC receives the desired IP + * Windows creates Interfaces in a weird way, so we cannot guarantee that the VM has the same interface as when we provisioned the image in Qemu + * Otherwise running `bind-mac.ps1` once would be enough + +```powershell +schtasks /create /tn "BindIPToMAC" ` + /tr "powershell -ExecutionPolicy Bypass -File C:\bind-mac.ps1" ` + /sc onstart /ru SYSTEM +``` diff --git a/test_helper/test_helper/test_helper.py b/test_helper/test_helper/test_helper.py index beea28e..ca674e9 100644 --- a/test_helper/test_helper/test_helper.py +++ b/test_helper/test_helper/test_helper.py @@ -99,12 +99,17 @@ def save_logs(self, test, message): self.save_machine_log(machine, "/tmp/vm_serial.log", dst_path) -def initialControllerVMSetup(controllerVM: Machine) -> None: +def initialControllerVMSetup( + controllerVM: Machine, target_os: Literal["linux", "windows"] = "linux" +) -> None: """ - This method configures the controllerVM initially, before the test suite - runs. It sets up e.g. the NFS share with the correct OS images. + This method configures the controllerVM initially, before the test + suite runs. It sets up e.g. the NFS share with the correct OS + images. :param controllerVM: machine object of the controllerVM + :param target_os: If "windows", prepare the NFS with the Windows + Server image. Otherwise places the Linux images in the NFS. :raises RuntimeError: If the machine object is not the controllerVM """ if controllerVM.name != "controllerVM": @@ -113,14 +118,18 @@ def initialControllerVMSetup(controllerVM: Machine) -> None: ) controllerVM.wait_for_unit("multi-user.target") - controllerVM.succeed("cp /etc/nixos.img /nfs-root/") - controllerVM.succeed("chmod 0666 /nfs-root/nixos.img") - controllerVM.succeed("cp /etc/cirros.img /nfs-root/") - controllerVM.succeed("chmod 0666 /nfs-root/cirros.img") - controllerVM.succeed("cp /etc/ubuntu.raw /nfs-root/") - controllerVM.succeed("chmod 0666 /nfs-root/ubuntu.raw") - controllerVM.succeed("cp /etc/ubuntu-seed.iso /nfs-root/") - controllerVM.succeed("chmod 0666 /nfs-root/ubuntu-seed.iso") + if target_os == "windows": + controllerVM.succeed("cp /etc/windows-server.img /nfs-root/windows-server.img") + controllerVM.succeed("chmod 0666 /nfs-root/windows-server.img") + else: + controllerVM.succeed("cp /etc/nixos.img /nfs-root/") + controllerVM.succeed("chmod 0666 /nfs-root/nixos.img") + controllerVM.succeed("cp /etc/cirros.img /nfs-root/") + controllerVM.succeed("chmod 0666 /nfs-root/cirros.img") + controllerVM.succeed("cp /etc/ubuntu.raw /nfs-root/") + controllerVM.succeed("chmod 0666 /nfs-root/ubuntu.raw") + controllerVM.succeed("cp /etc/ubuntu-seed.iso /nfs-root/") + controllerVM.succeed("chmod 0666 /nfs-root/ubuntu-seed.iso") controllerVM.succeed("mkdir -p /var/lib/libvirt/storage-pools/nfs-share") @@ -332,6 +341,17 @@ def teardownTestControllerVM(controllerVM: Machine, test: unittest.TestCase) -> "rsync -aL --no-perms --inplace --checksum /etc/nixos.img /nfs-root/nixos.img" ) + # Currently, we don't store any data about if we copied the windows image to + # the NFS. We therefore have to check if it's there before resetting it + # `test -e` returns 0 if a file exists and 1 otherwise. + test_return_code, _ = controllerVM.execute("test -e /nfs-root/windows-server.img") + if test_return_code == 0: + controllerVM.succeed( + "rsync -aL --no-perms --inplace --checksum /etc/windows-server.img /nfs-root/windows-server.img" + ) + + # Check the sanitizer last, as the assertion can fail. Otherwise, we might + # skip some clean up. test.assertNotEqual( statusController, 0, msg=f"Sanitizer detected an issue: {outController}" ) diff --git a/tests/default.nix b/tests/default.nix index 57c045f..6ba43d8 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -9,6 +9,11 @@ let inherit nixos-image; inherit (pkgs) writeText; }; + + windowsLibvirtDomainCfg = import ./windows-domain-xml.nix { + inherit pkgs; + }; + createTestSuite = { testScriptFile, @@ -157,6 +162,52 @@ let inherit enablePortForwarding; testScriptFile = ./testsuite_cpu_profiles_host.py; }; + + windows = createTestSuite { + inherit enablePortForwarding; + testScriptFile = ./testsuite_windows_default.py; + extraControllerConfig = [ + windowsLibvirtDomainCfg + ]; + extraComputeConfig = [ + windowsLibvirtDomainCfg + ]; + }; + + # The test requires a host with a recent Intel processor. The test is not + # enabled in our generic CI because of these hardware restrictions. + windows_cpu_profiles = createTestSuite { + inherit enablePortForwarding; + testScriptFile = ./testsuite_windows_cpu_profiles.py; + extraControllerConfig = [ + windowsLibvirtDomainCfg + ( + { ... }: + { + virtualisation.qemu.options = + + [ + "-cpu" + "host,+vmx" + ]; + } + ) + ]; + extraComputeConfig = [ + windowsLibvirtDomainCfg + ( + { ... }: + { + virtualisation.qemu.options = + + [ + "-cpu" + "host,+vmx" + ]; + } + ) + ]; + }; }; # Convenience attribute containing all nixos test driver attributes mainly diff --git a/tests/libvirt-test.nix b/tests/libvirt-test.nix index 1932f60..554ccfb 100644 --- a/tests/libvirt-test.nix +++ b/tests/libvirt-test.nix @@ -45,7 +45,7 @@ pkgs.testers.nixosTest { cores = 4; memorySize = 4096; interfaces.eth1.vlan = 1; - diskSize = 8192; + diskSize = 28672; forwardPorts = # Port forwarding prevents us from executing the nixos tests in # parallel in the CI, as they run in the same context and ports are diff --git a/tests/testsuite_windows_cpu_profiles.py b/tests/testsuite_windows_cpu_profiles.py new file mode 100644 index 0000000..336f9ce --- /dev/null +++ b/tests/testsuite_windows_cpu_profiles.py @@ -0,0 +1,73 @@ +import unittest + +# Following import statement allows for proper python IDE support and proper +# nix build support. The duplicate listing of imported functions is a bit +# unfortunate, but it seems to be the best compromise. This way the python IDE +# support works out of the box in VSCode and IntelliJ without requiring +# additional IDE configuration. +try: + from ..test_helper.test_helper import ( # type: ignore + LibvirtTestsBase, + initialComputeVMSetup, + initialControllerVMSetup, + wait_for_ssh, + ) +except Exception: + from test_helper import ( + LibvirtTestsBase, + initialComputeVMSetup, + initialControllerVMSetup, + wait_for_ssh, + ) + +# pyright: reportPossiblyUnboundVariable=false + +# Following is required to allow proper linting of the python code in IDEs. +# Because certain functions like start_all() and certain objects like computeVM +# or other machines are added by Nix, we need to provide certain stub objects +# in order to allow the IDE to lint the python code successfully. +if "start_all" not in globals(): + from ..test_helper.test_helper.nixos_test_stubs import ( # type: ignore + computeVM, + controllerVM, + start_all, + ) + + +class LibvirtTests(LibvirtTestsBase): # type: ignore + def __init__(self, methodName): + super().__init__(methodName, controllerVM, computeVM) + + @classmethod + def setUpClass(cls): + start_all() + initialControllerVMSetup(controllerVM, target_os="windows") + initialComputeVMSetup(computeVM) + + def test_windows_boot(self): + """ + Test that we do not introduce a regression with respect to booting windows. + """ + + controllerVM.succeed( + "virsh define /etc/domain-windows-server-sapphire-rapids.xml" + ) + controllerVM.succeed("virsh start testvm") + wait_for_ssh(controllerVM, user="administrator", password="FOO99bar!!") + + +def suite(): + # Test cases involving live migration sorted in alphabetical order. + testcases = [ + LibvirtTests.test_windows_boot, + ] + + suite = unittest.TestSuite() + for testcaseMethod in testcases: + suite.addTest(LibvirtTests(testcaseMethod.__name__)) + return suite + + +runner = unittest.TextTestRunner() +if not runner.run(suite()).wasSuccessful(): + raise Exception("Test Run unsuccessful") diff --git a/tests/testsuite_windows_default.py b/tests/testsuite_windows_default.py new file mode 100644 index 0000000..2812772 --- /dev/null +++ b/tests/testsuite_windows_default.py @@ -0,0 +1,71 @@ +import unittest + +# Following import statement allows for proper python IDE support and proper +# nix build support. The duplicate listing of imported functions is a bit +# unfortunate, but it seems to be the best compromise. This way the python IDE +# support works out of the box in VSCode and IntelliJ without requiring +# additional IDE configuration. +try: + from ..test_helper.test_helper import ( # type: ignore + LibvirtTestsBase, + initialComputeVMSetup, + initialControllerVMSetup, + wait_for_ssh, + ) +except Exception: + from test_helper import ( + LibvirtTestsBase, + initialComputeVMSetup, + initialControllerVMSetup, + wait_for_ssh, + ) + +# pyright: reportPossiblyUnboundVariable=false + +# Following is required to allow proper linting of the python code in IDEs. +# Because certain functions like start_all() and certain objects like computeVM +# or other machines are added by Nix, we need to provide certain stub objects +# in order to allow the IDE to lint the python code successfully. +if "start_all" not in globals(): + from ..test_helper.test_helper.nixos_test_stubs import ( # type: ignore + computeVM, + controllerVM, + start_all, + ) + + +class LibvirtTests(LibvirtTestsBase): # type: ignore + def __init__(self, methodName): + super().__init__(methodName, controllerVM, computeVM) + + @classmethod + def setUpClass(cls): + start_all() + initialControllerVMSetup(controllerVM, target_os="windows") + initialComputeVMSetup(computeVM) + + def test_windows_boot(self): + """ + Test that we do not introduce a regression with respect to booting windows. + """ + + controllerVM.succeed("virsh define /etc/domain-windows-server.xml") + controllerVM.succeed("virsh start testvm") + wait_for_ssh(controllerVM, user="administrator", password="FOO99bar!!") + + +def suite(): + # Test cases sorted in alphabetical order. + testcases = [ + LibvirtTests.test_windows_boot, + ] + + suite = unittest.TestSuite() + for testcaseMethod in testcases: + suite.addTest(LibvirtTests(testcaseMethod.__name__)) + return suite + + +runner = unittest.TextTestRunner() +if not runner.run(suite()).wasSuccessful(): + raise Exception("Test Run unsuccessful") diff --git a/tests/windows-domain-xml.nix b/tests/windows-domain-xml.nix new file mode 100644 index 0000000..20d0fbe --- /dev/null +++ b/tests/windows-domain-xml.nix @@ -0,0 +1,87 @@ +# Returns a NixOS module with a libvirt XML definitions in `/etc` for +# our Windows tests. + +{ + pkgs, +}: +{ lib, ... }: +let + + # The image size is currently 20 GiB + windows_server_raw = pkgs.fetchurl { + url = "https://nexus.vpn.cyberus-technology.de/repository/vm-test-images/server-2025-root-small-ssh-enabled.raw"; + hash = "sha256-Afc5ectMbmVxch8ivflQ4G27CcpKhCFsLPf5J9I+1KE="; + }; + + virsh_windows_server_xml = + { + image ? "/var/lib/libvirt/storage-pools/nfs-share/windows-server.img", + cpuModel ? "", + }: + '' + + testvm + 4eb6319a-4302-4407-9a56-802fc7e6a422 + 3072 + 3072 + ${lib.optionalString (cpuModel != "") '' + + ${cpuModel} + + ''} + 4 + + hvm + /etc/CLOUDHV.fd + + + + + + + destroy + restart + destroy + + cloud-hypervisor + + + + + + + + + +
+ + + + + + + ''; +in +{ + systemd.tmpfiles.settings = { + "10-chv" = { + "/etc/windows-server.img" = { + "L+" = { + argument = "${windows_server_raw}"; + }; + }; + "/etc/domain-windows-server.xml" = { + "C+" = { + argument = "${pkgs.writeText "domain-windows-server.xml" (virsh_windows_server_xml { })}"; + }; + }; + "/etc/domain-windows-server-sapphire-rapids.xml" = { + "C+" = { + argument = "${pkgs.writeText "domain-windows-server-sapphire-rapids.xml" (virsh_windows_server_xml { + cpuModel = "sapphire-rapids"; + })}"; + }; + }; + }; + }; +}