1313import paramiko
1414import pyte
1515
16+ from lib .commands import ssh
1617from lib .common import Defer , wait_for
1718from lib .host import Host
1819from lib .vm import VM
2122
2223from typing import Generator
2324
25+ BUFFER_READ_SIZE = 8192
26+
2427def 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
2938def 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