Skip to content

Commit 343a9df

Browse files
committed
Add grub navigation
1 parent 0cc574a commit 343a9df

1 file changed

Lines changed: 63 additions & 12 deletions

File tree

tests/install/with_tui.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import paramiko
1414
import pyte
1515

16+
from lib.commands import ssh
1617
from lib.common import Defer, wait_for
1718
from lib.host import Host
1819
from lib.vm import VM
@@ -21,10 +22,18 @@
2122

2223
from typing import Generator
2324

25+
BUFFER_READ_SIZE = 8192
26+
2427
def sha256(path: Path) -> str:
2528
with path.open("rb") as f:
2629
return hashlib.file_digest(f, "sha256").hexdigest()
2730

31+
def scan_for_mac_address(address: str, ip_range: str = "10.1.1-9.0-255") -> str | None:
32+
from data import ARP_SERVER
33+
awk = f"/Nmap scan report for/ {{ip=$5}} tolower($0) ~ /{address}/ {{print ip}}"
34+
command = f"nmap -sn {ip_range} | awk '{awk}'"
35+
output = ssh(ARP_SERVER, command).strip()
36+
return output or None
2837

2938
def vm_definition(firmware: str) -> dict:
3039
from data import NETWORKS
@@ -116,6 +125,9 @@ def test_install_with_tui(
116125
from data import HOST_DEFAULT_PASSWORD
117126

118127
vm = vm_booted_with_original_installer
128+
vif = vm.vifs()[0]
129+
mac_address = vif.param_get('MAC')
130+
assert mac_address is not None
119131
residence_host = vm.get_residence_host()
120132
dom_id = residence_host.xe(
121133
'vm-param-get',
@@ -139,17 +151,46 @@ def missing_host_key(self, client, hostname, key):
139151
logging.info(f"Connecting to serial line with {command!r}")
140152
channel.exec_command(command.encode())
141153
channel.settimeout(30.0)
142-
stdout = channel.makefile('rb', -1)
143154

155+
grub_screen = pyte.Screen(columns=100, lines=32)
156+
grub_screen.define_charset("U", "(")
157+
grub_stream = pyte.ByteStream(grub_screen)
158+
grub_stream.select_other_charset("@")
159+
160+
# Wait for grub to appear
161+
while not any("`e' to edit the commands" in line for line in grub_screen.display):
162+
grub_stream.feed(channel.recv(BUFFER_READ_SIZE))
163+
164+
# Wait for grub to stabilize
165+
time.sleep(1)
166+
while channel.recv_ready():
167+
grub_stream.feed(channel.recv(BUFFER_READ_SIZE))
168+
logging.info(f"Grub screen:\n{show_screen(grub_screen)}")
169+
170+
# Send:
171+
# - ctrl-n (x3): Next line
172+
# - ctrl-e: End of line
173+
# - space + vmlinuz extra config
174+
vmlinuz_extra_config = f"network_device=all sshpassword={HOST_DEFAULT_PASSWORD}".encode()
175+
for char in b"e\x0e\x0e\x0e\x05 " + vmlinuz_extra_config:
176+
channel.send(bytes([char]))
177+
time.sleep(0.1)
178+
179+
# Wait for grub to stabilize
180+
time.sleep(1)
181+
while channel.recv_ready():
182+
grub_stream.feed(channel.recv(BUFFER_READ_SIZE))
183+
logging.info(f"Grub screen after edition:\n{show_screen(grub_screen)}")
184+
185+
# Send ctrl-x: Save and boot
186+
channel.send(b"\x18")
187+
188+
# Initialize new screen
144189
screen = pyte.Screen(columns=80, lines=24)
145190
stream = pyte.ByteStream(screen)
146191

147-
# Wait for grub to finish
148-
for line in stdout:
149-
if b"Booting `install'" in line:
150-
break
151-
152192
# Wait for TUI to appear
193+
stdout = channel.makefile('rb', -1)
153194
for line in stdout:
154195
assert isinstance(line, bytes)
155196

@@ -181,39 +222,39 @@ def wait_for_dialog(
181222
any(wrapped_title in line for line in screen.display)
182223
and any(discriminant in line for line in screen.display)
183224
):
184-
stream.feed(channel.recv(1024))
225+
stream.feed(channel.recv(BUFFER_READ_SIZE))
185226
logging.info(f"Wait for {title!r} dialog to stabilize")
186227
time.sleep(delay)
187228
while channel.recv_ready():
188-
stream.feed(channel.recv(1024))
229+
stream.feed(channel.recv(BUFFER_READ_SIZE))
189230
logging.info(f"{title!r} dialog reached\n{show_screen(screen)}")
190231

191232
def send_tab(n: int = 1, delay: float = 1.0) -> None:
192233
channel.send(b"\t" * n)
193234
time.sleep(delay)
194235
while channel.recv_ready():
195-
stream.feed(channel.recv(1024))
236+
stream.feed(channel.recv(BUFFER_READ_SIZE))
196237
logging.info(f"Selection updated with {n} tab(s)\n{show_screen(screen)}")
197238

198239
def send_up(n: int = 1, delay: float = 1.0) -> None:
199240
channel.send(b"\x1b[A" * n)
200241
time.sleep(delay)
201242
while channel.recv_ready():
202-
stream.feed(channel.recv(1024))
243+
stream.feed(channel.recv(BUFFER_READ_SIZE))
203244
logging.info(f"Selection updated with {n} up(s)\n{show_screen(screen)}")
204245

205246
def send_password(n: int = 1, delay: float = 1.0) -> None:
206247
channel.send(f"{HOST_DEFAULT_PASSWORD}\t".encode() * 2)
207248
time.sleep(delay)
208249
while channel.recv_ready():
209-
stream.feed(channel.recv(1024))
250+
stream.feed(channel.recv(BUFFER_READ_SIZE))
210251
logging.info(f"Selection updated with {n} password(s)\n{show_screen(screen)}")
211252

212253
def send_pagedown(n: int = 1, delay: float = 1.0) -> None:
213254
channel.send(b"\x1b[6~" * n)
214255
time.sleep(delay)
215256
while channel.recv_ready():
216-
stream.feed(channel.recv(1024))
257+
stream.feed(channel.recv(BUFFER_READ_SIZE))
217258
logging.info(f"Selection updated with {n} pagedown(s)\n{show_screen(screen)}")
218259

219260
def validate(expected_selection: str | None = None, expected_validation: str = "Ok") -> None:
@@ -234,6 +275,16 @@ def validate(expected_selection: str | None = None, expected_validation: str = "
234275
send_tab()
235276
validate(expected_selection="[qwerty] us")
236277

278+
# The host should soon get an IP
279+
retries = 3
280+
for i in range(retries):
281+
ip = scan_for_mac_address(mac_address)
282+
if ip is not None:
283+
vm.ip = ip
284+
logging.info(f"VM IP: {ip}")
285+
break
286+
logging.info(f"Could not get an IP address on attempt {i}")
287+
237288
wait_for_dialog("Welcome to XCP-ng Setup")
238289
validate()
239290

0 commit comments

Comments
 (0)