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

nordisk/Skidbladnir

tests/install_matrix_live.rs

//! LIVE multi-OS install matrix (heavy — boots real KVM guests). `#[ignore]` so the
//! default `cargo test` stays hermetic; run it explicitly on a KVM-capable host:
//!
//! ```bash
//! # build the injected static probe + test-service first:
//! cargo build --release --target x86_64-unknown-linux-musl --manifest-path probe/Cargo.toml
//! # then the matrix (optionally SKID_ONLY=ubuntu-24.04,alpine-3.21 to subset):
//! cargo test --features matrix --test install_matrix_live -- --ignored --nocapture
//! ```
//!
//! Images are resolved from `$SKID_ISOS` (default `/mnt/work4t/skidbladnir/isos`);
//! per-VM scratch + the COW snapshot live under `$SKID_VMS`
//! (default `/mnt/work4t/skidbladnir/vms`); rows are sunk to `$WORKSPACE_SKIDBLADNIR`
//! (default `/mnt/work4t/skidbladnir/workspace_skidbladnir`).

#![cfg(feature = "matrix")]

use std::path::{Path, PathBuf};
use std::time::Duration;

use skidbladnir::events::JsonlSink;
use skidbladnir::matrix::{OsImage, run_matrix, sink_results};
use skidbladnir::service::Backend;

fn env_path(key: &str, default: &str) -> PathBuf {
    std::env::var(key)
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from(default))
}

#[test]
#[ignore = "heavy: boots real KVM guests; needs qemu + the musl probe built"]
fn live_install_matrix() {
    let isos = env_path("SKID_ISOS", "/mnt/work4t/skidbladnir/isos");
    let vms = env_path("SKID_VMS", "/mnt/work4t/skidbladnir/vms");
    let workspace = env_path(
        "WORKSPACE_SKIDBLADNIR",
        "/mnt/work4t/skidbladnir/workspace_skidbladnir",
    );

    // The injected static binaries (built for x86_64-unknown-linux-musl).
    let rel = "probe/target/x86_64-unknown-linux-musl/release";
    let probe_bin = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join(rel)
        .join("skidbladnir-probe");
    let testsvc_bin = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join(rel)
        .join("skidbladnir-testsvc");
    if !probe_bin.exists() || !testsvc_bin.exists() {
        panic!(
            "build the probe first:\n  cargo build --release --target x86_64-unknown-linux-musl \
             --manifest-path probe/Cargo.toml\n(missing {} / {})",
            probe_bin.display(),
            testsvc_bin.display()
        );
    }

    // The matrix axis: each OS exercises one renderer (Alpine → OpenRC; rest → systemd).
    let all = vec![
        OsImage::new(
            "ubuntu-24.04",
            isos.join("ubuntu-24.04-cloudimg-amd64.img"),
            Backend::SystemdSystem,
        )
        .memory_mb(2048),
        OsImage::new(
            "debian-12",
            isos.join("debian-12-genericcloud-amd64.qcow2"),
            Backend::SystemdSystem,
        )
        .memory_mb(2048),
        OsImage::new(
            "fedora-42",
            isos.join("fedora-42-cloud-amd64.qcow2"),
            Backend::SystemdSystem,
        )
        .memory_mb(2048),
        OsImage::new(
            "alpine-3.21",
            isos.join("alpine-3.21.0-x86_64-cloudinit.qcow2"),
            Backend::OpenRc,
        )
        .memory_mb(1024),
    ];
    let only: Option<Vec<String>> = std::env::var("SKID_ONLY")
        .ok()
        .map(|s| s.split(',').map(|x| x.trim().to_string()).collect());
    let images: Vec<OsImage> = all
        .into_iter()
        .filter(|i| only.as_ref().map(|o| o.contains(&i.name)).unwrap_or(true))
        .filter(|i| i.disk.exists())
        .collect();
    assert!(
        !images.is_empty(),
        "no base images found under {}",
        isos.display()
    );

    let run_id = format!("matrix-{}", chrono::Utc::now().format("%Y%m%dT%H%M%SZ"));
    let work_root = vms.join(&run_id);
    std::fs::create_dir_all(&work_root).unwrap();

    eprintln!("== install-matrix {run_id}: {} cell(s) ==", images.len());
    let timeout = Duration::from_secs(
        std::env::var("SKID_BOOT_TIMEOUT")
            .ok()
            .and_then(|s| s.parse().ok())
            .unwrap_or(360),
    );
    let rows = run_matrix(&images, &probe_bin, &testsvc_bin, &work_root, timeout);

    // Report table.
    eprintln!("\n== RESULTS (renderer × OS) ==");
    for r in &rows {
        eprintln!(
            "  [{:>9}] {:<14} {:<15} scope={}",
            r.outcome.label(),
            r.os,
            r.backend,
            r.scope
        );
        if let Some(rep) = r.outcome.report() {
            eprintln!(
                "             running={} pid={:?} ports={:?} health={}/{} at={}",
                rep.running, rep.pid, rep.ports, rep.health.kind, rep.health.ok, rep.asserted_at
            );
        }
    }

    // Sink rows → workspace JSONL + airgap_events (nornir autonom ingest).
    let reports_jsonl = workspace.join("probe_reports.jsonl");
    let sink = JsonlSink::under_warehouse(&workspace).unwrap();
    sink_results(&rows, &reports_jsonl, &sink, &run_id).unwrap();
    eprintln!("\nrows → {}", reports_jsonl.display());
    eprintln!("events → {}", sink.path().display());

    // A sample ProbeReport (the headline artifact).
    if let Some(rep) = rows.iter().find_map(|r| r.outcome.report()) {
        eprintln!(
            "\n== sample ProbeReport ==\n{}",
            serde_json::to_string_pretty(rep).unwrap()
        );
    }

    // Assert the deliverable: at least two DISTINCT OS guests round-tripped a probe.
    let with_report = rows.iter().filter(|r| r.outcome.report().is_some()).count();
    assert!(
        with_report >= 2,
        "expected ≥2 OS guests to round-trip a probe, got {with_report}"
    );
}