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