WIP: automating VM deployment

This commit is contained in:
Maxime Van Hees
2025-08-26 16:50:59 +02:00
parent 1bb731711b
commit 4b4f3371b0
10 changed files with 1213 additions and 1 deletions

View File

@@ -0,0 +1,234 @@
// End-to-end smoke test for the new qcow2 + cloud-hypervisor refactor
// This script executes in logical phases so we can see clearly what works.
//
// Phases:
// 1) Host preflight check
// 2) Image preparation (Ubuntu) -> raw disk
// 3) Launch VM via builder using prepared raw disk
// 4) Inspect VM info, list VMs
// 5) Stop & delete VM
// 6) Launch VM via one-shot wrapper vm_easy_launch
// 7) Inspect VM info, list VMs
// 8) Stop & delete VM
//
// Notes:
// - Run as root on the host (required for NBD/mount/networking).
// - Base images expected at:
// /images/noble-server-cloudimg-amd64.img
// /images/alpine-virt-cloudimg-amd64.qcow2 (Alpine prepare not implemented yet)
// /images/hypervisor-fw (firmware binary used via --kernel)
// - Network defaults: IPv4 NAT + dnsmasq DHCP; placeholder IPv6 on bridge + guest netplan.
//
// Conventions:
// - Functional builder chaining: b = memory_mb(b, 4096), etc.
// - Each phase prints a banner and either "OK" or "FAILED" with detailed error message.
fn banner(s) {
print("==================================================");
print(s);
print("==================================================");
}
fn ok(s) {
print("[OK] " + s);
}
fn fail(msg) {
print("[FAILED] " + msg);
}
fn dump_map(m) {
// simple pretty printer for small maps
for k in m.keys() {
print(" " + k + ": " + m[k].to_string());
}
}
fn dump_array(a) {
let i = 0;
for x in a {
print(" - " + x.to_string());
}
}
// ------------------------------------------------------------------------------------
// Phase 1: Host preflight check
// ------------------------------------------------------------------------------------
banner("PHASE 1: host_check()");
let hc = host_check();
if !(hc.ok == true) {
fail("host_check indicates missing dependencies; details:");
print("critical:");
dump_array(hc.critical);
print("optional:");
dump_array(hc.optional);
print("notes:");
dump_array(hc.notes);
// Short-circuit: nothing else will work without deps
throw "Missing critical host dependencies";
} else {
ok("host_check passed");
}
// ------------------------------------------------------------------------------------
// Phase 2: Image preparation for Ubuntu
// - produces a per-VM raw disk in $HOME/hero/virt/vms/<id>/disk.raw
// ------------------------------------------------------------------------------------
banner("PHASE 2: image_prepare (Ubuntu) -> raw disk");
let vmA = "vm-e2e-a";
let prep_opts = #{
id: vmA,
flavor: "ubuntu",
// source: optional override, default uses /images/noble-server-cloudimg-amd64.img
// target_dir: optional override, default $HOME/hero/virt/vms/<id>
net: #{
dhcp4: true,
dhcp6: false,
ipv6_addr: "400::10/64",
gw6: "400::1",
},
disable_cloud_init_net: true,
};
let prep_res = ();
let prep_ok = false;
try {
prep_res = image_prepare(prep_opts);
ok("image_prepare returned:");
dump_map(prep_res);
if prep_res.raw_disk == () {
fail("prep_res.raw_disk is UNIT; expected string path");
} else {
ok("raw_disk: " + prep_res.raw_disk);
prep_ok = true;
}
} catch (e) {
fail("image_prepare failed: " + e.to_string());
}
if !(prep_ok) {
throw "Stopping due to image_prepare failure";
}
// ------------------------------------------------------------------------------------
// Phase 3: Launch VM via builder using the prepared raw disk
// ------------------------------------------------------------------------------------
banner("PHASE 3: Launch via cloudhv_builder (disk from Phase 2)");
let b = cloudhv_builder(vmA);
let b = disk(b, prep_res.raw_disk);
let b = memory_mb(b, 4096);
let b = vcpus(b, 2);
// Optional extras:
// let b = extra_arg(b, "--serial"); let b = extra_arg(b, "tty");
// let b = no_default_net(b);
let vm_id_a = "";
try {
vm_id_a = launch(b);
ok("builder.launch started VM id: " + vm_id_a);
} catch (e) {
fail("builder.launch failed: " + e.to_string());
throw "Stopping due to launch failure for vm-e2e-a";
}
// ------------------------------------------------------------------------------------
// Phase 4: Inspect VM info, list VMs
// ------------------------------------------------------------------------------------
banner("PHASE 4: cloudhv_vm_info / cloudhv_vm_list");
try {
let info_a = cloudhv_vm_info(vm_id_a);
ok("cloudhv_vm_info:");
dump_map(info_a);
} catch (e) {
fail("cloudhv_vm_info failed: " + e.to_string());
}
try {
let vms = cloudhv_vm_list();
ok("cloudhv_vm_list count = " + vms.len.to_string());
} catch (e) {
fail("cloudhv_vm_list failed: " + e.to_string());
}
// ------------------------------------------------------------------------------------
// Phase 5: Stop & delete VM A
// ------------------------------------------------------------------------------------
banner("PHASE 5: Stop & delete VM A");
try {
cloudhv_vm_stop(vm_id_a, false);
ok("cloudhv_vm_stop graceful OK");
} catch (e) {
fail("cloudhv_vm_stop (graceful) failed: " + e.to_string() + " -> trying force");
try {
cloudhv_vm_stop(vm_id_a, true);
ok("cloudhv_vm_stop force OK");
} catch (e2) {
fail("cloudhv_vm_stop force failed: " + e2.to_string());
}
}
try {
cloudhv_vm_delete(vm_id_a, true);
ok("cloudhv_vm_delete OK (deleted disks)");
} catch (e) {
fail("cloudhv_vm_delete failed: " + e.to_string());
}
// ------------------------------------------------------------------------------------
// Phase 6: Launch VM via one-shot wrapper vm_easy_launch()
// ------------------------------------------------------------------------------------
banner("PHASE 6: vm_easy_launch for VM B");
let vmB = "vm-e2e-b";
let vm_id_b = "";
try {
vm_id_b = vm_easy_launch("ubuntu", vmB, 4096, 2);
ok("vm_easy_launch started VM id: " + vm_id_b);
} catch (e) {
fail("vm_easy_launch failed: " + e.to_string());
throw "Stopping due to vm_easy_launch failure";
}
// ------------------------------------------------------------------------------------
// Phase 7: Inspect VM B info, list VMs
// ------------------------------------------------------------------------------------
banner("PHASE 7: Inspect VM B");
try {
let info_b = cloudhv_vm_info(vm_id_b);
ok("cloudhv_vm_info (B):");
dump_map(info_b);
} catch (e) {
fail("cloudhv_vm_info (B) failed: " + e.to_string());
}
try {
let vms2 = cloudhv_vm_list();
ok("cloudhv_vm_list count = " + vms2.len.to_string());
} catch (e) {
fail("cloudhv_vm_list failed: " + e.to_string());
}
// ------------------------------------------------------------------------------------
// Phase 8: Stop & delete VM B
// ------------------------------------------------------------------------------------
banner("PHASE 8: Stop & delete VM B");
try {
cloudhv_vm_stop(vm_id_b, false);
ok("cloudhv_vm_stop (B) graceful OK");
} catch (e) {
fail("cloudhv_vm_stop (B) graceful failed: " + e.to_string() + " -> trying force");
try {
cloudhv_vm_stop(vm_id_b, true);
ok("cloudhv_vm_stop (B) force OK");
} catch (e2) {
fail("cloudhv_vm_stop (B) force failed: " + e2.to_string());
}
}
try {
cloudhv_vm_delete(vm_id_b, true);
ok("cloudhv_vm_delete (B) OK (deleted disks)");
} catch (e) {
fail("cloudhv_vm_delete (B) failed: " + e.to_string());
}
banner("DONE: All phases executed");