Skip to content

Commit 142b565

Browse files
committed
Move Windows guest util functions to lib/windows
Signed-off-by: Tu Dinh <[email protected]>
1 parent b3a585a commit 142b565

File tree

7 files changed

+184
-187
lines changed

7 files changed

+184
-187
lines changed

lib/windows/__init__.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import enum
2+
import logging
3+
import re
4+
import time
5+
6+
from data import ISO_DOWNLOAD_URL, TEST_DNS_SERVER
7+
from lib.commands import SSHCommandFailed
8+
from lib.common import strtobool, wait_for
9+
from lib.host import Host
10+
from lib.sr import SR
11+
from lib.vif import VIF
12+
from lib.vm import VM
13+
14+
from typing import Any, Dict, List, Union
15+
16+
# HACK: I originally thought that using Stop-Computer -Force would cause the SSH session to sometimes fail.
17+
# I could never confirm this in the end, but use a slightly delayed shutdown just to be safe anyway.
18+
WINDOWS_SHUTDOWN_COMMAND = "shutdown.exe -s -f -t 5"
19+
20+
21+
class PowerAction(enum.Enum):
22+
Nothing = "nothing"
23+
Shutdown = "shutdown"
24+
Reboot = "reboot"
25+
26+
27+
def iso_create(host: Host, sr: SR, param: Dict[str, Any]):
28+
if param["download"]:
29+
vdi = host.import_iso(ISO_DOWNLOAD_URL + param["name"], sr)
30+
new_param = param.copy()
31+
new_param["name"] = vdi.name()
32+
yield new_param
33+
vdi.destroy()
34+
else:
35+
yield param
36+
37+
38+
def try_get_and_store_vm_ip_serial(vm: VM, timeout: int):
39+
domid = vm.param_get("dom-id")
40+
logging.debug(f"Domain ID {domid}")
41+
command = f"xl console -t serial {domid} | grep '~xcp-ng-tests~.*~end~' | head -n 1"
42+
if timeout > 0:
43+
command = f"timeout {timeout} " + command
44+
res_host = vm.get_residence_host()
45+
report = res_host.ssh(command)
46+
logging.debug(f"Got report: {report}")
47+
match = re.match("~xcp-ng-tests~(.*)=(.*)~end~", report)
48+
if not match:
49+
return False
50+
vm.ip = match[2]
51+
return True
52+
53+
54+
def wait_for_vm_running_and_ssh_up_without_tools(vm: VM):
55+
wait_for(vm.is_running, "Wait for VM running")
56+
wait_for(vm.is_ssh_up, "Wait for SSH up")
57+
58+
59+
def enable_testsign(vm: VM, rootcert: Union[str, None]):
60+
if rootcert is not None:
61+
vm.execute_powershell_script(
62+
f"""certutil -addstore -f Root '{rootcert}';
63+
if ($LASTEXITCODE -ne 0) {{throw}}
64+
certutil -addstore -f TrustedPublisher '{rootcert}';
65+
if ($LASTEXITCODE -ne 0) {{throw}}"""
66+
)
67+
vm.execute_powershell_script(
68+
"bcdedit /set testsigning on; if ($LASTEXITCODE -ne 0) {throw};" + WINDOWS_SHUTDOWN_COMMAND
69+
)
70+
wait_for(vm.is_halted, "Wait for VM halted")
71+
vm.start()
72+
wait_for_vm_running_and_ssh_up_without_tools(vm)
73+
74+
75+
def insert_cd_safe(vm: VM, vdi_name: str, cd_path="D:/", retries=2):
76+
"""
77+
Insert a CD with retry.
78+
79+
HACK: Windows VM may not detect the CD drive in some cases.
80+
If this happens, reboot the VM in hopes that it will be detected.
81+
"""
82+
for _attempt in range(retries):
83+
# Eject the existing CD just in case.
84+
try:
85+
vm.eject_cd()
86+
# Leave some time for the guest to realize its CD got ejected.
87+
time.sleep(5)
88+
except SSHCommandFailed:
89+
pass
90+
91+
vm.insert_cd(vdi_name)
92+
if not vm.is_running():
93+
vm.start()
94+
# wait_for(vm.file_exists) doesn't handle SSHCommandFailed;
95+
# we need to check for it via wait_for(vm.is_ssh_up).
96+
wait_for_vm_running_and_ssh_up_without_tools(vm)
97+
98+
try:
99+
wait_for(lambda: vm.file_exists(cd_path, regular_file=False), timeout_secs=60)
100+
return
101+
except TimeoutError:
102+
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")
106+
107+
raise TimeoutError(f"Waiting for CD at {cd_path} failed")
108+
109+
110+
def vif_get_mac_without_separator(vif: VIF):
111+
mac = vif.param_get("MAC")
112+
assert mac is not None
113+
return mac.replace(":", "")
114+
115+
116+
def vif_has_rss(vif: VIF):
117+
# Even if the Xenvif hash setting request fails, Windows can still report the NIC as having RSS enabled as long as
118+
# the relevant OIDs are supported (Get-NetAdapterRss reports Enabled as True and Profile as Default).
119+
# We need to explicitly check MaxProcessors to see if the hash setting request has really succeeded.
120+
mac = vif_get_mac_without_separator(vif)
121+
return strtobool(
122+
vif.vm.execute_powershell_script(
123+
rf"""(Get-NetAdapter |
124+
Where-Object {{$_.PnPDeviceID -notlike 'root\kdnic\*' -and $_.PermanentAddress -eq '{mac}'}} |
125+
Get-NetAdapterRss).MaxProcessors -gt 0"""
126+
)
127+
)
128+
129+
130+
def vif_get_dns(vif: VIF):
131+
mac = vif_get_mac_without_separator(vif)
132+
return vif.vm.execute_powershell_script(
133+
rf"""Import-Module DnsClient; Get-NetAdapter |
134+
Where-Object {{$_.PnPDeviceID -notlike 'root\kdnic\*' -and $_.PermanentAddress -eq '{mac}'}} |
135+
Get-DnsClientServerAddress -AddressFamily IPv4 |
136+
Select-Object -ExpandProperty ServerAddresses"""
137+
).splitlines()
138+
139+
140+
def vif_set_dns(vif: VIF, nameservers: List[str]):
141+
mac = vif_get_mac_without_separator(vif)
142+
vif.vm.execute_powershell_script(
143+
rf"""Import-Module DnsClient; Get-NetAdapter |
144+
Where-Object {{$_.PnPDeviceID -notlike 'root\kdnic\*' -and $_.PermanentAddress -eq '{mac}'}} |
145+
Get-DnsClientServerAddress -AddressFamily IPv4 |
146+
Set-DnsClientServerAddress -ServerAddresses {",".join(nameservers)}"""
147+
)
148+
149+
150+
def wait_for_vm_xenvif_offboard(vm: VM):
151+
# Xenvif offboard will reset the NIC, so need to wait for it to disappear first
152+
wait_for(
153+
lambda: strtobool(
154+
vm.execute_powershell_script(
155+
r'$null -eq (Get-ScheduledTask "Copy-XenVifSettings" -ErrorAction SilentlyContinue)', simple_output=True
156+
)
157+
),
158+
timeout_secs=300,
159+
retry_delay_secs=30,
160+
)
161+
162+
163+
def set_vm_dns(vm: VM):
164+
logging.info(f"Set VM DNS to {TEST_DNS_SERVER}")
165+
vif = vm.vifs()[0]
166+
assert TEST_DNS_SERVER not in vif_get_dns(vif)
167+
vif_set_dns(vif, [TEST_DNS_SERVER])
168+
169+
170+
def check_vm_dns(vm: VM):
171+
# The restore task takes time to fire so wait for it
172+
vif = vm.vifs()[0]
173+
wait_for(
174+
lambda: TEST_DNS_SERVER in vif_get_dns(vif),
175+
f"Check VM DNS contains {TEST_DNS_SERVER}",
176+
timeout_secs=300,
177+
retry_delay_secs=30,
178+
)

