Skip to content

Commit

Permalink
Add Windows tools install/uninstall/upgrade tests
Browse files Browse the repository at this point in the history
Signed-off-by: Tu Dinh <[email protected]>
  • Loading branch information
Tu Dinh committed Nov 26, 2024
1 parent dcad90d commit c71c263
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 0 deletions.
13 changes: 13 additions & 0 deletions jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,19 @@
"paths": ["tests/guest-tools/unix"],
"markers": "multi_vms",
},
"tools-windows": {
"description": "tests our windows guest tools on a variety of VMs",
"requirements": [
"A pool >= 8.3. One host is enough.",
"A variety of windows VMs supported by our tools installer.",
],
"nb_pools": 1,
"params": {
"--vm[]": "multi/tools_windows",
},
"paths": ["tests/guest-tools/win"],
"markers": "multi_vms and windows_vm",
},
"xen": {
"description": "Testing of the Xen hypervisor itself",
"requirements": [
Expand Down
24 changes: 24 additions & 0 deletions tests/guest-tools/win/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import logging
import re
from lib.common import wait_for
from lib.vm import VM


def try_get_and_store_vm_ip_serial(vm: VM, timeout: int):
domid = vm.param_get("dom-id")
logging.debug(f"Domain ID {domid}")
command = f"xl console -t serial {domid} | grep '~xcp-ng-tests~.*~end~' | head -n 1"
if timeout > 0:
command = f"timeout {timeout} " + command
report = vm.host.ssh(command)
logging.debug(f"Got report: {report}")
match = re.match("~xcp-ng-tests~(.*)=(.*)~end~", report)
if not match:
return False
vm.ip = match[2]
return True


def wait_for_vm_running_and_ssh_up_without_tools(vm: VM):
wait_for(vm.is_running, "Wait for VM running")
wait_for(vm.is_ssh_up, "Wait for SSH up")
305 changes: 305 additions & 0 deletions tests/guest-tools/win/test_guest_tools_win.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
import enum
import logging
import pytest
from lib.common import wait_for
from lib.host import Host
from lib.snapshot import Snapshot
from lib.vdi import VDI
from lib.vm import VM
from . import *

from data import ISO_DOWNLOAD_URL

# Requirements:
# - XCP-ng >= 8.3.
#
# From --vm parameter:
# - A Windows VM with the following requirements:
# - Xen PV tools not installed
# - Testsigning enabled
# - Reports its IP via serial console on boot in this format:
# "~xcp-ng-tests~<mac>=<ip>~end~\r\n"
# The VM should report its IP frequently since the test script acquires the VM's IP based on a timeout.
# - Git Bash
# - OpenSSH:
# - Server enabled and allowed by firewall
# - SSH key installed into %ProgramData%\ssh\administrators_authorized_keys with appropriate permissions
# - Registry value "HKLM\SOFTWARE\OpenSSH DefaultShell" set to Git Bash
#
# Specific configuration:
# - ISO image of guest tools under test with the following structure:
# guest-tools-win.iso
# ├───package
# │ └───XenDrivers-x64.msi
# └───testsign
# └───*.crt
# - ISO image of other guest tools with the following structure:
# other-guest-tools-win.iso
# ├───citrix-9.4.0
# │ ├───XenBus
# │ ├───XenIface
# │ ├───XenNet
# │ ├───XenVbd
# │ ├───XenVif
# │ └───managementagent-9.4.0-x64.msi
# ├───xcp-ng-8.2.2.200
# │ └───managementagentx64.msi
# ├───xcp-ng-9.0.9000
# │ ├───package
# │ │ └───XenDrivers-x64.msi
# │ └───testsign
# │ └───*.crt
# └───install-drivers.ps1

ERROR_SUCCESS = 0
ERROR_INSTALL_FAILURE = 1603
ERROR_SUCCESS_REBOOT_INITIATED = 1641
ERROR_SUCCESS_REBOOT_REQUIRED = 3010


class PowerAction(enum.Enum):
Nothing = "nothing"
Shutdown = "shutdown"
Reboot = "reboot"


@pytest.fixture(scope="module")
def guest_tools_iso(host: Host):
vdi = host.import_iso(ISO_DOWNLOAD_URL + "guest-tools-win.iso", host.iso_sr_uuid())
yield vdi
vdi.destroy()


@pytest.fixture(scope="module")
def other_tools_iso(host: Host):
vdi = host.import_iso(ISO_DOWNLOAD_URL + "other-guest-tools-win.iso", host.iso_sr_uuid())
yield vdi
vdi.destroy()


def install_guest_tools(vm: VM, action: PowerAction, check: bool = True):
msiexec_args = "-i C:\\XenDrivers-x64.msi -log C:\\tools_install.log -passive -norestart"

if action == PowerAction.Nothing:
exitcode = vm.run_powershell_command("msiexec.exe", msiexec_args)
else:
if check:
raise Exception(f"Cannot check exit code with {action} action")
# when powershell runs msiexec it doesn't wait for it to end unlike ssh
# it only waits for stdin closing so we need Start-Process -Wait here
install_cmd = f"Start-Process -Wait msiexec.exe -ArgumentList '{msiexec_args}'"
if action != PowerAction.Nothing:
install_cmd += ";Stop-Computer -Force"
vm.start_background_powershell(install_cmd)
if action != PowerAction.Nothing:
wait_for(vm.is_halted, "Wait for VM halted")
if action == PowerAction.Reboot:
vm.start()
wait_for_vm_running_and_ssh_up_without_tools(vm)
exitcode = None

if check:
assert exitcode in [ERROR_SUCCESS, ERROR_SUCCESS_REBOOT_INITIATED, ERROR_SUCCESS_REBOOT_REQUIRED]
return exitcode


def uninstall_guest_tools(vm: VM, action: PowerAction):
uninstall_cmd = (
"Start-Process -Wait msiexec.exe -ArgumentList "
"'/x C:\\XenDrivers-x64.msi /l* C:\\tools_uninstall.log /passive /norestart'"
)
if action != PowerAction.Nothing:
uninstall_cmd += ";Stop-Computer -Force"
vm.start_background_powershell(uninstall_cmd)
if action != PowerAction.Nothing:
wait_for(vm.is_halted, "Wait for VM halted")
if action == PowerAction.Reboot:
vm.start()
wait_for_vm_running_and_ssh_up_without_tools(vm)


def install_cert_and_tools(vm: VM, guest_tools_iso: VDI, action: PowerAction, check: bool = True):
vm.insert_cd(guest_tools_iso.name())
wait_for(lambda: vm.path_exists("D:/"))

logging.info("Install VM root certs")
vm.ssh("certutil -addstore -f Root D:/testsign/XCP-ng_Test_Signer.crt")
vm.ssh("certutil -addstore -f TrustedPublisher D:/testsign/XCP-ng_Test_Signer.crt")

logging.info("Copy Windows PV drivers to VM")
vm.ssh(
"powershell.exe -noprofile -noninteractive Copy-Item -Force D:/package/XenDrivers-x64.msi C:/XenDrivers-x64.msi"
)

vm.eject_cd()
logging.info("Install Windows PV drivers")
return install_guest_tools(vm, action=action, check=check)


def install_other_drivers(vm: VM, other_tools_iso: VDI, driver_name: str, is_msi: bool):
vm.insert_cd(other_tools_iso.name())
wait_for(lambda: vm.path_exists("D:/"))

install_cmd = "D:\\install-drivers.ps1 -Shutdown "
if is_msi:
logging.info(f"Install {driver_name} MSI drivers")
install_cmd += f'-MsiPath "D:\\{driver_name}" '
else:
logging.info(f"Install {driver_name} drivers")
install_cmd += f'-DriverPath "D:\\{driver_name}" '
install_cmd += ">C:\\othertools.log"
vm.start_background_powershell(install_cmd)
wait_for(vm.is_halted, "Shutdown VM")

vm.eject_cd()
vm.start()
wait_for_vm_running_and_ssh_up_without_tools(vm)


@pytest.fixture(scope="module")
def running_windows_vm_without_tools(imported_vm: VM) -> VM:
vm = imported_vm
if not vm.is_running():
vm.start()
wait_for(vm.is_running, "Wait for VM running")
# whenever the guest changes its serial port config, xl console will drop out
# retry several times to force xl console to refresh
wait_for(lambda: try_get_and_store_vm_ip_serial(vm, timeout=10), "Wait for VM IP", 300)
logging.info(f"VM IP: {vm.ip}")
wait_for(vm.is_ssh_up, "Wait for VM SSH up")
return vm
# no teardown


@pytest.fixture(scope="module")
def unsealed_windows_vm_and_snapshot(running_windows_vm_without_tools: VM):
"""Unseal VM and get its IP, then shut it down. Cache the unsealed state in a snapshot to save time."""
vm = running_windows_vm_without_tools
# vm shutdown is not usable yet (there's no tools)
vm.ssh(["powershell.exe", "-noprofile", "-noninteractive", "Stop-Computer", "-Force"])
wait_for(vm.is_halted, "Shutdown VM")
snapshot = vm.snapshot()
yield vm, snapshot
snapshot.destroy(verify=True)


@pytest.fixture
def running_unsealed_windows_vm(unsealed_windows_vm_and_snapshot: tuple[VM, Snapshot]):
vm, snapshot = unsealed_windows_vm_and_snapshot
vm.start()
wait_for_vm_running_and_ssh_up_without_tools(vm)
yield vm
snapshot.revert()


@pytest.mark.multi_vms
@pytest.mark.usefixtures("windows_vm")
class TestGuestToolsWindows:
@pytest.fixture(scope="class")
def vm_install_test_tools(self, unsealed_windows_vm_and_snapshot, guest_tools_iso):
vm, snapshot = unsealed_windows_vm_and_snapshot
vm.start()
wait_for_vm_running_and_ssh_up_without_tools(vm)
install_cert_and_tools(vm, guest_tools_iso, PowerAction.Reboot, check=False)
yield vm
snapshot.revert()

def test_tools_after_reboot(self, vm_install_test_tools: VM):
vm = vm_install_test_tools
assert vm.are_windows_drivers_installed()

def test_drivers_detected(self, vm_install_test_tools: VM):
vm = vm_install_test_tools
assert vm.param_get("PV-drivers-detected")


@pytest.mark.multi_vms
@pytest.mark.usefixtures("windows_vm")
class TestGuestToolsWindowsDestructive:
@pytest.fixture
def vm_install_test_tools(self, running_unsealed_windows_vm: VM, guest_tools_iso):
install_cert_and_tools(running_unsealed_windows_vm, guest_tools_iso, PowerAction.Nothing)
return running_unsealed_windows_vm

@pytest.fixture
def vm_install_citrix_tools(self, running_unsealed_windows_vm: VM, other_tools_iso: VDI):
install_other_drivers(
running_unsealed_windows_vm, other_tools_iso, "citrix-9.4.0\\managementagent-9.4.0-x64.msi", is_msi=True
)
return running_unsealed_windows_vm

@pytest.fixture
def vm_install_xcpng_v8_tools(self, running_unsealed_windows_vm: VM, other_tools_iso: VDI):
install_other_drivers(
running_unsealed_windows_vm, other_tools_iso, "xcp-ng-8.2.2.200\\managementagentx64.msi", is_msi=True
)
return running_unsealed_windows_vm

@pytest.fixture
def vm_install_xcpng_v9_tools(self, running_unsealed_windows_vm: VM, other_tools_iso: VDI):
vm = running_unsealed_windows_vm

vm.insert_cd(other_tools_iso.name())
wait_for(lambda: vm.path_exists("D:/"))

logging.info("Install VM root certs")
vm.ssh("certutil -addstore -f Root D:/xcp-ng-9.0.9000/testsign/XCP-ng_Test_Signer.crt")
vm.ssh("certutil -addstore -f TrustedPublisher D:/xcp-ng-9.0.9000/testsign/XCP-ng_Test_Signer.crt")

vm.eject_cd()

install_other_drivers(vm, other_tools_iso, "xcp-ng-9.0.9000\\package\\XenDrivers-x64.msi", is_msi=True)
return vm

@pytest.fixture
def vm_install_citrix_vendor_drivers(self, unsealed_windows_vm_and_snapshot: VM, other_tools_iso: VDI):
vm, snapshot = unsealed_windows_vm_and_snapshot
assert not vm.param_get("has-vendor-device")
vm.param_set("has-vendor-device", True)
vm.start()
wait_for_vm_running_and_ssh_up_without_tools(vm)
install_other_drivers(vm, other_tools_iso, "citrix-9.4.0", is_msi=False)
yield vm
snapshot.revert()

def test_uninstall_tools(self, vm_install_test_tools: VM):
vm = vm_install_test_tools
vm.reboot()
wait_for_vm_running_and_ssh_up_without_tools(vm)
logging.info("Uninstall Windows PV drivers")
uninstall_guest_tools(vm, action=PowerAction.Reboot)
assert not vm.are_windows_drivers_installed()

def test_uninstall_tools_early(self, vm_install_test_tools: VM):
vm = vm_install_test_tools
logging.info("Uninstall Windows PV drivers before rebooting")
uninstall_guest_tools(vm, action=PowerAction.Reboot)
assert not vm.are_windows_drivers_installed()

def test_reinstall_tools_early(self, vm_install_test_tools: VM):
vm = vm_install_test_tools
vm.reboot()
wait_for_vm_running_and_ssh_up_without_tools(vm)
logging.info("Uninstall Windows PV drivers before reinstalling")
uninstall_guest_tools(vm, action=PowerAction.Nothing)
install_guest_tools(vm, action=PowerAction.Reboot, check=False)
assert vm.are_windows_drivers_installed()

def test_install_with_citrix_tools(self, vm_install_citrix_tools: VM, guest_tools_iso: VDI):
exitcode = install_cert_and_tools(vm_install_citrix_tools, guest_tools_iso, PowerAction.Nothing, check=False)
assert exitcode == ERROR_INSTALL_FAILURE

def test_install_with_xcpng_v8_tools(self, vm_install_xcpng_v8_tools: VM, guest_tools_iso: VDI):
exitcode = install_cert_and_tools(vm_install_xcpng_v8_tools, guest_tools_iso, PowerAction.Nothing, check=False)
assert exitcode == ERROR_INSTALL_FAILURE

def test_upgrade_with_xcpng_v9_tools(self, vm_install_xcpng_v9_tools: VM, guest_tools_iso: VDI):
vm = vm_install_xcpng_v9_tools
install_cert_and_tools(vm, guest_tools_iso, PowerAction.Reboot, check=False)
assert vm.are_windows_drivers_installed()

def test_install_with_citrix_vendor_drivers(self, vm_install_citrix_vendor_drivers: VM, guest_tools_iso: VDI):
exitcode = install_cert_and_tools(
vm_install_citrix_vendor_drivers, guest_tools_iso, PowerAction.Nothing, check=False
)
assert exitcode == ERROR_INSTALL_FAILURE
4 changes: 4 additions & 0 deletions vm_data.py-dist
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ VMS = {
"small_vm_windows": "",
# Debian VM (UEFI, no GUI)
"debian_uefi_vm": "",
# "small" Windows VM with testsign enabled
"small_vm_windows_testsign": "",
},
"multi": {
# all VMs we want to run "multi_vms" tests on
Expand All @@ -48,6 +50,8 @@ VMS = {
"uefi_unix": [],
# UEFI Windows VMs
"uefi_windows": [],
# Testsign UEFI Windows VMs
"tools_windows": [],
}
}

Expand Down

0 comments on commit c71c263

Please sign in to comment.