From a46b746983b2f0a61039bb553571cad876c91846 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 16 Apr 2026 15:16:23 +0200 Subject: [PATCH 1/7] tests: Add Windows domain description We collect all Windows specific setup in a dedicated nix derivation. We do so because we don't want to have Windows specific settings in our linux test, as we wont use them anyway. Along with this decoupling comes the advantage that the Windows image will not be included when calling test targets that don't reference this description. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- tests/windows-domain-xml.nix | 87 ++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/windows-domain-xml.nix 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"; + })}"; + }; + }; + }; + }; +} From 83d5ebbf20e0e7d80ca64f03714ba8dc8e36e18b Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Wed, 15 Apr 2026 14:07:45 +0200 Subject: [PATCH 2/7] tests: Copy images only as they are needed Make `initialControllerVMSetup` copy only the system images that we will need in our test. We do so as some images are quite large and need considerably time to be copied to the NFS. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- test_helper/test_helper/test_helper.py | 42 +++++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) 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}" ) From 2d23901d470343e9db6941636189ed9bb5ea004d Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Tue, 14 Apr 2026 14:51:02 +0200 Subject: [PATCH 3/7] tests: Increase the disk size of ControllerVM The NFS is stored on the virtual disk of the ControllerVM. We increase its disk size so it has enough space to also host the Windows image, which is 20 Gib in size. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- tests/libvirt-test.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 61bc19d0769ea8ca7fb698da9f2a78d03b4a9c18 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 16 Apr 2026 15:12:24 +0200 Subject: [PATCH 4/7] tests: Add a test suite for Windows tests We don't want to copy the Windows image in all test suites. Moreover, the Windows tests consume a lot of time because of the initial copy created of the image for each test. We therefore refrain from adding Windows test to other test suites and encapsulate them in their own test suite. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- tests/default.nix | 16 +++++++ tests/testsuite_windows_default.py | 71 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/testsuite_windows_default.py diff --git a/tests/default.nix b/tests/default.nix index 57c045f..6728e7c 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,17 @@ let inherit enablePortForwarding; testScriptFile = ./testsuite_cpu_profiles_host.py; }; + + windows = createTestSuite { + inherit enablePortForwarding; + testScriptFile = ./testsuite_windows_default.py; + extraControllerConfig = [ + windowsLibvirtDomainCfg + ]; + extraComputeConfig = [ + windowsLibvirtDomainCfg + ]; + }; }; # Convenience attribute containing all nixos test driver attributes mainly 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") From 87bd8b9999f0d10e165c2c337db8497f3c4a7ae3 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Thu, 16 Apr 2026 15:11:09 +0200 Subject: [PATCH 5/7] tests: Add Windows test suite with CPU profiles Similar to other CPU profile tests, these test require special hardware to run on. Because of this, we move them to their own test suite and do not merge them with the existing Windows test suite. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- tests/default.nix | 35 ++++++++++++ tests/testsuite_windows_cpu_profiles.py | 73 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 tests/testsuite_windows_cpu_profiles.py diff --git a/tests/default.nix b/tests/default.nix index 6728e7c..6ba43d8 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -173,6 +173,41 @@ let 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/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") From d087f910be52d5d8cba8ade2716ceb3fdc11bddd Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Wed, 15 Apr 2026 13:51:42 +0200 Subject: [PATCH 6/7] docs: Add documentation for the Windows image We build the image on our own. Because we there is no script to automatically generate the image, from which one could obtain infromation about the image, we describe it as best as we can in the documentation. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- docs/windows_image.md | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 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 +``` From 129593acf710c3705835d1f7c44772e7ffb3dd28 Mon Sep 17 00:00:00 2001 From: Pascal Scholz Date: Tue, 14 Apr 2026 15:00:41 +0200 Subject: [PATCH 7/7] readme: Add documentation for new Windows test suites Along with documenting the Windows test suite we als link the documentation with of the Windows image used in the test suites. Signed-off-by: Pascal Scholz On-behalf-of: SAP pascal.scholz@sap.com --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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)