-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Windows tools install/uninstall/upgrade tests
- Loading branch information
Tu Dinh
committed
Nov 26, 2024
1 parent
8de6367
commit 23f3981
Showing
4 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters