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

nordisk/gunnar

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