gunnar-server 2.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! **The machine at a glance** (`StorageReality.glance`, owner 2026-10-04):
//! the four lines the console's Machine card shows — `/data`'s shape, the
//! off-stripe copies on `/serialdat`, the free space, the disk speed — each
//! with gunnar's own verdict.
//!
//! The console is a thin layer (owner, 2026-10-04): it draws these lines as
//! they come and decides none of them, so every verdict is made HERE, once,
//! and the CLI prints the same words.
//!
//! # The mislabel this replaces
//!
//! The console's Machine page drew `serialdat.dev` and `serialdat.class` as
//! "Backing device / Backing class" directly under "Data root" — so on every
//! estate it said `/data` was backed by `/dev/vda3 (p3)`, the boot disk's
//! third partition, which is `/serialdat`'s. `/data` is the stripe the
//! installer laid over the data disks (`data.shape`, `data.N.dev`). The two
//! are now two separate lines, each naming its own disk.

use gunnar_proto::{FilesystemUse, GlanceLine};

pub const OK: &str = "ok";
pub const WARN: &str = "warn";
pub const BAD: &str = "bad";
pub const UNKNOWN: &str = "unknown";

/// Used share at which the free-space line turns amber and red: the marks
/// the estate's growth acts on (grow at 80 % used, emergency at 90 %).
pub const WARN_USED_PERCENT: u64 = 80;
pub const BAD_USED_PERCENT: u64 = 90;

/// Everything the four lines are read from — all of it already measured or
/// configured elsewhere; nothing here reads a disk.
#[derive(Debug, Clone, Default)]
pub struct Facts<'a> {
    /// The layout config is present (and verified or integrity-checked).
    pub record_present: bool,
    /// `data.mountpoint`, else the data root.
    pub data_label: &'a str,
    /// `data.shape`: `raid0` | `plain` | `p3` | "".
    pub data_shape: &'a str,
    /// `data.N.dev`, in order.
    pub data_devices: &'a [String],
    /// `serialdat.mountpoint`, else the configured slow root.
    pub serialdat_label: &'a str,
    /// `serialdat.class`: `p3` | `device` | `fallback` | "".
    pub serialdat_class: &'a str,
    /// `serialdat.dev`.
    pub serialdat_device: &'a str,
    /// The one off-stripe verdict: `off-stripe` | `same-device` | `absent`.
    pub off_stripe: &'a str,
    pub off_stripe_health: &'a [String],
    /// The data root's filesystem, measured on this call.
    pub data_fs: Option<&'a FilesystemUse>,
    /// The start-up disk test's outcome, `None` when it has not run.
    pub disk: Option<&'a crate::disktest::Outcome>,
}

/// `12.3 GiB`, binary, like every other byte figure an operator reads here.
pub fn human(n: u64) -> String {
    const K: f64 = 1024.0;
    let v = n as f64;
    if v >= K * K * K * K {
        format!("{:.1} TiB", v / (K * K * K * K))
    } else if v >= K * K * K {
        format!("{:.1} GiB", v / (K * K * K))
    } else if v >= K * K {
        format!("{:.1} MiB", v / (K * K))
    } else if v >= K {
        format!("{:.1} KiB", v / K)
    } else {
        format!("{n} B")
    }
}

fn line(key: &str, line: String, state: &str, detail: String) -> GlanceLine {
    GlanceLine { key: key.to_owned(), line, state: state.to_owned(), detail }
}

/// **`/data`, from the installer's own record** — never from the mount table.
pub fn data_line(f: &Facts) -> GlanceLine {
    let label = f.data_label;
    if !f.record_present {
        return line(
            "data",
            format!("{label}: shape not recorded"),
            UNKNOWN,
            "This box has no layout config (or it did not verify), so the installer's shape of the \
             data set is not known — and it is not guessed from what happens to be mounted."
                .into(),
        );
    }
    let devices = f.data_devices.join(", ");
    match f.data_shape {
        "raid0" if f.data_devices.len() >= 2 => line(
            "data",
            format!("{label} = raid0 over {devices}"),
            OK,
            "The repositories are on the data stripe, laid by the installer over the data disks."
                .into(),
        ),
        "plain" | "raid0" if !f.data_devices.is_empty() => line(
            "data",
            format!("{label} = {} on {devices}", f.data_shape),
            OK,
            "The repositories are on a data disk of their own (one disk, no stripe).".into(),
        ),
        "p3" => line(
            "data",
            format!("{label} = p3 on the boot disk"),
            WARN,
            "No data disk was found at install time: the repositories live on the boot disk's \
             third partition, beside the system."
                .into(),
        ),
        "" => line(
            "data",
            format!("{label}: the layout config names no shape"),
            UNKNOWN,
            "The config carries no `data.shape`.".into(),
        ),
        other => line(
            "data",
            if devices.is_empty() { format!("{label} = {other}") } else { format!("{label} = {other} over {devices}") },
            UNKNOWN,
            format!("`data.shape = {other}` is a shape this build has no verdict for."),
        ),
    }
}

