Fix raw USB live boot and verify both installation media paths

This commit is contained in:
Codex
2026-10-14 07:37:24 +03:00
parent c26442f6f5
commit b54693be0c
11 changed files with 339 additions and 8 deletions
+44 -3
View File
@@ -635,7 +635,12 @@ def qemu_path(path):
return str(path)
def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_drive=False):
def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_drive=False,
live_media='cdrom'):
if live_media not in {'cdrom', 'usb'}:
raise SmokeError('Unknown live media; choose cdrom or usb')
if keep_cdrom_drive and live_media == 'usb':
raise SmokeError('keep_cdrom_drive cannot be combined with usb live media')
scratch = Path(scratch)
owned_file(disk, scratch)
owned_file(variables, scratch)
@@ -658,10 +663,14 @@ def qemu_argv(qemu, scratch, disk, code, variables, iso, memory, *, keep_cdrom_d
if iso is not None:
optical += ',format=raw,file=' + qemu_path(iso)
argv += ['-drive', optical]
elif iso is not None and live_media == 'usb':
argv += ['-drive', f'if=none,id=acceptance-usb,format=raw,readonly=on,file={qemu_path(iso)}',
'-device', 'qemu-xhci,id=acceptance-xhci',
'-device', 'usb-storage,bus=acceptance-xhci.0,drive=acceptance-usb,bootindex=1']
elif iso is not None:
argv += ['-drive', f'file={qemu_path(iso)},media=cdrom,format=raw,readonly=on']
if iso is not None:
argv += ['-boot', 'order=d,strict=on']
argv += ['-boot', 'strict=on' if live_media == 'usb' else 'order=d,strict=on']
else:
argv += ['-boot', 'order=c,strict=on']
return argv
@@ -827,7 +836,8 @@ class VM:
self.stderr = open(scratch / f'{phase}-qemu.log', 'ab', buffering=0)
self.process = subprocess.Popen(qemu_argv(args.qemu, scratch, disk,
args.ovmf_code, scratch / 'OVMF_VARS.fd', iso, args.memory,
keep_cdrom_drive=getattr(args, 'keep_cdrom_drive', False)),
keep_cdrom_drive=getattr(args, 'keep_cdrom_drive', False),
live_media=getattr(args, 'live_media', 'cdrom')),
stdin=subprocess.DEVNULL, stdout=self.stderr, stderr=self.stderr)
self.console = None
self.qmp = None
@@ -910,6 +920,25 @@ for tool in hydra hydra-install qemu-system-x86_64 qemu-img remote-viewer foot l
for service in NetworkManager hydra-network hydra-dnsmasq; do systemctl is-active "$service"; done
'''
LIVE_GRAPHICAL_CHECK = r'''set -eu
systemctl is-active greetd
pgrep -u hydra-live -x labwc
pgrep -u hydra-live -x foot
'''
def live_graphical_probe(console, timeout=30):
# Serial login can become ready before greetd has launched the live shell.
deadline = time.monotonic() + timeout
while True:
try:
return console.command(LIVE_GRAPHICAL_CHECK, min(10, max(0, deadline - time.monotonic())))
except SmokeError:
if time.monotonic() >= deadline:
raise
time.sleep(min(1, max(0, deadline - time.monotonic())))
INSTALLED_CHECK = r'''set -eu
assert_no_match() {
if grep "$@"; then
@@ -1069,6 +1098,10 @@ def digest(path):
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--iso', required=True, type=Path)
parser.add_argument('--live-media', choices=('cdrom', 'usb'), default='cdrom',
help='Attach ISO as optical media or a read-only raw USB disk')
parser.add_argument('--live-only', action='store_true',
help='Check live boot and graphical session, then power off without installing')
parser.add_argument('--ovmf-code', required=True, type=Path)
parser.add_argument('--ovmf-vars', required=True, type=Path)
parser.add_argument('--qemu', default='qemu-system-x86_64')
@@ -1092,6 +1125,7 @@ def main(argv=None):
print(f'Acceptance artifacts: {scratch}', flush=True)
report = {'schema': 1, 'status': 'running', 'scratch': str(scratch),
'iso': str(args.iso), 'iso_sha256': digest(args.iso),
'live_media': args.live_media, 'scope': 'live-only' if args.live_only else 'install-reboot',
'checks': {}, 'physical_hardware': 'pending',
'nested_scope': 'Normal-user KVM VM launch, bridge helper and QMP; no nested guest OS boot',
'ovmf_code_sha256': digest(args.ovmf_code),
@@ -1115,6 +1149,13 @@ def main(argv=None):
console = vm.console
console.expect(rb'hydra-live[^\r\n]*[#$] ', args.boot_timeout)
checkpoint('live_boot', console.command(LIVE_CHECK, 90))
if args.live_only:
checkpoint('live_graphical_session', live_graphical_probe(console))
console.send('sudo -n poweroff\n')
vm.wait_poweroff(90)
checkpoint('live_poweroff', b'Live-only check completed without invoking the installer')
report['status'] = 'passed'
return 0
console.buffer = b''
installer_token = secrets.token_hex(12)
console.send(installer_command(installer_token) + '\n')