Skip to content

Commit 1b22f19

Browse files
authored
Merge pull request #469 from xcp-ng/dnt/windows
Add Windows tools onboarding tests
2 parents 604a77d + d5875fb commit 1b22f19

5 files changed

Lines changed: 104 additions & 14 deletions

File tree

data.py-dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ WIN_GUEST_TOOLS_ISOS = {
7272
"xenclean_path": "package\\XenClean\\x64\\Invoke-XenClean.ps1",
7373
# ISO-relative path of root cert file to be installed before guest tools (optional)
7474
"testsign_cert": "testsign\\XCP-ng_Test_Signer.crt",
75+
# What's the onboard family of our tools? This is equal to the WinPV VENDOR_NAME value
76+
"onboard_family": "XCP-ng",
7577
},
7678
# Add more guest tool ISOs here as needed
7779
}
@@ -100,6 +102,8 @@ OTHER_GUEST_TOOLS = {
100102

101103
# Can we upgrade automatically from this guest tool to our tools?
102104
"upgradable": True,
105+
# What is the expected onboarding phase after running XenClean when this tool is installed? (optional)
106+
"onboarding_phase": "see test_xenclean.py ONBOARDING_PHASES",
103107
},
104108
"vendor": {
105109
"vendor_device": True,

lib/windows/__init__.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,28 @@ def try_get_and_store_vm_ip_serial(vm: VM, timeout: int):
5151
return True
5252

5353

54+
def vm_shutdown_without_tools(vm: VM):
55+
if vm.is_running():
56+
vm.ssh(WINDOWS_SHUTDOWN_COMMAND)
57+
wait_for(vm.is_halted, "Wait for VM halted")
58+
59+
5460
def wait_for_vm_running_and_ssh_up_without_tools(vm: VM):
5561
wait_for(vm.is_running, "Wait for VM running")
5662
wait_for(vm.is_ssh_up, "Wait for SSH up")
5763

5864

5965
def enable_testsign(vm: VM, rootcert: Union[str, None]):
66+
assert vm.is_running()
67+
68+
if strtobool(vm.param_get("platform", "secureboot")):
69+
logging.info("Disable secure boot on test image")
70+
71+
vm_shutdown_without_tools(vm)
72+
vm.param_set('platform', False, key='secureboot')
73+
vm.start()
74+
wait_for_vm_running_and_ssh_up_without_tools(vm)
75+
6076
if rootcert is not None:
6177
vm.execute_powershell_script(
6278
f"""certutil -addstore -f Root '{rootcert}';
@@ -100,9 +116,7 @@ def insert_cd_safe(vm: VM, vdi_name: str, cd_path="D:/", retries=2):
100116
return
101117
except TimeoutError:
102118
logging.warning(f"Waiting for CD at {cd_path} failed, retrying by rebooting VM")
103-
# There might be no VM tools so use SSH instead.
104-
vm.ssh(WINDOWS_SHUTDOWN_COMMAND)
105-
wait_for(vm.is_halted, "Wait for VM halted")
119+
vm_shutdown_without_tools(vm)
106120

107121
raise TimeoutError(f"Waiting for CD at {cd_path} failed")
108122

tests/guest_tools/win/conftest.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from lib.sr import SR
1010
from lib.vm import VM
1111
from lib.windows import (
12-
WINDOWS_SHUTDOWN_COMMAND,
1312
PowerAction,
1413
iso_create,
1514
try_get_and_store_vm_ip_serial,
15+
vm_shutdown_without_tools,
1616
wait_for_vm_running_and_ssh_up_without_tools,
1717
)
1818
from lib.windows.guest_tools import install_guest_tools
@@ -39,9 +39,7 @@ def running_windows_vm_without_tools(imported_vm: VM) -> VM:
3939
def unsealed_windows_vm_and_snapshot(running_windows_vm_without_tools: VM):
4040
"""Unseal VM and get its IP, then shut it down. Cache the unsealed state in a snapshot to save time."""
4141
vm = running_windows_vm_without_tools
42-
# vm.shutdown is not usable yet (there's no tools).
43-
vm.ssh(WINDOWS_SHUTDOWN_COMMAND)
44-
wait_for(vm.is_halted, "Shutdown VM")
42+
vm_shutdown_without_tools(vm)
4543
snapshot = vm.snapshot()
4644
yield vm, snapshot
4745
snapshot.destroy(verify=True)

tests/guest_tools/win/test_guest_tools_win.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
from lib.common import strtobool, wait_for
88
from lib.vm import VM
99
from lib.windows import (
10-
WINDOWS_SHUTDOWN_COMMAND,
1110
PowerAction,
1211
check_vm_clipboard,
1312
check_vm_distro,
1413
check_vm_dns,
1514
set_vm_dns,
1615
vif_has_rss,
16+
vm_shutdown_without_tools,
1717
wait_for_vm_running_and_ssh_up_without_tools,
1818
)
1919
from lib.windows.guest_tools import (
@@ -146,9 +146,7 @@ def test_xenvbd_ssd(self, vm_install_test_tools_per_test_class: VM):
146146
class TestGuestToolsWindowsDestructive:
147147
def test_uninstall_tools(self, vm_install_test_tools_no_reboot: VM):
148148
vm = vm_install_test_tools_no_reboot
149-
vm.ssh(WINDOWS_SHUTDOWN_COMMAND)
150-
wait_for(vm.is_halted, "Shutdown VM")
151-
149+
vm_shutdown_without_tools(vm)
152150
vm.start()
153151
wait_for_vm_running_and_ssh_up_without_tools(vm)
154152

tests/guest_tools/win/test_xenclean.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,56 @@
1414
wait_for_vm_xenvif_offboard,
1515
)
1616

17-
from typing import Any, Dict, Tuple
17+
from typing import Any, Dict, Literal, Tuple, overload
1818

1919
# Test uninstallation of other drivers using the XenClean program.
2020

21+
ONBOARDING_PHASES = {
22+
0: "CleaningSucceeded",
23+
1: "Error",
24+
2: "UserCanceled",
25+
3: "RebootPending",
26+
64: "ReadyForOnboard",
27+
65: "AlreadyOnboarded",
28+
66: "OnboardDenied",
29+
}
2130

22-
def run_xenclean(vm: VM, guest_tools_iso: Dict[str, Any]):
31+
ONBOARD_EXIT_CODE_FILE = "C:\\onboard.txt"
32+
33+
34+
@overload
35+
def run_xenclean(vm: VM, guest_tools_iso: Dict[str, Any], onboard: Literal[False] = False) -> None: #
36+
...
37+
38+
39+
@overload
40+
def run_xenclean(vm: VM, guest_tools_iso: Dict[str, Any], onboard: Literal[True]) -> str: #
41+
...
42+
43+
44+
def run_xenclean(vm: VM, guest_tools_iso: Dict[str, Any], onboard: bool = False):
45+
"""
46+
Run XenClean from the provided guest tools.
47+
48+
A note on guest tools onboarding with XenClean:
49+
Onboarding is the transition from one guest tool to another, typically driven externally by repeatedly running
50+
XenClean. XenClean will exit with one of the exit codes documented in ONBOARDING_PHASES.
51+
"""
2352
insert_cd_safe(vm, guest_tools_iso["name"])
2453

2554
logging.info("Run XenClean")
2655
xenclean_path = PureWindowsPath("D:\\") / guest_tools_iso["xenclean_path"]
2756
if guest_tools_iso["xenclean_path"].lower().endswith(".ps1"):
57+
assert not onboard, "Onboarding not supported in older versions"
2858
xenclean_cmd = f"Set-Location C:\\; {xenclean_path} -NoReboot -Confirm:$false; {WINDOWS_SHUTDOWN_COMMAND}"
2959
else:
30-
xenclean_cmd = f"Set-Location C:\\; {xenclean_path} -noReboot -noConfirm; {WINDOWS_SHUTDOWN_COMMAND}"
60+
xenclean_cmd = f"Set-Location C:\\; {xenclean_path} -noReboot -noConfirm"
61+
if onboard:
62+
onboard_family = guest_tools_iso["onboard_family"]
63+
xenclean_cmd += f" -onboard {onboard_family}; Set-Content {ONBOARD_EXIT_CODE_FILE} $LASTEXITCODE -Force"
64+
else:
65+
xenclean_cmd += "; if ($LASTEXITCODE -ne 0) {{throw}}"
66+
xenclean_cmd += f"; {WINDOWS_SHUTDOWN_COMMAND}"
3167
vm.start_background_powershell(xenclean_cmd)
3268

3369
# XenClean sometimes takes a bit long due to all the calls to the uninstallers. We need an extended timeout.
@@ -37,6 +73,20 @@ def run_xenclean(vm: VM, guest_tools_iso: Dict[str, Any]):
3773
vm.start()
3874
wait_for_vm_running_and_ssh_up_without_tools(vm)
3975
wait_for_vm_xenvif_offboard(vm)
76+
if onboard:
77+
exitcode = vm.execute_powershell_script(f"Get-Content {ONBOARD_EXIT_CODE_FILE} -ErrorAction SilentlyContinue")
78+
logging.debug(f"Onboarding exit code: {exitcode}")
79+
assert exitcode, "Expected exit code string"
80+
onboarding_phase = ONBOARDING_PHASES[int(exitcode)]
81+
logging.info(f"Onboarding phase: {onboarding_phase}")
82+
return onboarding_phase
83+
84+
85+
@pytest.fixture(scope="module")
86+
def onboarding_guest_tools_iso(guest_tools_iso):
87+
if not guest_tools_iso.get("onboard_family"):
88+
pytest.skip("Onboarding info not declared in data.py")
89+
return guest_tools_iso
4090

4191

4292
@pytest.mark.multi_vms
@@ -48,6 +98,11 @@ def test_xenclean_without_tools(self, running_unsealed_windows_vm: VM, guest_too
4898
run_xenclean(vm, guest_tools_iso)
4999
assert vm.are_windows_tools_uninstalled()
50100

101+
def test_xenclean_onboard_without_tools(self, running_unsealed_windows_vm: VM, onboarding_guest_tools_iso):
102+
vm = running_unsealed_windows_vm
103+
logging.info("XenClean onboard with empty VM")
104+
assert run_xenclean(vm, onboarding_guest_tools_iso, onboard=True) == "ReadyForOnboard"
105+
51106
def test_xenclean_with_test_tools_early(self, vm_install_test_tools_no_reboot: VM, guest_tools_iso):
52107
vm = vm_install_test_tools_no_reboot
53108
logging.info("XenClean with test tools (without reboot)")
@@ -68,6 +123,16 @@ def test_xenclean_with_test_tools(self, vm_install_test_tools_no_reboot: VM, gue
68123
assert vm.are_windows_tools_uninstalled()
69124
check_vm_dns(vm)
70125

126+
def test_xenclean_onboard_with_test_tools(self, vm_install_test_tools_no_reboot: VM, onboarding_guest_tools_iso):
127+
vm = vm_install_test_tools_no_reboot
128+
vm.reboot()
129+
wait_for_vm_running_and_ssh_up_without_tools(vm)
130+
131+
logging.info("XenClean onboard with test tools")
132+
assert run_xenclean(vm, onboarding_guest_tools_iso, onboard=True) == "AlreadyOnboarded"
133+
logging.info("Check tools still working")
134+
assert vm.are_windows_tools_working()
135+
71136
def test_xenclean_with_other_tools(self, vm_install_other_drivers: Tuple[VM, Dict], guest_tools_iso):
72137
vm, param = vm_install_other_drivers
73138
if param.get("vendor_device"):
@@ -79,3 +144,14 @@ def test_xenclean_with_other_tools(self, vm_install_other_drivers: Tuple[VM, Dic
79144
logging.info("Check tools uninstalled")
80145
assert vm.are_windows_tools_uninstalled()
81146
check_vm_dns(vm)
147+
148+
def test_xenclean_onboard_with_other_tools(
149+
self, vm_install_other_drivers: Tuple[VM, Dict], onboarding_guest_tools_iso
150+
):
151+
vm, param = vm_install_other_drivers
152+
onboarding_phase = param.get("onboarding_phase")
153+
if not param.get("onboarding_phase"):
154+
pytest.skip("Skipping XenClean on other tools with no defined onboarding phase")
155+
156+
logging.info("XenClean onboard with other tools")
157+
assert run_xenclean(vm, onboarding_guest_tools_iso, onboard=True) == onboarding_phase

0 commit comments

Comments
 (0)