crates/mock-upcloud/kvm-guest/src/bin/utility_fabric_proof.rs
//! **Two gunnar boxes, one medium, ONE utility L2** — the proof that the
//! mock's utility network is UpCloud's shape, with real QEMU guests.
//!
//! Both servers boot the same medium from the tray, install, get ejected and
//! boot again from disk, exactly as the estate's power cycle does. Their eth1
//! sits on one `dgram` multicast group with the MAC the API would report. It
//! passes only when the fabric has ACKed BOTH boxes' DHCP with the API's
//! addresses — one in each utility /22, so option 121 is what gets either of
//! them off its own net.
//!
//! cargo run --release --bin utility-fabric-proof -- \
//! --iso /home/rickard/scratch/compose-final/metered-mock.iso --root /tmp/ufp
use std::path::PathBuf;
use std::time::{Duration, Instant};
use draupnir_guest::DraupnirGuest;
use mock_upcloud::kvm::{GuestEngine, Machine, MachineDisk};
use mock_upcloud::net::DhcpOffer;
const MEDIUM: &str = "01000000-0000-4000-8000-00000000150a";
struct Box_ {
server: &'static str,
mac: &'static str,
address: &'static str,
cores: u32,
disks: &'static [(&'static str, u64)],
}
/// The estate's two gunnar shapes: main the stronger, the twin at the
/// installer's system floor — each in its own utility /22.
const BOXES: &[Box_] = &[
Box_ {
server: "00000000-0000-4000-8000-0000000000a1",
mac: "0a:00:00:00:a1:02",
address: "10.13.8.100",
cores: 2,
disks: &[
("01000000-0000-4000-8000-0000000000a1", 24),
("01000000-0000-4000-8000-0000000000a2", 10),
],
},
Box_ {
server: "00000000-0000-4000-8000-0000000000b1",
mac: "0a:00:00:00:b1:02",
address: "10.13.12.101",
cores: 1,
disks: &[
("01000000-0000-4000-8000-0000000000b1", 12),
("01000000-0000-4000-8000-0000000000b2", 20),
],
},
];
fn machine(b: &Box_, from_tray: bool) -> Machine {
Machine {
server: b.server.into(),
mem_mb: 4096,
cores: b.cores,
disks: b
.disks
.iter()
.map(|(s, g)| MachineDisk {
storage: (*s).into(),
size_gib: *g,
template: None,
})
.collect(),
medium: from_tray.then(|| MEDIUM.to_string()),
cdrom_first: from_tray,
disk_first: false,
rtc_skew_seconds: 7200,
utility_mac: b.mac.into(),
utility_offer: DhcpOffer::for_address(b.address),
dhcp_late_first_boot_ms: 0,
user_data: None,
public_ip: String::new(),
public_admitted: mock_upcloud::kvm::INSTALLER_PUBLIC_PORTS.to_vec(),
hostname: b.address.into(),
ssh_keys: Vec::new(),
}
}
/// vm1, the front: a CLONE of the public Ubuntu template, as terraform makes it.
const FRONT_SERVER: &str = "00000000-0000-4000-8000-0000000000c1";
const FRONT_MAC: &str = "0a:00:00:00:c1:02";
const FRONT_ADDRESS: &str = "10.13.12.109";
const UBUNTU: &str = "Ubuntu Server 24.04 LTS (Noble Numbat)";
fn front(keys: &[String]) -> Machine {
Machine {
server: FRONT_SERVER.into(),
mem_mb: 2048,
cores: 2,
disks: vec![MachineDisk {
storage: "01000000-0000-4000-8000-0000000000c1".into(),
size_gib: 25,
template: Some(UBUNTU.into()),
}],
medium: None,
cdrom_first: false,
disk_first: false,
rtc_skew_seconds: 7200,
utility_mac: FRONT_MAC.into(),
utility_offer: DhcpOffer::for_address(FRONT_ADDRESS),
dhcp_late_first_boot_ms: 0,
user_data: None,
public_ip: String::new(),
public_admitted: mock_upcloud::kvm::INSTALLER_PUBLIC_PORTS.to_vec(),
hostname: "acme-front".into(),
ssh_keys: keys.to_vec(),
}
}
/// The host port the engine forwarded to `guest_port` on the server's last boot.
fn forward(engine: &DraupnirGuest, server: &str, guest_port: u16) -> Option<String> {
let ev = engine.evidence(server)?;
let boot = ev["boots"].as_array()?.last()?.clone();
boot["forwards"]
.as_array()?
.iter()
.find(|f| f["guest_port"] == guest_port)
.and_then(|f| f["host"].as_str().map(str::to_string))
}
/// `SSH-2.0-…` from the forwarded port, or `None` while nothing answers.
fn ssh_banner(at: &str) -> Option<String> {
use std::io::Read;
let mut s =
std::net::TcpStream::connect_timeout(&at.parse().ok()?, Duration::from_secs(2)).ok()?;
s.set_read_timeout(Some(Duration::from_secs(3))).ok()?;
let mut buf = [0u8; 64];
let n = s.read(&mut buf).ok()?;
let b = String::from_utf8_lossy(&buf[..n]).trim().to_string();
b.starts_with("SSH-").then_some(b)
}
fn main() {
if let Err(e) = run() {
eprintln!("utility-fabric-proof: FAIL — {e}");
std::process::exit(1);
}
}
fn run() -> Result<(), String> {
let mut iso: Option<PathBuf> = None;
let mut root = PathBuf::from("/tmp/ufp");
let mut budget = 600u64;
let mut ubuntu: Option<PathBuf> = None;
let mut key: Option<PathBuf> = None;
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
match a.as_str() {
"--iso" => iso = args.next().map(PathBuf::from),
"--ubuntu-image" => ubuntu = args.next().map(PathBuf::from),
"--key" => key = args.next().map(PathBuf::from),
"--root" => root = PathBuf::from(args.next().ok_or("--root wants a directory")?),
"--budget" => {
budget = args
.next()
.ok_or("--budget wants seconds")?
.parse()
.map_err(|e| format!("--budget: {e}"))?
}
other => return Err(format!("unknown argument: {other}")),
}
}
let iso = iso.ok_or("--iso FILE is required: the medium both boxes boot")?;
let (engine, _) = DraupnirGuest::for_estate(root.clone())?;
let mut engine = engine.with_utility_fabric()?;
let keys: Vec<String> = match &key {
Some(k) => vec![std::fs::read_to_string(k)
.map_err(|e| format!("{}: {e}", k.display()))?
.trim()
.to_string()],
None => Vec::new(),
};
if let Some(img) = ubuntu.clone() {
engine = engine.with_template_image("Ubuntu Server 24.04", img)?;
}
let (fabric, group) = engine.utility.clone().ok_or("the fabric did not start")?;
println!("utility L2 on {group}");
let medium = engine.medium_path(MEDIUM);
std::fs::create_dir_all(medium.parent().unwrap()).map_err(|e| e.to_string())?;
std::fs::copy(&iso, &medium).map_err(|e| format!("{}: {e}", iso.display()))?;
let started = Instant::now();
let deadline = started + Duration::from_secs(budget);
// 0. The front, a clone of the Ubuntu template, when an image was given.
let with_front = ubuntu.is_some();
if with_front {
engine.power_on(&front(&keys))?;
println!("{FRONT_ADDRESS} (front) booting the Ubuntu template");
}
// 1. Both boxes install from the SAME medium.
for b in BOXES {
engine.power_on(&machine(b, true))?;
println!("{} installing from the tray", b.address);
}
// 2. Each installer ends by powering the machine off; then the tray is
// empty and the box boots from its disk, as `cdrom/eject` + start does.
let mut rebooted = [false; 2];
while rebooted.iter().any(|r| !r) {
if Instant::now() > deadline {
return Err(format!("an installer did not finish inside {budget} s"));
}
for (i, b) in BOXES.iter().enumerate() {
if !rebooted[i] && engine.running(b.server) == Some(false) {
engine.power_on(&machine(b, false))?;
rebooted[i] = true;
println!(
"{} installed after {} s, booting from its disk",
b.address,
started.elapsed().as_secs()
);
}
}
std::thread::sleep(Duration::from_secs(2));
}
// 3. Both installed boxes take their API address from the utility L2.
let mut want: Vec<String> = BOXES.iter().map(|b| b.address.to_string()).collect();
if with_front {
want.push(FRONT_ADDRESS.to_string());
}
let mut banner: Option<String> = None;
loop {
let acked = fabric.lock().map(|f| f.acked()).unwrap_or_default();
if with_front && banner.is_none() {
banner = forward(&engine, FRONT_SERVER, 22)
.and_then(|at| ssh_banner(&at).map(|b| format!("{b} at {at}")));
}
if want.iter().all(|w| acked.contains(w)) && (!with_front || banner.is_some()) {
println!(
"PASS — ACKed on the utility L2: {} ({} s)",
acked.join(", "),
started.elapsed().as_secs()
);
if let Some(b) = &banner {
println!("PASS — the front answers SSH: {b}");
}
break;
}
if Instant::now() > deadline {
let why = format!("after {budget} s the fabric ACKed {acked:?} (wanted {want:?}); front SSH: {banner:?}");
if std::env::var_os("UFP_KEEP").is_some() {
println!("FAIL (kept running for a look) — {why}");
std::thread::sleep(Duration::from_secs(u64::MAX / 4));
}
engine.kill_all();
return Err(why);
}
std::thread::sleep(Duration::from_secs(2));
}
if std::env::var_os("UFP_KEEP").is_some() {
println!(
"UFP_KEEP set: leaving the machines running; front SSH forward {:?}",
forward(&engine, FRONT_SERVER, 22)
);
std::thread::sleep(Duration::from_secs(u64::MAX / 4));
}
for b in BOXES {
engine.power_off(b.server, true);
}
if with_front {
engine.power_off(FRONT_SERVER, true);
}
Ok(())
}