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

nordisk/Skidbladnir

crates/installer/nordisk-pairing/tests/holger_boxes_pair.rs

//! **Two holger boxes FINISH an install, in one process** (0.5.5).
//!
//! Until 0.5.5 a holger box kept what the installer fed it (`boxside`) and
//! never reported PAIRED, so an installer closing on `CloseWhen::Paired` ran
//! out its budget (holger stinky 14, correction 4). Here the REAL installer
//! (`installer::run`) meets two `Kind::Holger` boxes — `boxside::Installed`
//! served by the real bootstrap listener on loopback, each running the shared
//! loop (`boxloop::run` over `BoxDoors`) — and the run closes only because
//! both boxes' signals say PAIRED: the primary by its product step, the twin by
//! become → certified → follow. The tunnel is the in-process no-op (a TUN needs
//! CAP_NET_ADMIN; `wireguard::box_plan` is unit-tested and the draupnir fleet
//! test runs the wire); everything else is the production code.

use nordisk_pairing::boxloop::{self, BoxDoors, Steps};
use nordisk_pairing::boxside::{self, Installed, Product};
use nordisk_pairing::door::Door;
use nordisk_pairing::elect::Capacity;
use nordisk_pairing::installer::{run, CloseWhen, Material, Plan, PlanVm, Sink};
use nordisk_pairing::installer_link::{DirectPath, Path};
use nordisk_pairing::join::{FrontTunnel, JoinReply};
use nordisk_pairing::mtls::InstallerCa;
use nordisk_pairing::seed::{Kind, Seed, TunnelKind};
use nordisk_pairing::signal::Signal;
use nordisk_pairing::tunnel::{NoTunnel, Tunnel};
use nordisk_pairing::Role;
use std::collections::BTreeMap;
use std::net::{Ipv4Addr, SocketAddrV4, TcpListener};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;

const ESTATE: &str = "holger.test";
const H: Duration = Duration::from_secs(3600);

/// holger's product half, as the installer sees it: its kind and capacity.
struct HolgerLike {
    cores: u32,
    fed_role: Mutex<Option<Role>>,
}

impl Product for HolgerLike {
    fn kind(&self) -> Kind {
        Kind::Holger
    }
    fn tunnel_pubkey(&self) -> String {
        String::new()
    }
    fn capacity(&self) -> Result<Option<Capacity>, String> {
        Ok(Some(Capacity { cores: self.cores, ram_bytes: 1 << 30, data_bytes: 1 << 30, disk_mib_s: 0 }))
    }
    fn offer(&self, _fed: Option<&JoinReply>) -> BTreeMap<String, String> {
        BTreeMap::from([("version".to_string(), "test".to_string())])
    }
    fn fed(&self, reply: &JoinReply) {
        *self.fed_role.lock().unwrap() = reply.role;
    }
}

/// holger's steps, faked where holger-twin-mtls will land: the primary is
/// ready once fed; the twin confers, is certified, and has followed.
struct HolgerSteps {
    became: Arc<Mutex<Option<Ipv4Addr>>>,
}

impl Steps for HolgerSteps {
    fn primary_ready(&self, _feed: &BTreeMap<String, String>) -> Result<bool, String> {
        Ok(true)
    }
    fn become_twin(&self, primary: Ipv4Addr) -> Result<(), String> {
        *self.became.lock().unwrap() = Some(primary);
        Ok(())
    }
    fn follow_state(&self) -> Result<(u64, u64), String> {
        Ok((3, 3))
    }
}

struct Collect(Mutex<Vec<String>>);

impl Sink for Collect {
    fn lines(&self, _vm: &str, _lines: &[String]) {}
    fn status(&self, _vm: &str, _s: Option<&Signal>, _l: &str) {}
    fn ledger(&self, _d: &Door) {}
    fn say(&self, line: &str) {
        self.0.lock().unwrap().push(line.to_string());
    }
    fn secret_offer(&self, _vm: &str, _o: &nordisk_pairing::bootstrap::SecretOffer) -> Result<(), String> {
        Ok(())
    }
}

struct OneBox {
    installed: Arc<Installed<HolgerLike>>,
    at: SocketAddrV4,
    root: std::path::PathBuf,
    became: Arc<Mutex<Option<Ipv4Addr>>>,
    rested: Arc<Mutex<Option<nordisk_pairing::pair::Machine>>>,
}

