Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

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(())
}