tests/guest_tools/win/__init__.py

Lines changed: 0 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +0,0 @@
1-
import enum
2-
import logging
3-
import re
4-
import time
5-
6-
from data import ISO_DOWNLOAD_URL, TEST_DNS_SERVER
7-
from lib.commands import SSHCommandFailed
8-
from lib.common import strtobool, wait_for
9-
from lib.host import Host
10-
from lib.sr import SR
11-
from lib.vif import VIF
12-
from lib.vm import VM
13-
14-
from typing import Any, Dict, List, Union
15-
16-
# HACK: I originally thought that using Stop-Computer -Force would cause the SSH session to sometimes fail.
17-
# I could never confirm this in the end, but use a slightly delayed shutdown just to be safe anyway.
18-
WINDOWS_SHUTDOWN_COMMAND = "shutdown.exe -s -f -t 5"
19-
20-
21-
class PowerAction(enum.Enum):
22-
Nothing = "nothing"
23-
Shutdown = "shutdown"
24-
Reboot = "reboot"
25-
26-
27-
def iso_create(host: Host, sr: SR, param: Dict[str, Any]):
28-
if param["download"]:
29-
vdi = host.import_iso(ISO_DOWNLOAD_URL + param["name"], sr)
30-
new_param = param.copy()
31-
new_param["name"] = vdi.name()
32-
yield new_param
33-
vdi.destroy()
34-
else:
35-
yield param
36-
37-
38-
def try_get_and_store_vm_ip_serial(vm: VM, timeout: int):
39-
domid = vm.param_get("dom-id")
40-
logging.debug(f"Domain ID {domid}")
41-
command = f"xl console -t serial {domid} | grep '~xcp-ng-tests~.*~end~' | head -n 1"
42-
if timeout > 0:
43-
command = f"timeout {timeout} " + command
44-
res_host = vm.get_residence_host()
45-
report = res_host.ssh(command)
46-
logging.debug(f"Got report: {report}")
47-
match = re.match("~xcp-ng-tests~(.*)=(.*)~end~", report)
48-
if not match:
49-
return False
50-
vm.ip = match[2]
51-
return True
52-
53-
54-
def wait_for_vm_running_and_ssh_up_without_tools(vm: VM):
55-
wait_for(vm.is_running, "Wait for VM running")
56-
wait_for(vm.is_ssh_up, "Wait for SSH up")
57-
58-
59-
def enable_testsign(vm: VM, rootcert: Union[str, None]):
60-
if rootcert is not None:
61-
vm.execute_powershell_script(
62-
f"""certutil -addstore -f Root '{rootcert}';
63-
if ($LASTEXITCODE -ne 0) {{throw}}
64-
certutil -addstore -f TrustedPublisher '{rootcert}';
65-
if ($LASTEXITCODE -ne 0) {{throw}}"""
66-
)
67-
vm.execute_powershell_script(
68-
"bcdedit /set testsigning on; if ($LASTEXITCODE -ne 0) {throw};" + WINDOWS_SHUTDOWN_COMMAND
69-
)
70-
wait_for(vm.is_halted, "Wait for VM halted")
71-
vm.start()
72-
wait_for_vm_running_and_ssh_up_without_tools(vm)
73-
74-
75-
def insert_cd_safe(vm: VM, vdi_name: str, cd_path="D:/", retries=2):
76-
"""
77-
Insert a CD with retry.
78-
79-
HACK: Windows VM may not detect the CD drive in some cases.
80-
If this happens, reboot the VM in hopes that it will be detected.
81-
"""
82-
for _attempt in range(retries):
83-
# Eject the existing CD just in case.
84-
try:
85-
vm.eject_cd()
86-
# Leave some time for the guest to realize its CD got ejected.
87-
time.sleep(5)
88-
except SSHCommandFailed:
89-
pass
90-
91-
vm.insert_cd(vdi_name)
92-
if not vm.is_running():
93-
vm.start()
94-
# wait_for(vm.file_exists) doesn't handle SSHCommandFailed;
95-
# we need to check for it via wait_for(vm.is_ssh_up).
96-
wait_for_vm_running_and_ssh_up_without_tools(vm)
97-
98-
try:
99-
wait_for(lambda: vm.file_exists(cd_path, regular_file=False), timeout_secs=60)
100-
return
101-
except TimeoutError:
102-
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")
106-
107-
raise TimeoutError(f"Waiting for CD at {cd_path} failed")
108-
109-
110-
def vif_get_mac_without_separator(vif: VIF):
111-
mac = vif.param_get("MAC")
112-
assert mac is not None
113-
return mac.replace(":", "")
114-
115-
116-
def vif_has_rss(vif: VIF):
117-
# Even if the Xenvif hash setting request fails, Windows can still report the NIC as having RSS enabled as long as
118-
# the relevant OIDs are supported (Get-NetAdapterRss reports Enabled as True and Profile as Default).
119-
# We need to explicitly check MaxProcessors to see if the hash setting request has really succeeded.
120-
mac = vif_get_mac_without_separator(vif)
121-
return strtobool(
122-
vif.vm.execute_powershell_script(
123-
rf"""(Get-NetAdapter |
124-
Where-Object {{$_.PnPDeviceID -notlike 'root\kdnic\*' -and $_.PermanentAddress -eq '{mac}'}} |
125-
Get-NetAdapterRss).MaxProcessors -gt 0"""
126-
)
127-
)
128-
129-
130-
def vif_get_dns(vif: VIF):
131-
mac = vif_get_mac_without_separator(vif)
132-
return vif.vm.execute_powershell_script(
133-
rf"""Import-Module DnsClient; Get-NetAdapter |
134-
Where-Object {{$_.PnPDeviceID -notlike 'root\kdnic\*' -and $_.PermanentAddress -eq '{mac}'}} |
135-
Get-DnsClientServerAddress -AddressFamily IPv4 |
136-
Select-Object -ExpandProperty ServerAddresses"""
137-
).splitlines()
138-
139-
140-
def vif_set_dns(vif: VIF, nameservers: List[str]):
141-
mac = vif_get_mac_without_separator(vif)
142-
vif.vm.execute_powershell_script(
143-
rf"""Import-Module DnsClient; Get-NetAdapter |
144-
Where-Object {{$_.PnPDeviceID -notlike 'root\kdnic\*' -and $_.PermanentAddress -eq '{mac}'}} |
145-
Get-DnsClientServerAddress -AddressFamily IPv4 |
146-
Set-DnsClientServerAddress -ServerAddresses {",".join(nameservers)}"""
147-
)
148-
149-
150-
def wait_for_vm_xenvif_offboard(vm: VM):
151-
# Xenvif offboard will reset the NIC, so need to wait for it to disappear first
152-
wait_for(
153-
lambda: strtobool(
154-
vm.execute_powershell_script(
155-
r'$null -eq (Get-ScheduledTask "Copy-XenVifSettings" -ErrorAction SilentlyContinue)', simple_output=True
156-
)
157-
),
158-
timeout_secs=300,
159-
retry_delay_secs=30,
160-
)
161-
162-
163-
def set_vm_dns(vm: VM):
164-
logging.info(f"Set VM DNS to {TEST_DNS_SERVER}")
165-
vif = vm.vifs()[0]
166-
assert TEST_DNS_SERVER not in vif_get_dns(vif)
167-
vif_set_dns(vif, [TEST_DNS_SERVER])
168-
169-
170-
def check_vm_dns(vm: VM):
171-
# The restore task takes time to fire so wait for it
172-
vif = vm.vifs()[0]
173-
wait_for(
174-
lambda: TEST_DNS_SERVER in vif_get_dns(vif),
175-
f"Check VM DNS contains {TEST_DNS_SERVER}",
176-
timeout_secs=300,
177-
retry_delay_secs=30,
178-
)

