crates/gunnar-recipe/tests/clock_kernel_edge.rs
//! **Every medium gunnar ships asks its donor kernel for a paravirt wall
//! clock** — the row-level `kernel` edge that keeps an appliance off the
//! emulated CMOS.
//!
//! # The defect, measured on the live estate 2026-09-20
//!
//! The appliance re-imaged cleanly and then read `+7 198 668 ms` against the
//! front — 1 h 59 m 58.7 s, the CEST offset to the second. Every credential fell
//! outside `gunnar_grpc::auth::MAX_CLOCK_SKEW_MS` (±300 000 ms) and the console
//! key, the git banner and monetize's tenant poll went red behind that one
//! cause.
//!
//! The cause is not a read in gunnar. gunnar reads `SystemTime::now()` and
//! nothing else, and that was PROVEN under KVM on oden the same day: a guest
//! handed a CMOS two hours ahead (`-rtc base=2026-09-20T12:00:00` on a host
//! reading 10:00 UTC) printed its first line at `2026-09-20T12:00:03.485013Z` —
//! the CMOS reproduced to the millisecond, nothing added, nothing subtracted.
//!
//! The cause is which clock the KERNEL reads. The donor's built config carried
//! `# CONFIG_HYPERVISOR_GUEST is not set`, so there was no kvmclock and
//! `x86_platform.get_wallclock` fell through to `mach_get_cmos_time` — the
//! emulated mc146818, filled by the hypervisor's `-rtc base=`. The front, same
//! hypervisor and same zone, was right to 468 ms because ordinary Ubuntu has
//! `CONFIG_KVM_GUEST=y` and reads the KVM wall-clock MSR instead, which the KVM
//! module fills from the host's own `CLOCK_REALTIME`.
//!
//! # Why a test and not just a fragment line
//!
//! The donor may grant these symbols today through its own merge layers. Saying
//! it on the ROW is what turns a donor that drops them into a refusal BY NAME at
//! `--build kernel`, instead of a medium that boots two hours fast and refuses
//! every credential it is handed. This test is what keeps the row saying it: a
//! medium added later starts red with its own name in the output, which is the
//! lesson `machine_named.rs` paid for.
use nornir_build_thing::kernel_requires::KconfigValue;
use nornir_build_thing::plugin::BuildThing;
use nornir_build_thing::requires::{EdgeRequirement, Requirement};
use nornir_build_thing::variant::BuildVariantSet;
use gunnar_recipe::GunnarBuildThing;
/// The four symbols, and why each one is on the list. A subset is not a
/// smaller version of this requirement: `CONFIG_KVM_GUEST` without
/// `CONFIG_PARAVIRT_CLOCK` has no pvclock to read, and either one without
/// `CONFIG_HYPERVISOR_GUEST` is not offered by Kconfig at all.
const CLOCK_SYMBOLS: &[&str] = &[
"CONFIG_HYPERVISOR_GUEST",
"CONFIG_PARAVIRT",
"CONFIG_KVM_GUEST",
"CONFIG_PARAVIRT_CLOCK",
];
fn set() -> BuildVariantSet {
GunnarBuildThing.variants()
}
/// The `=y` Kconfig symbols a row asks of its `kernel` edge.
fn kernel_symbols(requires: &[EdgeRequirement]) -> Vec<String> {
let mut found: Vec<String> = requires
.iter()
.filter(|r| r.edge == "kernel")
.filter_map(|r| match &r.what {
Requirement::Kconfig(k) => Some(k.symbol.clone()),
_ => None,
})
.collect();
found.sort();
found.dedup();
found
}
/// Every medium that boots a kernel — the live ISO and every installer twin —
/// names all four, BY NAME in the failure.
#[test]
fn every_medium_asks_its_donor_kernel_for_a_paravirt_wall_clock() {
let set = set();
let mut media: Vec<(String, Vec<String>)> = Vec::new();
for iso in &set.isos {
media.push((iso.name.clone(), kernel_symbols(&iso.requires)));
}
for installer in &set.installers {
media.push((installer.name.clone(), kernel_symbols(&installer.requires)));
}
assert!(
media.len() >= 2,
"gunnar declares no media at all — this test would then assert nothing"
);
let mut missing: Vec<String> = Vec::new();
for (name, symbols) in &media {
for wanted in CLOCK_SYMBOLS {
if !symbols.iter().any(|s| s == wanted) {
missing.push(format!("{name} does not ask for {wanted}"));
}
}
}
assert!(
missing.is_empty(),
"a medium that does not name the paravirt clock boots on the emulated CMOS, which was \
measured two hours fast on UpCloud 2026-09-20:\n {}",
missing.join("\n ")
);
}
/// Every one of the four is spelled `=y`, never `=m`: the appliance carries no
/// initramfs and no module loader, so a clock source built as a module is a
/// clock source that is never there when the wall clock is read — which is
/// during `setup_arch`, long before any filesystem.
#[test]
fn the_clock_symbols_are_built_in_never_modules() {
let set = set();
let mut wrong: Vec<String> = Vec::new();
let mut seen = 0usize;
let rows: Vec<(String, Vec<EdgeRequirement>)> = set
.isos
.iter()
.map(|i| (i.name.clone(), i.requires.clone()))
.chain(
set.installers
.iter()
.map(|i| (i.name.clone(), i.requires.clone())),
)
.collect();
for (name, requires) in rows {
for r in requires.iter().filter(|r| r.edge == "kernel") {
let Requirement::Kconfig(k) = &r.what else {
continue;
};
if !CLOCK_SYMBOLS.contains(&k.symbol.as_str()) {
continue;
}
seen += 1;
if !matches!(k.value, KconfigValue::Y) {
wrong.push(format!("{name}: {} = {:?}", k.symbol, k.value));
}
}
}
assert!(seen > 0, "no clock requirement was examined at all");
assert!(
wrong.is_empty(),
"the appliance has no module loader; a modular clocksource is absent at setup_arch:\n {}",
wrong.join("\n ")
);
}
/// Each symbol carries a reason a reader can act on. The build prints these
/// when it refuses a donor, and "CONFIG_KVM_GUEST is missing" with no sentence
/// behind it is how a refusal becomes a shrug.
#[test]
fn each_clock_symbol_says_why_it_is_needed() {
let set = set();
let mut silent: Vec<String> = Vec::new();
for installer in &set.installers {
for r in installer.requires.iter().filter(|r| r.edge == "kernel") {
let Requirement::Kconfig(k) = &r.what else {
continue;
};
if !CLOCK_SYMBOLS.contains(&k.symbol.as_str()) {
continue;
}
if k.reason.trim().len() < 40 {
silent.push(format!("{}: {:?}", k.symbol, k.reason));
}
}
}
assert!(
silent.is_empty(),
"a requirement with no sentence behind it cannot be argued with:\n {}",
silent.join("\n ")
);
}
/// NOT VACUOUS: the reader really does find a symbol that is absent. If
/// `kernel_symbols` ever returned everything (a filter inverted, an edge name
/// changed), the assertions above would pass over an empty declaration.
#[test]
fn the_reader_can_tell_a_named_symbol_from_an_unnamed_one() {
let set = set();
let installer = set
.installers
.first()
.expect("gunnar declares at least one installer row");
let symbols = kernel_symbols(&installer.requires);
assert!(
symbols.iter().any(|s| s == "CONFIG_KVM_GUEST"),
"{}: the reader found no CONFIG_KVM_GUEST — it reads nothing",
installer.name
);
assert!(
!symbols
.iter()
.any(|s| s == "CONFIG_ACPI_HOTPLUG_IO_PORTS_MADE_UP"),
"{}: the reader claims a symbol nobody declared — it reads everything",
installer.name
);
}