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

nordisk/gunnar

crates/gunnar-server/tests/fed_clock_does_not_hold_the_feed.rs

// The pairing loop and its door are `wg-client` code; the gix engine arm builds without it.
#![cfg(feature = "wg-client")]
//! **An installer's feed naming a clock that does not answer YET is taken at
//! once** (cov-E r3 2026-10-06, runs 2 and 4).
//!
//! MEASURED: the twin was fed before the election spoke (role none, the
//! front's clock offer in the feed, its tunnel still DOWN). `Installed::fed`
//! applies the fed estate values live (`pairing::take_feed`), and the clock's
//! first fed sync ran its whole backoff ON THE FEED'S THREAD — 5 + 10 + 20 +
//! 40 + 60 + 60 s of pauses, each try timing out against a front only the
//! tunnel reaches. For 219 s (the same in both runs) the twin's bootstrap
//! listener answered nobody: the installer's next feed — the one naming the
//! twin's role, which raises its tunnel — got "nothing from the VM over the
//! link", the front's certify loop got "the twin gave no key: tcp connect
//! error", and the log gate failed the install on that loop. The clock waited
//! for a tunnel only the feed it was blocking could raise.
//!
//! The first fed syncs still happen, and still back off — on their own
//! thread.
//!
//! One test in this file, on purpose: it says this process owns the clock
//! (`GUNNAR_CLOCK`, as the appliance's PID 1 is told), which no other test
//! may share.

use std::sync::mpsc;
use std::time::{Duration, Instant};

use estate_clock::report::{Outcome, Trigger};
use gunnar_server::feed::{FEED_CLOCK_KEY, FEED_CLOCK_PEER};

#[test]
fn a_fed_clock_that_does_not_answer_yet_does_not_hold_the_installers_feed() {
    // A peer nobody listens on: every try is refused, the outcome is NoQuorum,
    // and the old code paused 5 s before the next.
    let closed = std::net::TcpListener::bind("127.0.0.1:0")
        .expect("bind")
        .local_addr()
        .expect("addr");
    let peer = format!("peer://{closed}");
    let key = {
        let pem = gunnar_core::mint_ed25519().expect("mint");
        gunnar_core::ssh_key::PrivateKey::from_openssh(&pem)
            .expect("parse")
            .public_key()
            .to_openssh()
            .expect("render")
    };
    // This process owns the clock, as the appliance's PID 1 does; the medium
    // names the same source the door feeds.
    std::env::set_var(estate_clock::config::CLOCK_ENV, &peer);
    std::env::set_var(estate_clock::config::CLOCK_KEY_ENV, &key);
    assert!(
        estate_clock::report::last().is_none(),
        "nothing synced before the feed"
    );

    let fed: nordisk_pairing::feedbus::Feed = [
        (FEED_CLOCK_KEY.to_string(), key),
        (FEED_CLOCK_PEER.to_string(), peer),
    ]
    .into_iter()
    .collect();
    let (done, took) = mpsc::channel();
    std::thread::spawn(move || {
        let t0 = Instant::now();
        gunnar_server::pairing::take_feed(Some(&fed));
        let _ = done.send(t0.elapsed());
    });
    let took = took.recv_timeout(Duration::from_secs(4)).expect(
        "the installer's feed is still held by the clock's first fed sync: its backoff (5 + 10 + 20 + 40 + 60 + 60 s) runs on the \
         feed's thread, so the installer cannot feed this box its role, its tunnel stays down, and the clock stays unreachable",
    );
    assert!(
        took < Duration::from_secs(2),
        "the feed was taken in {took:?}; the clock's syncs are not the feed's to wait for"
    );

    // ... and the first fed sync happened all the same, off the feed's thread.
    let t0 = Instant::now();
    let report = loop {
        if let Some(r) = estate_clock::report::last() {
            break r;
        }
        assert!(
            t0.elapsed() < Duration::from_secs(10),
            "the fed clock was never tried"
        );
        std::thread::sleep(Duration::from_millis(50));
    };
    assert_eq!(report.trigger, Trigger::Timer, "{report:?}");
    assert_eq!(
        report.outcome,
        Outcome::NoQuorum,
        "nobody answers on {closed}: {report:?}"
    );
}