tests/guest_tools/win/conftest.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
from lib.snapshot import Snapshot
99
from lib.sr import SR
1010
from lib.vm import VM
11-
12-
from . import (
11+
from lib.windows import (
1312
WINDOWS_SHUTDOWN_COMMAND,
1413
PowerAction,
1514
iso_create,
1615
try_get_and_store_vm_ip_serial,
1716
wait_for_vm_running_and_ssh_up_without_tools,
1817
)
19-
from .guest_tools import install_guest_tools
20-
from .other_tools import install_other_drivers
18+
from lib.windows.guest_tools import install_guest_tools
19+
from lib.windows.other_tools import install_other_drivers
2120

2221
from typing import Any, Dict, Tuple
2322

tests/guest_tools/win/test_guest_tools_win.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66
from lib.commands import SSHCommandFailed
77
from lib.common import strtobool, wait_for
88
from lib.vm import VM
9-
10-
from . import (
9+
from lib.windows import (
1110
WINDOWS_SHUTDOWN_COMMAND,
1211
PowerAction,
1312
check_vm_dns,
1413
set_vm_dns,
1514
vif_has_rss,
1615
wait_for_vm_running_and_ssh_up_without_tools,
1716
)
18-
from .guest_tools import (
17+
from lib.windows.guest_tools import (
1918
ERROR_INSTALL_FAILURE,
2019
install_guest_tools,
2120
uninstall_guest_tools,

0 commit comments

Comments
 (0)