feat: ARM ISO boot integration test, OVMF boot fixes
Some checks failed
CI/CD / typecheck (pull_request) Failing after 9s
CI/CD / test (pull_request) Failing after 10s
CI/CD / lint (pull_request) Failing after 22s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
Some checks failed
CI/CD / typecheck (pull_request) Failing after 9s
CI/CD / test (pull_request) Failing after 10s
CI/CD / lint (pull_request) Failing after 22s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
ARM integration test: - arm-iso-provision.test.ts: aarch64 VM boots from bastion-generated ISO - Uses QEMU aarch64 emulation (slow but validates the R1 scenario) - Generous timeouts for emulated boot (15min discovery, 60min install) - test-provision.sh updated: `sudo ./scripts/test-provision.sh arm` VM boot fixes: - setBootDisk() preserves UEFI loader/nvram when switching to disk boot - /boot/efi mount gets nofail in fstab (prevents emergency mode in VMs) - chronyd enable uses || true (fails in kickstart chroot) - createIsoVm supports arch parameter for ARM VMs Note: SSH-after-reboot in OVMF VMs still fails — OVMF doesn't respect efibootmgr changes and loops PXE/HTTP Boot. Real hardware works fine. The install flow itself (discovery → kickstart → complete) is validated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,13 +40,17 @@ export function createPxeVm(config: PxeVmConfig): void {
|
||||
const diskPath = join(IMAGE_DIR, `${config.name}.qcow2`);
|
||||
run(`qemu-img create -f qcow2 "${diskPath}" ${config.diskSize}G`);
|
||||
|
||||
// OVMF firmware paths (Fedora)
|
||||
const ovmfCode = arch === "aarch64"
|
||||
? "/usr/share/edk2/aarch64/QEMU_EFI-pflash.raw"
|
||||
: "/usr/share/edk2/ovmf/OVMF_CODE.fd";
|
||||
|
||||
if (!existsSync(ovmfCode)) {
|
||||
throw new Error(`OVMF firmware not found at ${ovmfCode}. Install: sudo dnf install edk2-ovmf`);
|
||||
// UEFI firmware paths (Fedora)
|
||||
if (arch === "aarch64") {
|
||||
const aavmf = "/usr/share/edk2/aarch64/QEMU_EFI.fd";
|
||||
if (!existsSync(aavmf)) {
|
||||
throw new Error(`AAVMF firmware not found at ${aavmf}. Install: sudo dnf install edk2-aarch64`);
|
||||
}
|
||||
} else {
|
||||
const ovmf = "/usr/share/edk2/ovmf/OVMF_CODE.fd";
|
||||
if (!existsSync(ovmf)) {
|
||||
throw new Error(`OVMF firmware not found at ${ovmf}. Install: sudo dnf install edk2-ovmf`);
|
||||
}
|
||||
}
|
||||
|
||||
const virtInstallArgs = [
|
||||
@@ -104,6 +108,31 @@ export function rebootPxeVm(name: string): void {
|
||||
log(`PXE VM ${name} restarted`);
|
||||
}
|
||||
|
||||
/** Change VM boot order to disk first (skip PXE on next boot). */
|
||||
export function setBootDisk(name: string): void {
|
||||
log(`Setting ${name} boot order to disk first`);
|
||||
virsh("destroy", name);
|
||||
spawnSync("sleep", ["2"]);
|
||||
// Get current XML, replace boot dev='network' with boot dev='hd'
|
||||
// This preserves UEFI loader/nvram settings (virt-xml --boot hd can break them)
|
||||
const dumpXml = virsh("dumpxml", name);
|
||||
if (dumpXml.status !== 0) throw new Error("Failed to dump VM XML");
|
||||
let xml = dumpXml.stdout;
|
||||
// Replace any <boot dev='...' /> entries with hd
|
||||
xml = xml.replace(/<boot dev='[^']*'\/>/g, "<boot dev='hd'/>");
|
||||
// If no boot dev entry, add one before </os>
|
||||
if (!xml.includes("<boot dev=")) {
|
||||
xml = xml.replace("</os>", " <boot dev='hd'/>\n </os>");
|
||||
}
|
||||
const xmlPath = `/tmp/${name}-bootfix.xml`;
|
||||
const { writeFileSync: writeFs, unlinkSync: unlinkFs } = require("node:fs") as typeof import("node:fs");
|
||||
writeFs(xmlPath, xml);
|
||||
run(`virsh define "${xmlPath}"`);
|
||||
try { unlinkFs(xmlPath); } catch { /* ignore */ }
|
||||
virsh("start", name);
|
||||
log(`${name} restarted with disk boot (UEFI preserved)`);
|
||||
}
|
||||
|
||||
export interface IsoVmConfig {
|
||||
name: string;
|
||||
memory: number; // MB
|
||||
@@ -111,13 +140,15 @@ export interface IsoVmConfig {
|
||||
diskSize: number; // GB
|
||||
network: string; // libvirt network name
|
||||
isoPath: string; // path to boot ISO
|
||||
arch?: "x86_64" | "aarch64";
|
||||
}
|
||||
|
||||
/** Create a UEFI VM that boots from a CD-ROM ISO (not PXE). */
|
||||
export function createIsoVm(config: IsoVmConfig): void {
|
||||
destroyPxeVm(config.name);
|
||||
|
||||
log(`Creating ISO boot VM: ${config.name} (${config.memory}MB RAM, ${config.vcpus} vCPU, ${config.diskSize}GB disk)`);
|
||||
const arch = config.arch ?? "x86_64";
|
||||
log(`Creating ISO boot VM: ${config.name} (${arch}, ${config.memory}MB RAM, ${config.vcpus} vCPU, ${config.diskSize}GB disk)`);
|
||||
|
||||
// Create blank disk
|
||||
const diskPath = join(IMAGE_DIR, `${config.name}.qcow2`);
|
||||
@@ -140,7 +171,11 @@ export function createIsoVm(config: IsoVmConfig): void {
|
||||
"--graphics=vnc,listen=127.0.0.1",
|
||||
];
|
||||
|
||||
if (arch === "aarch64") {
|
||||
virtInstallArgs.push("--arch=aarch64", "--machine=virt");
|
||||
}
|
||||
|
||||
log(`Running: virt-install --name=${config.name} --boot=uefi,cdrom ...`);
|
||||
run(virtInstallArgs.join(" "), { timeout: 30_000 });
|
||||
run(virtInstallArgs.join(" "), { timeout: 60_000 });
|
||||
log(`ISO boot VM ${config.name} created and booting from ISO`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user