/// **`/serialdat` — the off-stripe copies' own disk**, and the verdict on it.
pub fn serialdat_line(f: &Facts) -> GlanceLine {
    let label = f.serialdat_label;
    let dev = f.serialdat_device;
    let text = match (f.serialdat_class, dev) {
        ("p3", "") => format!("{label} on the boot disk (P3)"),
        ("p3", dev) => format!("{label} on {dev} (boot disk P3)"),
        ("device", "") => format!("{label} on a disk of its own"),
        ("device", dev) => format!("{label} on {dev} (its own disk)"),
        ("fallback", _) => format!("{label} is a directory on the data set (no partition of its own)"),
        _ if label.is_empty() => "/serialdat: nothing said where it lives".to_owned(),
        _ => format!("{label}: nothing said what disk it is on"),
    };
    let state = match f.off_stripe {
        "off-stripe" => OK,
        "same-device" | "absent" => BAD,
        _ => UNKNOWN,
    };
    let verdict = match f.off_stripe {
        "off-stripe" => " — the off-stripe copies are on another failure domain",
        "same-device" => " — the off-stripe copies share a device with the data (no second failure domain)",
        "absent" => " — there is no off-stripe copy",
        _ => "",
    };
    line("serialdat", format!("{text}{verdict}"), state, f.off_stripe_health.join(" "))
}

/// **Free space on the data root**, measured on this call.
pub fn free_line(f: &Facts) -> GlanceLine {
    let label = f.data_label;
    match f.data_fs {
        Some(fs) if fs.measured && fs.total_bytes > 0 => {
            let used = fs.total_bytes.saturating_sub(fs.free_bytes);
            let pct = used.saturating_mul(100) / fs.total_bytes;
            let state = if pct >= BAD_USED_PERCENT {
                BAD
            } else if pct >= WARN_USED_PERCENT {
                WARN
            } else {
                OK
            };
            line(
                "free",
                format!("{label}: {} free of {} ({pct} % used)", human(fs.free_bytes), human(fs.total_bytes)),
                state,
                format!(
                    "Amber from {WARN_USED_PERCENT} % used, red from {BAD_USED_PERCENT} % — the marks the \
                     estate's growth acts on."
                ),
            )
        }
        Some(fs) => line(
            "free",
            format!("{label}: free space not measured"),
            UNKNOWN,
            if fs.error.is_empty() { "statvfs answered no size".to_owned() } else { fs.error.clone() },
        ),
        None => line("free", format!("{label}: free space not measured"), UNKNOWN, String::new()),
    }
}

/// **The disk speed the start-up test measured.**
pub fn speed_line(f: &Facts) -> GlanceLine {
    use crate::disktest::Outcome;
    match f.disk {
        Some(Outcome::Done(r)) => line(
            "speed",
            format!("disk speed: {}", r.row()),
            OK,
            "Measured once at start-up on the data root: sequential write and read, and 4 KiB \
             writes each followed by fsync."
                .into(),
        ),
        Some(Outcome::Skipped { free, need }) => line(
            "speed",
            format!("disk speed: not measured — {} free, the test needs {}", human(*free), human(*need)),
            WARN,
            "The start-up test never fills the disk it measures.".into(),
        ),
        Some(Outcome::Failed(why)) => line("speed", format!("disk speed: the test failed — {why}"), BAD, String::new()),
        None => line(
            "speed",
            "disk speed: not measured yet".to_owned(),
            UNKNOWN,
            "The start-up disk test has not finished a run in this process (it runs on the \
             appliance, or with GUNNAR_DISKTEST set)."
                .into(),
        ),
    }
}