/// One holger box: its data root, the medium's seed, the bootstrap listener on
/// loopback, and the shared loop on its own thread.
fn boot(ca: &InstallerCa, name: &str, cores: u32, done: Arc<AtomicBool>) -> OneBox {
    let boot = ca.issue_bootstrap(H).unwrap();
    let seed = Seed {
        estate: ESTATE.into(),
        tunnel: TunnelKind::Wireguard,
        front: Default::default(),
        kind: Kind::Holger,
        installer_ca_pem: ca.cert_pem.clone(),
        bootstrap_cert_pem: boot.cert_pem,
        bootstrap_key_pem: boot.key_pem,
        peers_expected: 3,
        mullvad: None,
        tailscale: None,
        installer_link: None,
    };
    let root = std::env::temp_dir().join(format!("holger-boxes-pair-{}-{name}", std::process::id()));
    let _ = std::fs::remove_dir_all(&root);
    let installed = Arc::new(Installed::open(&root, seed.clone(), HolgerLike { cores, fed_role: Mutex::new(None) }).unwrap());
    let l = TcpListener::bind("127.0.0.1:0").unwrap();
    let std::net::SocketAddr::V4(at) = l.local_addr().unwrap() else { panic!() };
    let log: &'static nordisk_pairing::bootlog::BootLog = Box::leak(Box::new(nordisk_pairing::bootlog::BootLog::new(100)));
    log.push("korp-start: exec holger\nholger: first boot");
    let (vm, ca_pem) = (installed.clone(), ca.cert_pem.clone());
    std::thread::spawn(move || nordisk_pairing::bootstrap::serve(vm.as_ref(), log, &ca_pem, l, &|_| {}));

    let became = Arc::new(Mutex::new(None));
    let rested = Arc::new(Mutex::new(None));
    let (i, b, r, root2) = (installed.clone(), became.clone(), rested.clone(), root.clone());
    std::thread::spawn(move || {
        let tunnel: boxloop::TunnelFactory = Box::new(|_, _| Ok(Box::new(NoTunnel::default()) as Box<dyn Tunnel>));
        let doors = BoxDoors::new(i, HolgerSteps { became: b }, tunnel);
        let seed = seed.clone();
        let m = boxloop::run(&root2, &seed, &doors, &|_| {}, &|d| {
            if !done.load(Ordering::SeqCst) {
                std::thread::sleep(d.min(Duration::from_millis(100)));
            }
        });
        *r.lock().unwrap() = Some(m);
    });
    OneBox { installed, at, root, became, rested }
}

#[test]
fn the_installer_closes_two_holger_boxes_only_once_their_own_loops_report_paired() {
    let ca = InstallerCa::mint(ESTATE, H).unwrap();
    let done = Arc::new(AtomicBool::new(false));
    let main = boot(&ca, "vm2", 4, done.clone());
    let twin = boot(&ca, "vm3", 1, done.clone());

    let vm = |name: &str, public: [u8; 4], overlay: Ipv4Addr| PlanVm {
        name: name.into(),
        public: Ipv4Addr::from(public),
        utility: Ipv4Addr::new(10, 13, 8, public[3] + 1),
        overlay,
        kind: Kind::Holger,
        feed: BTreeMap::new(),
    };
    let plan = Plan {
        estate: ESTATE.into(),
        tunnel: TunnelKind::Wireguard,
        vms: vec![vm("vm2", [127, 0, 0, 1], nordisk_pairing::OVERLAY_BOX_A), vm("vm3", [127, 0, 0, 2], nordisk_pairing::OVERLAY_BOX_B)],
        feed: BTreeMap::new(),
        front: Some(FrontTunnel { tunnel_pubkey: "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=".into(), endpoint: "10.13.8.1:51820".into() }),
        port: 0,
        link_port: 0,
        budget_s: 90,
        close_when: CloseWhen::Paired,
        box_cert_valid_s: None,
        front_waits_for: Vec::new(),
    };
    let m = Material { ca: ca.clone(), me: ca.issue_installer(H).unwrap(), link: None };
    let paths: Vec<(PlanVm, Box<dyn Path>)> = vec![(plan.vms[0].clone(), Box::new(DirectPath { at: main.at })), (plan.vms[1].clone(), Box::new(DirectPath { at: twin.at }))];
    let sink = Collect(Mutex::new(Vec::new()));
    let out = run(&plan, &m, paths, &sink).unwrap_or_else(|e| panic!("{e}\n{:#?}", sink.0.lock().unwrap()));
    done.store(true, Ordering::SeqCst);

    assert!(out.vms.iter().all(|v| v.closed && v.admitted), "{out:?}");
    assert!(sink.0.lock().unwrap().iter().any(|l| l.contains("every VM is paired")), "closed on PAIRED, not on fed: {:#?}", sink.0.lock().unwrap());
    for (b, name, role) in [(&main, "vm2", Role::Primary), (&twin, "vm3", Role::Twin)] {
        assert!(b.installed.closed(), "{name}: the installer closed its listener");
        assert_eq!(*b.installed.product.fed_role.lock().unwrap(), Some(role), "{name}: the election (the weaker box is the twin)");
        let s = Signal::parse(&std::fs::read_to_string(b.root.join(boxside::SIGNAL_REL)).unwrap()).unwrap();
        assert!(s.paired && s.name == name && s.role == Some(role), "{name}: the signal the box wrote itself: {s:?}");
        let v = out.vms.iter().find(|v| v.name == name).unwrap();
        assert!(v.signal.as_deref().is_some_and(|l| l.ends_with("PAIRED")), "{name}: the installer read PAIRED: {v:?}");
    }
    assert_eq!(*twin.became.lock().unwrap(), Some(Ipv4Addr::new(10, 13, 8, 2)), "the twin became the twin of the primary's UTILITY address");
    assert_eq!(*main.became.lock().unwrap(), None, "the primary becomes nobody's twin");
    for b in [&main, &twin] {
        for _ in 0..50 {
            if b.rested.lock().unwrap().is_some() {
                break;
            }
            std::thread::sleep(Duration::from_millis(20));
        }
        assert!(b.rested.lock().unwrap().as_ref().is_some_and(|m| m.paired()), "the loop rests PAIRED");
        let _ = std::fs::remove_dir_all(&b.root);
    }
}