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:?}"
);
}