-
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
Signed-off-by: Tu Dinh <[email protected]>
- Loading branch information
Tu Dinh
committed
Nov 28, 2024
1 parent
485df19
commit 25cea38
Showing
8 changed files
with
404 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
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,45 @@ | ||
import enum | ||
import logging | ||
import re | ||
from typing import Any | ||
from data import ISO_DOWNLOAD_URL | ||
from lib.common import wait_for | ||
from lib.host import Host | ||
from lib.vm import VM | ||
|
||
|
||
class PowerAction(enum.Enum): | ||
Nothing = "nothing" | ||
Shutdown = "shutdown" | ||
Reboot = "reboot" | ||
|
||
|
||
def iso_create(host: Host, param: dict[str, Any]): | ||
if param["download"]: | ||
vdi = host.import_iso(ISO_DOWNLOAD_URL + param["name"], host.iso_sr_uuid()) | ||
new_param = param.copy() | ||
new_param["name"] = vdi.name() | ||
yield new_param | ||
vdi.destroy() | ||
else: | ||
yield param | ||
|
||
|
||
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,91 @@ | ||
import logging | ||
from typing import Any | ||
import pytest | ||
|
||
from data import OTHER_GUEST_TOOLS, OTHER_GUEST_TOOLS_ISO, WIN_GUEST_TOOLS_ISOS | ||
from lib.common import wait_for | ||
from lib.host import Host | ||
from lib.snapshot import Snapshot | ||
from lib.vm import VM | ||
from . import PowerAction, iso_create, try_get_and_store_vm_ip_serial, wait_for_vm_running_and_ssh_up_without_tools | ||
from .guest_tools import install_guest_tools | ||
from .other_tools import install_other_drivers | ||
|
||
|
||
@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.fixture(scope="class") | ||
def vm_install_test_tools_per_test_class(unsealed_windows_vm_and_snapshot, guest_tools_iso: dict[str, Any]): | ||
vm, snapshot = unsealed_windows_vm_and_snapshot | ||
vm.start() | ||
wait_for_vm_running_and_ssh_up_without_tools(vm) | ||
install_guest_tools(vm, guest_tools_iso, PowerAction.Reboot, check=False) | ||
yield vm | ||
snapshot.revert() | ||
|
||
|
||
@pytest.fixture | ||
def vm_install_test_tools(running_unsealed_windows_vm: VM, guest_tools_iso: dict[str, Any]): | ||
install_guest_tools(running_unsealed_windows_vm, guest_tools_iso, PowerAction.Nothing) | ||
return running_unsealed_windows_vm | ||
|
||
|
||
@pytest.fixture( | ||
scope="module", | ||
ids=list(WIN_GUEST_TOOLS_ISOS.keys()), | ||
params=list(WIN_GUEST_TOOLS_ISOS.values()), | ||
) | ||
def guest_tools_iso(host: Host, request: pytest.FixtureRequest): | ||
yield from iso_create(host, request.param) | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def other_tools_iso(host: Host): | ||
yield from iso_create(host, OTHER_GUEST_TOOLS_ISO) | ||
|
||
|
||
@pytest.fixture(ids=list(OTHER_GUEST_TOOLS.keys()), params=list(OTHER_GUEST_TOOLS.values())) | ||
def vm_install_other_drivers( | ||
unsealed_windows_vm_and_snapshot: tuple[VM, Snapshot], | ||
other_tools_iso: dict[str, Any], | ||
request: pytest.FixtureRequest, | ||
): | ||
vm, snapshot = unsealed_windows_vm_and_snapshot | ||
param = request.param | ||
install_other_drivers(vm, other_tools_iso["name"], param) | ||
yield vm, param | ||
snapshot.revert() |
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,70 @@ | ||
import logging | ||
from pathlib import PureWindowsPath | ||
from typing import Any | ||
|
||
from lib.common import wait_for | ||
from lib.vm import VM | ||
from . import PowerAction, wait_for_vm_running_and_ssh_up_without_tools | ||
|
||
|
||
ERROR_SUCCESS = 0 | ||
ERROR_INSTALL_FAILURE = 1603 | ||
ERROR_SUCCESS_REBOOT_INITIATED = 1641 | ||
ERROR_SUCCESS_REBOOT_REQUIRED = 3010 | ||
|
||
GUEST_TOOLS_COPY_PATH = "C:\\package.msi" | ||
|
||
|
||
def install_guest_tools(vm: VM, guest_tools_iso: dict[str, Any], action: PowerAction, check: bool = True): | ||
vm.insert_cd(guest_tools_iso["name"]) | ||
wait_for(lambda: vm.path_exists("D:/")) | ||
|
||
if guest_tools_iso.get("testsign_cert"): | ||
logging.info("Install VM root certs") | ||
rootcert = PureWindowsPath("D:\\") / guest_tools_iso["testsign_cert"] | ||
vm.execute_powershell_script(f"certutil -addstore -f Root '{rootcert}'") | ||
vm.execute_powershell_script(f"certutil -addstore -f TrustedPublisher '{rootcert}'") | ||
|
||
logging.info("Copy Windows PV drivers to VM") | ||
package_path = PureWindowsPath("D:\\") / guest_tools_iso["package"] | ||
vm.execute_powershell_script(f"Copy-Item -Force '{package_path}' '{GUEST_TOOLS_COPY_PATH}'") | ||
|
||
vm.eject_cd() | ||
|
||
logging.info("Install Windows PV drivers") | ||
msiexec_args = f"/i {GUEST_TOOLS_COPY_PATH} /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): | ||
msiexec_args = f"/x {GUEST_TOOLS_COPY_PATH} /log C:\\tools_uninstall.log /passive /norestart" | ||
uninstall_cmd = f"Start-Process -Wait msiexec.exe -ArgumentList '{msiexec_args}'" | ||
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) |
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,41 @@ | ||
import logging | ||
from pathlib import PureWindowsPath | ||
from typing import Any | ||
|
||
from lib.common import wait_for | ||
from lib.vm import VM | ||
from . import wait_for_vm_running_and_ssh_up_without_tools | ||
|
||
|
||
def install_other_drivers(vm: VM, other_tools_iso_name: str, param: dict[str, Any]): | ||
if param.get("vendor_device"): | ||
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) | ||
|
||
vm.insert_cd(other_tools_iso_name) | ||
wait_for(lambda: vm.path_exists("D:/")) | ||
|
||
if param.get("testsign_cert"): | ||
logging.info("Install VM root certs") | ||
rootcert = PureWindowsPath("D:\\") / param["path"] / param["testsign_cert"] | ||
vm.execute_powershell_script(f"certutil -addstore -f Root '{rootcert}'") | ||
vm.execute_powershell_script(f"certutil -addstore -f TrustedPublisher '{rootcert}'") | ||
|
||
package_path = PureWindowsPath("D:\\") / param["path"] / param["package"] | ||
install_cmd = "D:\\install-drivers.ps1 -Shutdown " | ||
if param["is_msi"]: | ||
logging.info(f"Install MSI drivers: {package_path}") | ||
install_cmd += f"-MsiPath '{package_path}' " | ||
else: | ||
logging.info(f"Install drivers: {package_path}") | ||
install_cmd += f"-DriverPath '{package_path}' " | ||
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) |
Oops, something went wrong.