/// The four lines, in the card's order.
pub fn lines(f: &Facts) -> Vec<GlanceLine> {
    vec![data_line(f), serialdat_line(f), free_line(f), speed_line(f)]
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::disktest::{Outcome, ReadHow, Report};
    use std::time::Duration;

    fn devs(d: &[&str]) -> Vec<String> {
        d.iter().map(|s| s.to_string()).collect()
    }

    fn estate<'a>(devices: &'a [String], fs: &'a FilesystemUse) -> Facts<'a> {
        Facts {
            record_present: true,
            data_label: "/data",
            data_shape: "raid0",
            data_devices: devices,
            serialdat_label: "/serialdat",
            serialdat_class: "p3",
            serialdat_device: "/dev/vda3",
            off_stripe: "off-stripe",
            off_stripe_health: &[],
            data_fs: Some(fs),
            disk: None,
        }
    }

    fn fs(total: u64, free: u64) -> FilesystemUse {
        FilesystemUse { what: "data root".into(), path: "/data".into(), measured: true, total_bytes: total, free_bytes: free, error: String::new() }
    }

    /// ★ **The mislabel, fixed**: `/data` names ITS devices, and `/dev/vda3`
    /// is said once — on the `/serialdat` line, as the boot disk's P3.
    #[test]
    fn data_names_its_own_devices_and_serialdat_names_the_boot_disk() {
        let d = devs(&["/dev/vdb", "/dev/vdc"]);
        let fs = fs(100 << 30, 60 << 30);
        let f = estate(&d, &fs);
        let data = data_line(&f);
        assert_eq!((data.line.as_str(), data.state.as_str()), ("/data = raid0 over /dev/vdb, /dev/vdc", "ok"));
        assert!(!data.line.contains("vda3"), "the data line must not name the boot disk: {}", data.line);
        let sd = serialdat_line(&f);
        assert!(sd.line.starts_with("/serialdat on /dev/vda3 (boot disk P3)"), "{}", sd.line);
        assert_eq!(sd.state, "ok");
        let keys: Vec<_> = lines(&f).into_iter().map(|l| l.key).collect();
        assert_eq!(keys, ["data", "serialdat", "free", "speed"]);
    }

    /// `same-device` and `absent` are never green; an unknown word is never
    /// green either.
    #[test]
    fn the_off_stripe_copies_are_green_only_when_off_stripe() {
        let d = devs(&["/dev/vdb"]);
        let fs = fs(1, 1);
        for (word, state) in [("off-stripe", "ok"), ("same-device", "bad"), ("absent", "bad"), ("", "unknown"), ("weird", "unknown")] {
            let mut f = estate(&d, &fs);
            f.off_stripe = word;
            assert_eq!(serialdat_line(&f).state, state, "{word}");
        }
    }

    #[test]
    fn the_data_shapes_each_have_their_words() {
        let one = devs(&["/dev/vdb"]);
        let none: Vec<String> = Vec::new();
        let fs = fs(1, 1);
        let mut f = estate(&one, &fs);
        f.data_shape = "plain";
        assert_eq!((data_line(&f).line.as_str(), data_line(&f).state.as_str()), ("/data = plain on /dev/vdb", "ok"));
        let mut f = estate(&none, &fs);
        f.data_shape = "p3";
        assert_eq!(data_line(&f).state, "warn");
        f.record_present = false;
        assert_eq!(data_line(&f).state, "unknown", "no record: not guessed");
    }

    /// Amber at 80 % used, red at 90 %; an unmeasured filesystem is unknown,
    /// never a number.
    #[test]
    fn free_space_turns_amber_then_red_and_unmeasured_is_unknown() {
        let d = devs(&["/dev/vdb", "/dev/vdc"]);
        for (free, state) in [(50u64, "ok"), (19, "warn"), (5, "bad")] {
            let fs = fs(100, free);
            assert_eq!(free_line(&estate(&d, &fs)).state, state, "{free} free of 100");
        }
        let broken = FilesystemUse { measured: false, error: "EIO".into(), ..fs(0, 0) };
        let l = free_line(&estate(&d, &broken));
        assert_eq!(l.state, "unknown");
        assert!(!l.line.chars().any(|c| c.is_ascii_digit()), "no number that was not read: {}", l.line);
    }

    #[test]
    fn the_disk_speed_is_green_only_when_measured() {
        let d = devs(&["/dev/vdb", "/dev/vdc"]);
        let fs = fs(1, 1);
        let mut f = estate(&d, &fs);
        assert_eq!(speed_line(&f).state, "unknown");
        let done = Outcome::Done(Report {
            seq_write_bps: 8.12e8,
            seq_read_bps: 1.0e9,
            read_how: ReadHow::Direct,
            sync_ops_per_s: 3100.0,
            elapsed: Duration::from_millis(1600),
            file_bytes: 1 << 30,
            write_elapsed: Duration::from_millis(800),
            read_elapsed: Duration::from_millis(500),
            sync_ops: 4960,
            sync_elapsed: Duration::from_millis(300),
        });
        f.disk = Some(&done);
        let l = speed_line(&f);
        assert_eq!(l.state, "ok");
        assert!(l.line.contains("812 MB/s"), "{}", l.line);
        let failed = Outcome::Failed("EIO".into());
        f.disk = Some(&failed);
        assert_eq!(speed_line(&f).state, "bad");
    }
}