crates/nornir-build-thing/src/target/presets.rs
//! **Target presets** — the EXACT virtual-hardware shape of every cloud we may
//! deploy an appliance medium to, as one Rust const per cloud.
//!
//! A [`Target`](super::Target) says what a medium is built FOR (firmware,
//! consoles, buses, nic). A [`TargetPreset`] says what a NAMED machine — UpCloud,
//! GCE, AWS Nitro … — actually IS, with the facts a proof door needs on top of
//! the `target` group: the disk bus, the NIC model, the CPU model exposure, the
//! chipset, whether a UART sits at `0x3f8`, how a medium is brought, whether a
//! metadata service answers. Each preset ends in a verdict on **whether QEMU
//! can mimic it** ([`Mimic`]): the mimicable ones become `appliance-boot/<preset>`
//! rows the installtest measures; the rest are declared rows that stay
//! `Absent (needs real cloud)` so the matrix shows the gap instead of a green
//! nobody measured.
//!
//! # Why this is data in the engine and not a shell script
//!
//! The installer ISO shipped 2026-08-28 booted under OVMF with a captured UART
//! and died on UpCloud, whose QEMU has **no UART at 0x3f8** (`ttyS1`/`ttyS2` at
//! `0x2f8`/`0x3e8` exist), boots **SeaBIOS only**, attaches the ISO as an **IDE
//! cdrom**, the disk as **virtio-blk**, the NIC as **virtio-net**, and shows the
//! console over **VNC/VGA**. Not one of those facts lived in a test. This table
//! is where such facts live from now on; the model is written up in
//! `edda/.nornir/build-design.md` (f9) and the operator's side in
//! `edda/.nornir/build-guide.md`.
//!
//! Every field is honest about its confidence: [`Tri::Unknown`] where the
//! documentation is silent, never a guess dressed as a fact.
//!
//! # ONE roster (2026-08-29, oden lane B)
//!
//! [`PRESETS`] is the only list of named machines in the engine. The `target`
//! group's `preset = "<name>"` resolves HERE ([`preset`]), the emitted
//! `[[target-preset]]` roster ([`super::presets_toml`]) is rendered FROM these
//! consts through [`TargetPreset::target`], and the test matrix's
//! `appliance-boot/<name>` rows are floored by [`TargetPreset::status`]. There
//! is no second table of names anywhere, so a name cannot drift between the
//! toml, the validator and the matrix — one writer per fact.
use super::{Bus, Console, Cpu, Firmware, Nic, Target, UartPort};
/// A fact that may be documented true, documented false, or not documented.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tri {
Yes,
No,
/// The vendor's documentation is silent. Never treated as either.
Unknown,
}
impl Tri {
pub fn as_str(self) -> &'static str {
match self {
Tri::Yes => "yes",
Tri::No => "no",
Tri::Unknown => "unknown",
}
}
}
/// **Which legacy COM ports the guest really has** — the fact that decides
/// whether a `serial` console may exist and which `ttyS<n>` it is.
///
/// UpCloud is the reason this is a list and not a yes/no on `0x3f8`: nothing
/// sits at `0x3f8`, but `ttyS1`/`ttyS2` (`0x2f8`/`0x3e8`) are real, and a proof
/// rig that mimics the machine must create exactly those slots
/// (`-serial none -serial file:…`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Uarts {
/// Measured or documented: exactly these ports, in COM order.
Known(&'static [UartPort]),
/// The vendor's documentation is silent. Spelled as NO port in the derived
/// `target` group: a serial console nobody can prove exists is a dark boot
/// waiting to happen, so the preset must not name `serial` either.
Unknown,
}
impl Uarts {
/// The ports as the `target` group spells them (`Unknown` = none).
pub fn ports(self) -> &'static [UartPort] {
match self {
Uarts::Known(p) => p,
Uarts::Unknown => &[],
}
}
/// Is there a 16550 at `0x3f8` (`ttyS0`)? [`Tri::Unknown`] when undocumented.
pub fn ttys0(self) -> Tri {
match self {
Uarts::Known(p) if p.contains(&UartPort::Com1) => Tri::Yes,
Uarts::Known(_) => Tri::No,
Uarts::Unknown => Tri::Unknown,
}
}
/// qemu's `-serial` slots, COM1..COM4: `true` = a port exists there.
pub fn slots(self) -> [bool; 4] {
let mut out = [false; 4];
for p in self.ports() {
out[p.index()] = true;
}
out
}
}
/// **Where a measurement was taken** — the fact that splits two claims that
/// shared one word for a year.
///
/// `upcloud` and `qemu-local-bios` were both `measured`, and they are not the
/// same evidence: one is a real UpCloud Cloud Server in se-sto1, the other is
/// `qemu-system-x86_64` on this box. A roster that calls both "measured" cannot
/// report its own progress honestly — "3 of 13 measured" reads as three clouds
/// when it is one cloud and two rigs.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MeasuredOn {
/// A QEMU/KVM rig on our own proof box. Proves the MEDIUM and everything
/// the mimic renders — never the platform, and never a device QEMU stood
/// in for ([`MimicAxes::substitutions`]).
QemuRig,
/// The real platform: an instance of this cloud, booted and watched.
RealCloud,
}
impl MeasuredOn {
pub fn as_str(self) -> &'static str {
match self {
MeasuredOn::QemuRig => "qemu rig",
MeasuredOn::RealCloud => "real cloud",
}
}
}
/// **The evidence behind a `measured` claim**: where it was taken and what was
/// seen. One value, so a preset cannot name a date without naming the machine
/// it was taken on.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Evidence {
pub on: MeasuredOn,
/// What was actually observed — dates, host, the lines that were read.
pub what: &'static str,
}
impl Evidence {
/// A QEMU-rig measurement on our own proof box.
pub const fn rig(what: &'static str) -> Evidence {
Evidence { on: MeasuredOn::QemuRig, what }
}
/// A measurement taken on the real platform.
pub const fn cloud(what: &'static str) -> Evidence {
Evidence { on: MeasuredOn::RealCloud, what }
}
/// The one string a report quotes — the evidence never travels without
/// saying which kind it is.
pub fn label(&self) -> String {
format!("{}: {}", self.on.as_str(), self.what)
}
}
/// How well a preset is known — DERIVED ([`TargetPreset::status`]), never
/// declared beside the facts it summarises.
///
/// **Two measured words, not one** (2026-09-05). `Measured` used to cover both
/// "someone booted a real UpCloud instance" and "someone ran QEMU here", and the
/// roster could not report progress honestly while those shared a word. The
/// split is derived from [`TargetPreset::measured`]'s own [`MeasuredOn`], so it
/// cannot be declared wrong.
///
/// `NeedsRealCloud` keeps its name and its meaning on purpose: it is the
/// variant other repos compare against (`gunnar/installtest/src/firmware.rs`
/// gates `--target` on `p.status() != PresetStatus::NeedsRealCloud`, i.e. "is
/// this renderable at all"), so the split is additive for every such caller.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PresetStatus {
/// Booted on a QEMU rig here ([`MeasuredOn::QemuRig`]). Proves the medium
/// and the shape; proves the PLATFORM only when
/// [`MimicAxes::is_exact`] also holds.
MimicMeasured,
/// Booted on the real platform ([`MeasuredOn::RealCloud`]) — the only
/// evidence that means the cloud itself.
CloudMeasured,
/// QEMU can mimic it ([`Mimic::Qemu`]) but nobody has booted it yet — the
/// `appliance-boot/<name>` row is a hole a KVM proof can fill.
Stub,
/// QEMU cannot mimic it ([`Mimic::NeedsRealCloud`]): the row stays
/// `Absent (needs real cloud)` until a real deploy measures it.
NeedsRealCloud,
}
impl PresetStatus {
pub fn as_str(self) -> &'static str {
match self {
PresetStatus::MimicMeasured => "mimic-measured",
PresetStatus::CloudMeasured => "cloud-measured",
PresetStatus::Stub => "stub",
PresetStatus::NeedsRealCloud => "needs-real-cloud",
}
}
/// Whether this status rests on a boot that actually happened — the
/// question `Measured` used to answer, kept as a method so a caller that
/// only wanted "has evidence" does not have to learn the split.
pub fn is_measured(self) -> bool {
matches!(self, PresetStatus::MimicMeasured | PresetStatus::CloudMeasured)
}
}
/// Which firmware(s) the platform can start a guest with, and Secure Boot.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecureBoot {
/// No Secure Boot offered at all.
None,
/// Available but off unless asked for (AWS `uefi` + NitroTPM, GCE Shielded
/// VM toggle, Azure Trusted Launch, OpenStack image property).
Optional,
/// On for the platform's default images (GCE Shielded VM on newer images).
DefaultOn,
Unknown,
}
/// How the customer reaches the guest's console.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConsoleReach {
/// A VGA framebuffer over VNC/noVNC — the ONLY console (UpCloud).
VgaVnc,
/// A serial console on `ttyS0` (EC2 Serial Console, GCE serial port 1,
/// Azure Serial Console via boot diagnostics, Hetzner/Scaleway/OVH VNC
/// that is really a serial-backed console are spelled per preset).
Serial,
/// Both a VGA path (VNC or a screenshot facility) and a serial console.
Both,
Unknown,
}
/// The bus the BOOT disk rides, as the guest kernel sees it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiskBus {
/// `virtio-blk` (`/dev/vda`) — kernel `VIRTIO_BLK`.
VirtioBlk,
/// `virtio-scsi` (`/dev/sda`) — kernel `SCSI_VIRTIO SCSI BLK_DEV_SD`.
VirtioScsi,
/// NVMe (`/dev/nvme0n1`) — kernel `BLK_DEV_NVME`.
Nvme,
/// Legacy IDE/PIIX (`/dev/sda` via `ATA_PIIX`) — Azure gen1 OS disk.
Ide,
/// Hyper-V synthetic SCSI (`hv_storvsc`) — kernel `HYPERV_STORAGE`.
HvScsi,
/// Xen blkfront (`/dev/xvda`) — kernel `XEN_BLKDEV_FRONTEND`.
XenBlk,
/// Real hardware (bare metal): whatever the box has, typically NVMe/SATA.
Metal,
}
impl DiskBus {
pub fn as_str(self) -> &'static str {
match self {
DiskBus::VirtioBlk => "virtio-blk",
DiskBus::VirtioScsi => "virtio-scsi",
DiskBus::Nvme => "nvme",
DiskBus::Ide => "ide",
DiskBus::HvScsi => "hv-scsi",
DiskBus::XenBlk => "xen-blk",
DiskBus::Metal => "metal",
}
}
/// The QEMU device model that presents this bus, or `None` when QEMU (under
/// KVM, on this host) cannot present it: Hyper-V and Xen paravirtual devices
/// need their own hypervisor.
pub fn qemu_mimic(self) -> Option<&'static str> {
match self {
DiskBus::VirtioBlk => Some("virtio-blk-pci"),
DiskBus::VirtioScsi => Some("virtio-scsi-pci"),
DiskBus::Nvme => Some("nvme"),
DiskBus::Ide => Some("ide-hd"),
// **`xen-disk` is listed and not attachable — the listing was the
// trap.** MEASURED on oden 2026-09-05, QEMU 10.2.1:
//
// ```text
// $ qemu-system-x86_64 -device help | grep xen
// name "xen-disk", bus xen-bus, desc "Xen Disk Device" ← it IS listed
// $ qemu-system-x86_64 -machine q35 -device xen-disk
// No 'xen-bus' bus found for device 'xen-disk' ← it is NOT attachable
// $ qemu-system-x86_64 -accel help → tcg mshv kvm (no `xen`)
// $ qemu-system-x86_64 -machine q35,xen-version=0x4000a
// Property 'pc-q35-10.2-machine.xen-version' not found (no Xen emulation)
// ```
//
// This arm briefly said `Some("xen-disk")` on the strength of the
// `-device help` line alone, and called the year-old
// `NeedsRealCloud("xen blkfront/netfront need a Xen hypervisor")`
// sentence a lie. The sentence was RIGHT. `-device help` enumerates
// every compiled-in model regardless of whether this build has a bus
// to hang it on: without the `xen` accelerator or a machine with
// `xen-version`, no `xen-bus` is ever created, so `xen-disk`,
// `xen-net-device` and `xen-cdrom` cannot be instantiated and
// `xen-platform` answers "requires a Xen guest".
//
// The guard that ratified the mistake is fixed with it: see
// `the_device_words_are_attachable_on_this_qemu` below, which asks
// QEMU to actually BUILD each device instead of grepping a listing.
DiskBus::XenBlk | DiskBus::HvScsi | DiskBus::Metal => None,
}
}
/// The `target.buses` word, where one exists.
pub fn target_bus(self) -> Option<Bus> {
match self {
DiskBus::VirtioBlk => Some(Bus::VirtioBlk),
DiskBus::VirtioScsi => Some(Bus::VirtioScsi),
DiskBus::Nvme => Some(Bus::Nvme),
DiskBus::HvScsi => Some(Bus::HvStorvsc),
DiskBus::XenBlk => Some(Bus::XenBlk),
// `ide` is the one bus left the group spells only as a CDROM (an
// Azure Gen1 OS disk is a real IDE hard disk), and `metal` is not a
// bus word at all.
DiskBus::Ide | DiskBus::Metal => None,
}
}
}
/// The NIC model the guest sees.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NicModel {
/// `virtio-net` — kernel `VIRTIO_NET`.
VirtioNet,
/// Intel e1000 — kernel `E1000`.
E1000,
/// Intel 82574L e1000e — kernel `E1000E`.
E1000e,
/// AWS Elastic Network Adapter — kernel `ENA_ETHERNET`. Nitro only.
Ena,
/// Google Virtual NIC — kernel `GVE`.
Gvnic,
/// Hyper-V synthetic NIC — kernel `HYPERV_NET`.
HvNetvsc,
/// Xen netfront — kernel `XEN_NETDEV_FRONTEND`.
XenNetfront,
/// Real hardware.
Metal,
}
impl NicModel {
pub fn as_str(self) -> &'static str {
match self {
NicModel::VirtioNet => "virtio-net",
NicModel::E1000 => "e1000",
NicModel::E1000e => "e1000e",
NicModel::Ena => "ena",
NicModel::Gvnic => "gvnic",
NicModel::HvNetvsc => "hv-netvsc",
NicModel::XenNetfront => "xen-netfront",
NicModel::Metal => "metal",
}
}
/// The QEMU `-device` model, or `None` when QEMU has no such NIC (ENA,
/// gVNIC, hv_netvsc, xen-netfront outside Xen).
pub fn qemu_mimic(self) -> Option<&'static str> {
match self {
NicModel::VirtioNet => Some("virtio-net-pci"),
NicModel::E1000 => Some("e1000"),
NicModel::E1000e => Some("e1000e"),
// `xen-net-device` is listed by `-device help` and is NOT
// attachable — the same measurement, and the same trap, as
// `DiskBus::XenBlk`. Read that comment; it carries the transcript.
NicModel::XenNetfront => None,
// ENA and gVNIC are not even listed (`ena`, `ena-pci`, `gve`,
// `gvnic`, `gve-pci` — none appear); hv_netvsc has no model though
// `vmbus-bridge` and `hv-balloon` do.
NicModel::Ena | NicModel::Gvnic | NicModel::HvNetvsc | NicModel::Metal => None,
}
}
/// The `target.nic` word, where one exists.
pub fn target_nic(self) -> Option<Nic> {
match self {
NicModel::VirtioNet => Some(Nic::Virtio),
NicModel::E1000 | NicModel::E1000e => Some(Nic::E1000),
NicModel::Ena => Some(Nic::Ena),
NicModel::HvNetvsc => Some(Nic::HvNetvsc),
NicModel::Gvnic => Some(Nic::Gvnic),
NicModel::XenNetfront => Some(Nic::XenNetfront),
// Bare metal has no model word: whatever the box has.
NicModel::Metal => None,
}
}
}
/// How the platform exposes the CPU to the guest — decides RDRAND, AVX,
/// `/proc/cpuinfo`'s model name, and whether a build tuned `-C target-cpu`
/// survives.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CpuModel {
/// `-cpu host`: the guest sees the host's own model and flags.
HostPassthrough,
/// A named QEMU model (`EPYC-Rome`, `Skylake-Server`, …): a fixed flag set
/// regardless of the host, RDRAND included on every model named here.
Named(&'static str),
/// QEMU's own default (`qemu64` under KVM): NO RDRAND, no AVX. The shape
/// every proof in this constellation booted before `-cpu` was a choice.
QemuDefault,
/// Not a QEMU host at all (Hyper-V, Xen, bare metal) — the guest sees what
/// that hypervisor or the metal gives.
Native,
Unknown,
}
impl CpuModel {
/// The `-cpu` argument to render, if any. `QemuDefault` and `Unknown`
/// render nothing, which is the historical argv.
pub fn qemu_arg(self) -> Option<&'static str> {
match self {
CpuModel::HostPassthrough => Some("host"),
CpuModel::Named(m) => Some(m),
CpuModel::QemuDefault | CpuModel::Native | CpuModel::Unknown => None,
}
}
pub fn label(self) -> String {
match self {
CpuModel::HostPassthrough => "host".into(),
CpuModel::Named(m) => m.to_string(),
CpuModel::QemuDefault => "qemu64 (qemu default, no RDRAND)".into(),
CpuModel::Native => "native (not qemu)".into(),
CpuModel::Unknown => "unknown".into(),
}
}
}
/// The machine chipset the guest sees.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Chipset {
/// i440FX + PIIX (`-machine pc`).
Pc,
/// Q35 + ICH9 (`-machine q35`).
Q35,
/// Hyper-V (no PCI bus on gen2 except SR-IOV VFs), Xen, or bare metal.
NotQemu,
Unknown,
}
impl Chipset {
pub fn qemu_machine(self) -> Option<&'static str> {
match self {
Chipset::Pc => Some("pc"),
Chipset::Q35 => Some("q35"),
Chipset::NotQemu | Chipset::Unknown => None,
}
}
}
/// How a custom medium or image is brought to the platform.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediumBring {
/// Upload an ISO and attach it as a (IDE) cdrom; install onto the disk.
IsoCdrom,
/// Import a RAW disk image (the installed disk, pre-laid) — no ISO path.
ImportRaw,
/// Import qcow2 (OpenStack-style glance image).
ImportQcow2,
/// Import a fixed-size VHD (Azure).
ImportVhd,
/// Several of the above (AWS: raw/vhd/vmdk/ova via VM Import; GCE: raw
/// tar.gz; Hetzner: ISO via the ISO library / snapshot from a rescue dd).
Several(&'static str),
/// Bare metal: PXE / vendor rescue + dd, or a real burned medium.
Metal(&'static str),
Unknown,
}
impl MediumBring {
/// **How the medium arrives, in one phrase.** A door that refuses a preset
/// because its medium cannot be attached has to be able to SAY which way
/// this platform takes one, and the word belongs beside the variants rather
/// than in every caller that needs it (LAW 5).
pub fn describe(self) -> String {
match self {
MediumBring::IsoCdrom => "an ISO attached as an IDE cdrom".to_string(),
MediumBring::ImportRaw => "an imported RAW disk image (no ISO attach)".to_string(),
MediumBring::ImportQcow2 => "an imported qcow2 disk image (no ISO attach)".to_string(),
MediumBring::ImportVhd => "an imported fixed-size VHD (no ISO attach)".to_string(),
MediumBring::Several(how) => format!("one of several imports: {how}"),
MediumBring::Metal(how) => format!("bare metal: {how}"),
MediumBring::Unknown => "undocumented — nobody has written down how a medium reaches this platform".to_string(),
}
}
/// Whether this platform takes an attached ISO at all. `false` means the
/// INSTALLER path does not exist here: a proof of such a preset can only
/// ever prove the INSTALLED disk boots on its device set.
///
/// **A PLATFORM fact, and only that.** It decides what a DOOR does — the
/// UpCloud deploy attaches two storages off this word, and
/// [`crate::installer::InstallerBuild::cloud_medium_gap`] reads it to say
/// that such a machine owes no import container, because the container its
/// cloud takes is the `.iso` [`crate::iso_writer`] already wrote. It says
/// NOTHING about the kernel: for that, ask
/// [`kernel_medium_bus`](Self::kernel_medium_bus), whose answer for the
/// same variant is `None`.
pub fn is_iso_attach(self) -> bool {
matches!(self, MediumBring::IsoCdrom)
}
/// **Which bus the RUNNING KERNEL must drive to READ the medium — `None`
/// when it never reads one.**
///
/// # This is NOT [`describe`](Self::describe)'s fact, and conflating them cost two lanes
///
/// `MediumBring` says how the **platform** hands a medium over.
/// [`crate::target::Target::buses`] says which **drivers the kernel must
/// carry**. For a medium that rides twice those are not one fact, and until
/// 2026-09-07 [`TargetPreset::target`] derived the second from the first:
/// `IsoCdrom` inserted [`Bus::IdeCdrom`](crate::target::Bus::IdeCdrom),
/// which puts `CONFIG_ATA` + `ATA_PIIX` + `BLK_DEV_SR` on the donor
/// kernel's edge. The shared donor `tunnr/tunnr-kernel` is the `nuked`
/// preset, whose fragment says `# CONFIG_ATA is not set`, so
/// [`crate::kernel::KernelBuild::apply_requirements`] refused the build —
/// *"a consumer requires what the preset vetoes"* — and four gunnar
/// installer rows carried `preset: None` for a month rather than name the
/// machine they had been running on.
///
/// # The measurement that settles it
///
/// **measured 2026-09-07, gunnar.rs on UpCloud (se-sto1):** the appliance
/// serving that box runs `tunnr-kernel`, whose shipped `kernel.config` says
/// `# CONFIG_ATA is not set` — and it booted from the ISO this crate
/// composes. If the derivation described the medium, that kernel could not
/// have started. It can, because the ISO is attached TWICE
/// (`gunnar/deploy/upcloud/src/machine.rs`):
///
/// * once as an **IDE cdrom**, which is what SeaBIOS boots — firmware
/// reading an El-Torito cdrom uses the BIOS' own INT 13h path and asks
/// Linux for no driver at all; and
/// * once as a **virtio disk**, which is where Linux and `korp-installer`
/// find the ISO-9660 `CD001` magic. The installer probes ONLY virtio, so
/// a cdrom-only attach boots the kernel, starts PID 1 and dies with
/// `INSTALLER: volume: no block device carries the ISO-9660 magic
/// (CD001) … (probed: loop1 … vda)` — no `sr0` anywhere, because the
/// kernel has no ATA to make one (MEASURED 2026-08-15, and again on the
/// SeaBIOS rig leg B1).
///
/// The virtio face needs no row here: it IS the machine's
/// [`boot_disk`](TargetPreset::boot_disk), already the first entry of
/// `buses`.
///
/// # Why a `Bus` and not a `bool`
///
/// So the day a platform hands a medium over on a device the kernel really
/// must drive — a cdrom-ONLY attach, an installer that learns to read `sr0`
/// — the answer is a bus word with a reason beside it, not a second table
/// somewhere else. Every variant is `None` TODAY, and each arm below says
/// why that is a finding rather than an oversight.
pub fn kernel_medium_bus(self) -> Option<crate::target::Bus> {
match self {
// The firmware boots the cdrom. A kernel is not REQUIRED to read it:
// the appliance donors do (tunnr 3f7c2ff, PIIX/AHCI + SR, a donor
// choice), and a kernel without that stack reads the virtio twin, which
// sits on the boot disk's own bus.
MediumBring::IsoCdrom => None,
// An imported disk is not a medium the running kernel opens at all:
// the install is ALREADY laid on it, and the device it arrives on is
// the machine's `boot_disk`, which owns that bus word.
MediumBring::ImportRaw | MediumBring::ImportQcow2 | MediumBring::ImportVhd => None,
// Several ways in, and every one of them in the roster is an import
// (AWS VM Import, GCE's disk.raw tar.gz). Same answer, same reason.
MediumBring::Several(_) => None,
// Bare metal: a burned stick or a PXE image the vendor's firmware
// reads, then the installed disk. `boot_disk` is `metal` and the
// group refuses to spell it at all, so nothing reaches a kernel
// edge from here.
MediumBring::Metal(_) => None,
// Nobody has written this platform down. An undocumented medium
// gets the answer that changes nothing (`None`), never a guessed
// driver: a wrong bus is a kernel that will not build, and this
// crate refuses a guess wearing a derivation's clothes.
MediumBring::Unknown => None,
}
}
}
/// The verdict: can QEMU on our proof box be shaped into this machine?
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mimic {
/// Yes — every device the guest must drive exists as a QEMU model, so the
/// `appliance-boot/<preset>` row is MEASURED under KVM.
Qemu,
/// No — the named device/behaviour has no QEMU model (or the platform is
/// not QEMU at all). The row is declared and stays `Absent (needs real
/// cloud)` until a real deploy measures it.
NeedsRealCloud(&'static str),
}
/// **What QEMU can do about ONE facet of a machine.**
///
/// The whole-machine [`Mimic`] verdict is a single word for five independent
/// facts, and a single word has to take the worst of them. That cost real
/// coverage: `aws-nitro` was `NeedsRealCloud` because ENA has no QEMU model, and
/// the verdict swallowed its firmware, its chipset, its medium and its **NVMe
/// boot disk** — which QEMU models exactly (`-device nvme`, measured on oden
/// 2026-09-05, QEMU 10.2.1) and which drives the guest's real
/// `CONFIG_BLK_DEV_NVME` path. Four facts went unproven because a fifth could
/// not be.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MimicAxis {
/// QEMU models this facet exactly; the word is the `-device`/`-machine`
/// value the door renders.
Exact(&'static str),
/// QEMU has no model for `missing`, but the door may render `with` in its
/// place and still exercise everything the facet is not: the bus topology,
/// the driver-independent boot, the address plumbing. A row proven this way
/// **must carry the substitution** — see [`MimicAxes::substitutions`] —
/// because a green that does not say what was swapped is the kind of green
/// this crate exists to refuse.
Substitute {
/// The device the real machine presents.
missing: &'static str,
/// What QEMU is asked to present instead.
with: &'static str,
},
/// Not renderable at all, with or without a substitution — bare metal, or a
/// platform that is not a hypervisor QEMU can stand in for.
Absent(&'static str),
}
impl MimicAxis {
/// Whether QEMU models this facet as the real machine has it.
pub fn is_exact(self) -> bool {
matches!(self, MimicAxis::Exact(_))
}
/// Whether the door can render this facet at all (exactly or by standing in).
pub fn is_renderable(self) -> bool {
!matches!(self, MimicAxis::Absent(_))
}
}
/// **The five facets of one machine, judged separately.**
///
/// Derived from the preset's own device words ([`TargetPreset::axes`]) rather
/// than declared, so it cannot drift from them the way the hand-written
/// [`Mimic`] field did.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MimicAxes {
/// UEFI vs SeaBIOS — QEMU renders both, so this is `Exact` unless the
/// platform is not a QEMU-shaped machine at all.
pub firmware: MimicAxis,
/// The chipset (`-machine pc` / `q35`).
pub chipset: MimicAxis,
/// The boot disk's bus.
pub disk: MimicAxis,
/// The NIC model.
pub nic: MimicAxis,
/// How the medium arrives (an attached cdrom vs an imported disk image).
pub medium: MimicAxis,
}
impl MimicAxes {
/// The five, in a fixed order, each with the facet's name — so a caller
/// reports them without re-listing the fields and getting the order wrong.
pub fn each(&self) -> [(&'static str, MimicAxis); 5] {
[
("firmware", self.firmware),
("chipset", self.chipset),
("disk", self.disk),
("nic", self.nic),
("medium", self.medium),
]
}
/// Every facet QEMU must stand in for, as `(facet, missing, with)`.
///
/// **A proof row must print this.** An empty list is the only thing that
/// makes a KVM green mean "this machine", and a non-empty one means the row
/// proved a SHAPE — everything but the named devices.
pub fn substitutions(&self) -> Vec<(&'static str, &'static str, &'static str)> {
self.each()
.into_iter()
.filter_map(|(facet, axis)| match axis {
MimicAxis::Substitute { missing, with } => Some((facet, missing, with)),
_ => None,
})
.collect()
}
/// Every facet that cannot be rendered at all, as `(facet, why)`.
pub fn absent(&self) -> Vec<(&'static str, &'static str)> {
self.each()
.into_iter()
.filter_map(|(facet, axis)| match axis {
MimicAxis::Absent(why) => Some((facet, why)),
_ => None,
})
.collect()
}
/// Every facet is modelled exactly — a KVM green here means the machine.
pub fn is_exact(&self) -> bool {
self.each().into_iter().all(|(_, a)| a.is_exact())
}
/// The door can launch this shape (no facet is [`MimicAxis::Absent`]),
/// though [`substitutions`](Self::substitutions) may be non-empty.
pub fn is_renderable(&self) -> bool {
self.each().into_iter().all(|(_, a)| a.is_renderable())
}
}
/// **One cloud's machine shape.** Every field a proof door or a kernel recipe
/// asks about, with the vendor-documented answer or an honest `Unknown`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TargetPreset {
/// The word on `--target <name>` and in the row `appliance-boot/<name>`.
pub name: &'static str,
/// The platform, for a human.
pub cloud: &'static str,
/// Which firmware starts the guest (a platform that offers both is
/// `Both`; the door then runs the one the preset's `chipset` implies unless
/// told otherwise).
pub firmware: Firmware,
pub secure_boot: SecureBoot,
/// The consoles the guest HAS, in `console=` order (last = `/dev/console`).
/// `serial` here means the FIRST port in `uarts`; naming it with no port
/// is refused by `Target::validate` on the derived group.
pub consoles: &'static [Console],
/// The legacy COM ports that really exist.
pub uarts: Uarts,
pub console_reach: ConsoleReach,
pub boot_disk: DiskBus,
pub medium: MediumBring,
pub nic: NicModel,
/// How many NICs the guest gets by default (UpCloud: public + utility).
pub nics: u8,
pub cpu: CpuModel,
pub chipset: Chipset,
pub acpi: Tri,
/// The metadata endpoint, when one exists (`169.254.169.254` almost
/// everywhere; Azure also has the wireserver `168.63.129.16`).
pub metadata: Option<&'static str>,
/// Is IPv6 configured on the guest NIC by default? Our kernel has NO IPv6
/// (the owner's ruling), so `Yes` here means DHCPv6/SLAAC traffic the
/// appliance will ignore, never a feature it needs.
pub ipv6_default: Tri,
pub mimic: Mimic,
/// **The evidence**: where and when this exact shape was booted, or `None`
/// for a shape that is researched but not yet measured. The one input to
/// [`TargetPreset::status`] that `mimic` does not decide — and since
/// 2026-09-05 it also carries WHICH KIND of machine took the measurement
/// ([`MeasuredOn`]), because "we booted UpCloud" and "we ran QEMU" are not
/// the same claim.
pub measured: Option<Evidence>,
/// One line a report can quote.
pub notes: &'static str,
}
impl TargetPreset {
/// Whether a `console=` points at a UART — the kernel drives `SERIAL_8250`.
pub fn has_serial(&self) -> bool {
self.consoles.contains(&Console::Serial)
}
/// Whether the guest has a VGA text console — the door takes a QMP
/// `screendump` for the proof when this is the only console.
pub fn has_vga(&self) -> bool {
self.consoles.contains(&Console::Vga)
}
/// Is there a 16550 UART at `0x3f8` (`ttyS0`)? Derived from [`Self::uarts`].
pub fn uart_ttys0(&self) -> Tri {
self.uarts.ttys0()
}
/// How well this shape is known: measured (on the cloud, or on a rig) >
/// mimicable-but-unproven (stub) > not mimicable (needs a real cloud).
/// Derived — including WHICH measurement it was, from the evidence's own
/// [`MeasuredOn`], so the roster cannot claim a cloud boot for a rig run.
pub fn status(&self) -> PresetStatus {
match (self.measured, self.mimic) {
(Some(e), _) => match e.on {
MeasuredOn::QemuRig => PresetStatus::MimicMeasured,
MeasuredOn::RealCloud => PresetStatus::CloudMeasured,
},
(None, Mimic::Qemu) => PresetStatus::Stub,
(None, Mimic::NeedsRealCloud(_)) => PresetStatus::NeedsRealCloud,
}
}
/// **The `target` group this preset IS** — the one derivation every
/// consumer reads (`Target::effective`, the emitted roster, the Kconfig
/// rows). `Err(why)` for a shape the group cannot spell yet (gVNIC,
/// Hyper-V, Xen, bare metal): the group grows when the kernel recipe learns
/// the Kconfig closure for that device, not before, and a row naming such a
/// preset is refused by name rather than built for a wrong default. NVMe
/// and ENA left that list on the aws-nitro lane — the closure now spells
/// `Bus::Nvme`/`Nic::Ena`, so `AWS_NITRO.target()` is `Ok` (it stays
/// `needs-real-cloud` because QEMU still cannot mimic the DEVICES).
///
/// The derivation, fact by fact:
/// * `buses` = the boot disk's bus word, plus the bus the KERNEL must drive
/// to read the medium when there is one ([`MediumBring::kernel_medium_bus`]
/// — today always `None`, and that method is where the reason lives). It
/// is NOT the way the platform hands the medium over: an ISO attached as
/// an IDE cdrom is booted by the FIRMWARE, and a kernel is not REQUIRED to
/// open it (the appliance donors do anyway since tunnr `3f7c2ff`, and
/// korp-installer ≥ 0.1.14 reads `sr0` first). The ISO's virtio twin, the
/// fallback for a kernel without that stack, is on the boot disk's own bus. Deriving `ide-cdrom` from `MediumBring::IsoCdrom` put
/// `CONFIG_ATA` on a donor that vetoes it and blocked four gunnar rows
/// for a month (2026-08-28 … 2026-09-07);
/// * `uarts` = exactly the declared ports ([`Uarts::ports`]; `Unknown` =
/// none), so `serial` in `consoles` resolves to the first REAL port
/// (`ttyS1` on UpCloud, never a `ttyS0` that is not there);
/// * `cpu` = the kernel's vendor half: an `EPYC*` model is `Cpu::Epyc`
/// (`CPU_SUP_AMD`); every other model, `host` passthrough and the qemu
/// default make no vendor claim (`Cpu::Generic`). The qemu `-cpu` word
/// stays the preset's own exact model ([`QemuShape::cpu`]);
/// * `nics` is at least 1 (a machine with no NIC serves nobody).
pub fn target(&self) -> Result<Target, String> {
let disk = self
.boot_disk
.target_bus()
.ok_or_else(|| format!("boot disk `{}` has no target.buses word yet", self.boot_disk.as_str()))?;
let nic = self
.nic
.target_nic()
.ok_or_else(|| format!("nic `{}` has no target.nic word yet", self.nic.as_str()))?;
let mut buses = vec![disk];
if let Some(b) = self.medium.kernel_medium_bus() {
if !buses.contains(&b) {
buses.insert(0, b);
}
}
let cpu = match self.cpu {
CpuModel::Named(m) if m.starts_with("EPYC") => Cpu::Epyc,
_ => Cpu::Generic,
};
// `preset` stays `None`: this IS the shape, not a name pointing at one.
Ok(Target {
preset: None,
firmware: self.firmware,
consoles: self.consoles.to_vec(),
uarts: self.uarts.ports().to_vec(),
buses,
nic,
nics: self.nics.max(1),
cpu,
})
}
/// **The five facets, judged separately** — see [`MimicAxes`].
///
/// DERIVED from this preset's own device words, never declared, so it cannot
/// drift from them. That matters twice over: the hand-written [`Mimic`]
/// field HAD drifted (`aws-xen-hvm` said Xen PV needs a Xen hypervisor while
/// QEMU has modelled `xen-disk`/`xen-net-device` all along), and a
/// second-source verdict is exactly the shape this crate refuses everywhere
/// else. [`mimic_agrees_with_axes`](Self::mimic_agrees_with_axes) is the
/// guard that keeps the two honest.
pub fn axes(&self) -> MimicAxes {
// Bare metal is the one platform nothing stands in for: the board is the
// board, the firmware is the vendor's, the devices are soldered on.
let metal = matches!(self.medium, MediumBring::Metal(_));
let firmware = if metal {
MimicAxis::Absent("bare metal: the vendor's own UEFI, not OVMF")
} else {
MimicAxis::Exact(match self.firmware {
Firmware::Uefi => "OVMF",
Firmware::SeaBios => "SeaBIOS",
Firmware::Both => "OVMF or SeaBIOS",
})
};
let chipset = match (metal, self.chipset.qemu_machine()) {
(true, _) => MimicAxis::Absent("bare metal: the board is the board"),
(false, Some(m)) => MimicAxis::Exact(m),
// Hyper-V gen2 has no PCI bus and Xen HVM is its own platform, so
// neither IS a QEMU machine type. A q35 guest still stands in for
// everything the chipset is not — the firmware handoff, the memory
// map, the boot order, the medium — which is the whole point of
// judging this facet apart from the devices hanging off it.
(false, None) => MimicAxis::Substitute {
missing: match self.chipset {
// Hyper-V gen2 and Xen HVM really are other platforms.
Chipset::NotQemu => "the platform's own chipset",
// `Unknown` is not a claim that the chipset differs — it is
// that nobody wrote it down. It substitutes for the same
// reason (the door has to render SOMETHING) but a reader
// must be able to tell "we know it differs" from "we never
// looked", because only the second one is closed by
// research rather than by hardware.
_ => "an undocumented chipset",
},
with: "q35",
},
};
let disk = match self.boot_disk.qemu_mimic() {
Some(d) => MimicAxis::Exact(d),
None if metal => MimicAxis::Absent("bare metal: real NVMe/SATA"),
// `hv_storvsc` is a SCSI LLDD over VMBus and `virtio-scsi` is a SCSI
// LLDD over virtio: the guest's `SCSI`/`BLK_DEV_SD` layers, the
// partition scan and the root mount are the same code either way.
// Only the transport differs, and that is what the substitution
// says out loud.
None => MimicAxis::Substitute {
missing: self.boot_disk.as_str(),
with: "virtio-scsi-pci",
},
};
let nic = match self.nic.qemu_mimic() {
Some(n) => MimicAxis::Exact(n),
None if metal => MimicAxis::Absent("bare metal: a real NIC"),
None => MimicAxis::Substitute {
missing: self.nic.as_str(),
with: "virtio-net-pci",
},
};
let medium = match self.medium {
MediumBring::Metal(how) => MimicAxis::Absent(how),
MediumBring::Unknown => MimicAxis::Absent(
"undocumented — nobody has written down how a medium reaches this platform",
),
// An imported disk image is a plain `-drive`; an ISO is `-cdrom`.
// Both are things QEMU does, which is why a cloud with "no ISO door"
// is a PRODUCT-shape constraint and not a mimic constraint.
m if m.is_iso_attach() => MimicAxis::Exact("-cdrom"),
_ => MimicAxis::Exact("imported disk image as a -drive"),
};
MimicAxes { firmware, chipset, disk, nic, medium }
}
/// **Does the declared [`Mimic`] say the same thing the device words do?**
///
/// `Mimic::Qemu` must mean every axis is [`MimicAxis::Exact`], and
/// `Mimic::NeedsRealCloud` must mean at least one is not. Anything else is
/// two sources of truth disagreeing, which is how `aws-xen-hvm` sat
/// unprovable for a year on a sentence that was never true of this QEMU.
pub fn mimic_agrees_with_axes(&self) -> bool {
match self.mimic {
Mimic::Qemu => self.axes().is_exact(),
Mimic::NeedsRealCloud(_) => !self.axes().is_exact(),
}
}
/// The QEMU rendering of this shape, when QEMU can mimic it.
pub fn qemu_shape(&self) -> Option<QemuShape> {
let Mimic::Qemu = self.mimic else { return None };
Some(QemuShape {
machine: self.chipset.qemu_machine()?,
cpu: self.cpu.qemu_arg(),
disk_device: self.boot_disk.qemu_mimic()?,
nic_device: self.nic.qemu_mimic()?,
nics: self.nics.max(1),
uarts: self.uarts.slots(),
serial: self.has_serial(),
vga: self.has_vga(),
firmware: self.firmware,
medium_as_cdrom: self.medium.is_iso_attach(),
})
}
/// The reason this preset cannot be measured under QEMU, if any.
pub fn gap(&self) -> Option<&'static str> {
match self.mimic {
Mimic::Qemu => None,
Mimic::NeedsRealCloud(why) => Some(why),
}
}
}
/// What the proof door hands `kvmproof::Machine` and the attachment writers.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QemuShape {
pub machine: &'static str,
pub cpu: Option<&'static str>,
pub disk_device: &'static str,
pub nic_device: &'static str,
pub nics: u8,
/// The COM slots to create, COM1..COM4 — `kvmproof::Machine::uarts`: a
/// present slot is captured to a file, an absent one is `-serial none`.
/// UpCloud is `[false, true, true, false]`: nothing at 0x3f8, real
/// ttyS1/ttyS2.
pub uarts: [bool; 4],
/// A `console=` names a UART — the guest's kernel WRITES to one, so the
/// door may gate on the serial log. False = gate on applied output.
pub serial: bool,
/// A VGA adapter exists and the proof may `screendump` it.
pub vga: bool,
pub firmware: Firmware,
/// The medium rides `-cdrom` (IDE) in addition to its virtio face.
pub medium_as_cdrom: bool,
}
// ── the table ────────────────────────────────────────────────────────────────
//
// Sources and confidence per value: edda/.nornir/cloud-target-profiles.md.
/// Our own proof box, the shape every pre-2026-08-28 proof ran: q35 + OVMF,
/// UART captured, virtio-blk, virtio-net, qemu64.
///
/// **This preset IS `Target::default()`** (pinned by test): the medium is
/// laid for both firmwares (the box starts either; the Q35 chipset makes the
/// door pick OVMF), one NIC, COM1. An undeclared `target` group therefore
/// means "qemu-local" by construction, not by a second table.
pub const QEMU_LOCAL: TargetPreset = TargetPreset {
name: "qemu-local",
cloud: "this box: qemu-system-x86_64 + KVM, OVMF",
firmware: Firmware::Both,
secure_boot: SecureBoot::Optional,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::VirtioBlk,
medium: MediumBring::IsoCdrom,
nic: NicModel::VirtioNet,
nics: 1,
cpu: CpuModel::QemuDefault,
chipset: Chipset::Q35,
acpi: Tri::Yes,
metadata: None,
ipv6_default: Tri::No,
mimic: Mimic::Qemu,
measured: Some(Evidence::rig("every installer proof before 2026-08-28: OVMF, COM1 captured, virtio")),
notes: "the historical proof shape; a green here proves the medium, not any cloud",
};
/// The same box under SeaBIOS (`-machine pc`), still with a UART.
pub const QEMU_LOCAL_BIOS: TargetPreset = TargetPreset {
name: "qemu-local-bios",
cloud: "this box: qemu-system-x86_64 + KVM, SeaBIOS",
firmware: Firmware::SeaBios,
secure_boot: SecureBoot::None,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::VirtioBlk,
medium: MediumBring::IsoCdrom,
nic: NicModel::VirtioNet,
nics: 1,
cpu: CpuModel::QemuDefault,
chipset: Chipset::Pc,
acpi: Tri::Yes,
metadata: None,
ipv6_default: Tri::No,
mimic: Mimic::Qemu,
measured: Some(Evidence::rig("2026-08-29 gunnar-installtest --firmware seabios: B1 2002 ms INSTALL-OK, B2 3820 ms, B3 2020 ms")),
notes: "SeaBIOS with a UART: the half-way house that hid UpCloud's dark boot",
};
/// UpCloud Cloud Servers — measured 2026-08-28/29 on gunnar-appliance
/// (se-sto1) and confirmed by the docs where they speak.
pub const UPCLOUD: TargetPreset = TargetPreset {
name: "upcloud",
cloud: "UpCloud Cloud Servers",
firmware: Firmware::SeaBios,
secure_boot: SecureBoot::None,
consoles: &[Console::Vga],
uarts: Uarts::Known(&[UartPort::Com2, UartPort::Com3]),
console_reach: ConsoleReach::VgaVnc,
boot_disk: DiskBus::VirtioBlk,
medium: MediumBring::IsoCdrom,
nic: NicModel::VirtioNet,
nics: 2,
cpu: CpuModel::Named("EPYC-Rome"),
chipset: Chipset::Pc,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::Yes,
mimic: Mimic::Qemu,
measured: Some(Evidence::cloud("2026-08-28/29 gunnar-appliance se-sto1: SeaBIOS only, ISO as IDE cdrom, no ttyS0, VNC the only console")),
notes: "no ttyS0 (ttyS1/ttyS2 at 0x2f8/0x3e8 exist), VNC is the only console, ISO = IDE cdrom, \
disk = virtio-blk, public + utility NIC; cpu model is measured from the guest, not documented",
};
/// Scaleway Instances (DEV1 / PLAY2 / PRO2): KVM, virtio everywhere.
pub const SCALEWAY_INSTANCE: TargetPreset = TargetPreset {
name: "scaleway-instance",
cloud: "Scaleway Instances (DEV1, PLAY2, PRO2)",
firmware: Firmware::Uefi,
secure_boot: SecureBoot::Unknown,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Serial,
boot_disk: DiskBus::VirtioBlk,
medium: MediumBring::ImportQcow2,
nic: NicModel::VirtioNet,
nics: 1,
cpu: CpuModel::Unknown,
chipset: Chipset::Unknown,
acpi: Tri::Yes,
metadata: Some("169.254.42.42"),
ipv6_default: Tri::No,
mimic: Mimic::NeedsRealCloud(
"no ISO attach: a custom image is a qcow2 snapshot import, so the INSTALLER path \
does not exist on this platform — the installed disk must be imported pre-laid",
),
measured: None,
notes: "no ISO door; the appliance would ship as an imported disk image, a different product shape",
};
/// Scaleway Elastic Metal: real hardware.
pub const SCALEWAY_ELASTIC_METAL: TargetPreset = TargetPreset {
name: "scaleway-elastic-metal",
cloud: "Scaleway Elastic Metal",
firmware: Firmware::Uefi,
secure_boot: SecureBoot::Unknown,
// No documented COM port → no `serial` console may be named (a serial
// console nobody can prove exists is the dark boot).
consoles: &[Console::Vga],
uarts: Uarts::Unknown,
console_reach: ConsoleReach::Serial,
boot_disk: DiskBus::Metal,
medium: MediumBring::Metal("rescue mode + dd / custom install via the console"),
nic: NicModel::Metal,
nics: 1,
cpu: CpuModel::Native,
chipset: Chipset::NotQemu,
acpi: Tri::Yes,
metadata: Some("169.254.42.42"),
ipv6_default: Tri::No,
mimic: Mimic::NeedsRealCloud("bare metal: real NVMe/SATA, real NIC, the vendor's UEFI"),
measured: None,
notes: "the iso-metal row of the chain matrix, not a KVM row",
};
/// AWS EC2 on the legacy Xen HVM families (m4, c4, t2, …).
pub const AWS_XEN_HVM: TargetPreset = TargetPreset {
name: "aws-xen-hvm",
cloud: "AWS EC2, Xen HVM instance families",
firmware: Firmware::SeaBios,
secure_boot: SecureBoot::None,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::XenBlk,
medium: MediumBring::Several("VM Import (raw/vhd/vmdk/ova) → AMI; no ISO attach"),
nic: NicModel::XenNetfront,
nics: 1,
cpu: CpuModel::Native,
chipset: Chipset::NotQemu,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::No,
// RE-MEASURED and RE-CONFIRMED 2026-09-05 (see `DiskBus::qemu_mimic`): this
// sentence was called a lie on the strength of a `-device help` line, and it
// is not one. QEMU 10.2.1 on oden has no `xen` accelerator, no machine with
// `xen-version`, and therefore no `xen-bus` — `-device xen-disk` answers
// "No 'xen-bus' bus found". The listing shows a model; the bus is what a
// guest binds against.
mimic: Mimic::NeedsRealCloud("xen blkfront/netfront need a Xen hypervisor: this QEMU lists xen-disk/xen-net-device but has no xen accel and no xen-bus to attach them to"),
measured: None,
notes: "legacy; the serial console is the instance screenshot + get-console-output, not interactive",
};
/// AWS EC2 on Nitro (m5+, c5+, t3+, all Graviton on arm64).
pub const AWS_NITRO: TargetPreset = TargetPreset {
name: "aws-nitro",
cloud: "AWS EC2, Nitro instance families",
firmware: Firmware::Both,
secure_boot: SecureBoot::Optional,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::Nvme,
medium: MediumBring::Several("VM Import (raw/vhd/vmdk/ova) or import-snapshot + register-image --boot-mode; no ISO attach"),
nic: NicModel::Ena,
nics: 1,
cpu: CpuModel::Native,
chipset: Chipset::NotQemu,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::No,
mimic: Mimic::NeedsRealCloud("ENA has no QEMU model; the NVMe controller is Nitro's own, not QEMU's"),
measured: None,
notes: "boot mode per AMI (legacy-bios | uefi | uefi-preferred); EC2 Serial Console = ttyS0",
};
/// Google Compute Engine with virtio devices (PD on virtio-scsi, virtio-net).
pub const GCE_VIRTIO: TargetPreset = TargetPreset {
name: "gce-virtio",
cloud: "Google Compute Engine, virtio-net + virtio-scsi Persistent Disk",
firmware: Firmware::Uefi,
secure_boot: SecureBoot::Optional,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::VirtioScsi,
medium: MediumBring::Several("raw disk.raw in a tar.gz → images create; no ISO attach"),
nic: NicModel::VirtioNet,
nics: 1,
cpu: CpuModel::Named("Skylake-Server"),
chipset: Chipset::Pc,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::No,
mimic: Mimic::Qemu,
measured: None,
notes: "the medium is a disk image, not an ISO; the qemu row proves the INSTALLED disk boots on \
this device set, not that the installer runs there",
};
/// Google Compute Engine with gVNIC (default/required on gen3+ machine series).
pub const GCE_GVNIC: TargetPreset = TargetPreset {
name: "gce-gvnic",
cloud: "Google Compute Engine, gVNIC + NVMe (gen3+ series, Hyperdisk, Local SSD)",
firmware: Firmware::Uefi,
secure_boot: SecureBoot::DefaultOn,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::Nvme,
medium: MediumBring::Several("raw disk.raw in a tar.gz → images create; no ISO attach"),
nic: NicModel::Gvnic,
nics: 1,
cpu: CpuModel::Native,
chipset: Chipset::Pc,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::No,
mimic: Mimic::NeedsRealCloud("gVNIC has no QEMU model; kernel needs GVE"),
measured: None,
notes: "C3/N4/C4 and newer require gVNIC; MTU 1460",
};
/// Azure Generation 1 (Hyper-V gen1).
pub const AZURE_GEN1: TargetPreset = TargetPreset {
name: "azure-gen1",
cloud: "Microsoft Azure, Generation 1 VM",
firmware: Firmware::SeaBios,
secure_boot: SecureBoot::None,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::Ide,
medium: MediumBring::ImportVhd,
nic: NicModel::HvNetvsc,
nics: 1,
cpu: CpuModel::Native,
chipset: Chipset::NotQemu,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::No,
mimic: Mimic::NeedsRealCloud("Hyper-V synthetic NIC/SCSI (hv_netvsc, hv_storvsc) need Hyper-V"),
measured: None,
notes: "OS disk on IDE, data disks on hv_storvsc; fixed-size VHD only; serial via boot diagnostics",
};
/// Azure Generation 2 (Hyper-V gen2, UEFI).
pub const AZURE_GEN2: TargetPreset = TargetPreset {
name: "azure-gen2",
cloud: "Microsoft Azure, Generation 2 VM (Trusted Launch optional)",
firmware: Firmware::Uefi,
secure_boot: SecureBoot::Optional,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::Both,
boot_disk: DiskBus::HvScsi,
medium: MediumBring::ImportVhd,
nic: NicModel::HvNetvsc,
nics: 1,
cpu: CpuModel::Native,
chipset: Chipset::NotQemu,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::No,
mimic: Mimic::NeedsRealCloud("Hyper-V gen2: no PCI bus, no IDE, hv_storvsc + hv_netvsc only"),
measured: None,
notes: "no legacy devices at all; framebuffer is hyperv_fb/hyperv_drm",
};
/// Hetzner Cloud.
pub const HETZNER_CLOUD: TargetPreset = TargetPreset {
name: "hetzner-cloud",
cloud: "Hetzner Cloud",
firmware: Firmware::SeaBios,
secure_boot: SecureBoot::None,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::VgaVnc,
boot_disk: DiskBus::VirtioBlk,
medium: MediumBring::IsoCdrom,
nic: NicModel::VirtioNet,
nics: 1,
cpu: CpuModel::Unknown,
chipset: Chipset::Pc,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::Yes,
mimic: Mimic::Qemu,
measured: None,
notes: "ISO library + custom ISO on request, attached as cdrom; volumes ride virtio-scsi",
};
/// OVHcloud Public Cloud (OpenStack).
pub const OVH_PUBLIC_CLOUD: TargetPreset = TargetPreset {
name: "ovh-public-cloud",
cloud: "OVHcloud Public Cloud instances (OpenStack/KVM)",
firmware: Firmware::SeaBios,
secure_boot: SecureBoot::None,
consoles: &[Console::Vga, Console::Serial],
uarts: Uarts::Known(&[UartPort::Com1]),
console_reach: ConsoleReach::VgaVnc,
boot_disk: DiskBus::VirtioBlk,
medium: MediumBring::ImportQcow2,
nic: NicModel::VirtioNet,
nics: 1,
cpu: CpuModel::Unknown,
chipset: Chipset::Pc,
acpi: Tri::Yes,
metadata: Some("169.254.169.254"),
ipv6_default: Tri::Yes,
mimic: Mimic::Qemu,
measured: None,
notes: "glance image (qcow2/raw) import; UEFI via hw_firmware_type=uefi image property",
};
/// **Every preset**, in report order — THE roster. `Target::validate` resolves
/// `preset = "<name>"` here, `super::presets_toml` emits it, and
/// [`boot_roster`] floors the test matrix's `appliance-boot/<name>` rows with
/// it. Add a machine = add a const above and a line here; nothing else.
pub const PRESETS: &[TargetPreset] = &[
QEMU_LOCAL,
QEMU_LOCAL_BIOS,
UPCLOUD,
SCALEWAY_INSTANCE,
SCALEWAY_ELASTIC_METAL,
AWS_XEN_HVM,
AWS_NITRO,
GCE_VIRTIO,
GCE_GVNIC,
AZURE_GEN1,
AZURE_GEN2,
HETZNER_CLOUD,
OVH_PUBLIC_CLOUD,
];
/// Look a preset up by its word.
pub fn preset(name: &str) -> Option<&'static TargetPreset> {
PRESETS.iter().find(|p| p.name == name)
}
/// The names, for a usage line or a roster check.
pub fn names() -> Vec<&'static str> {
PRESETS.iter().map(|p| p.name).collect()
}
/// **The test matrix's `appliance-boot/<preset>` roster, emitted from the
/// code.** One row per preset; a mimicable preset is a KVM row (a hole the
/// installtest fills), a `NeedsRealCloud` preset is an `Absent (needs real
/// cloud: …)` row until a real deploy emits under that component name.
/// `nornir_testmatrix::appliance::ApplianceBootGrid` folds rows onto it.
/// Each row also carries the preset's SUBSTITUTIONS
/// ([`MimicAxes::substitutions`]) — the devices QEMU must stand in for. A KVM
/// green with a non-empty substitution list proved a SHAPE, not a machine, and
/// a row that does not say which devices were swapped is exactly the kind of
/// green this constellation refuses. `ApplianceBootGrid` enforces it: a `pass`
/// row for such a preset that does not NAME them is folded RED.
#[cfg(feature = "testmatrix")]
pub fn boot_roster() -> Vec<nornir_testmatrix::appliance::BootPreset> {
use nornir_testmatrix::appliance::{BootPreset, BootReach, BootSubstitution};
PRESETS
.iter()
.map(|p| BootPreset {
name: p.name.to_string(),
reach: match p.gap() {
None => BootReach::Kvm,
Some(why) => BootReach::RealCloud(why.to_string()),
},
// The evidence never travels without saying which kind it is —
// `real cloud: …` vs `qemu rig: …`, from `MeasuredOn`.
measured: p.measured.map(|e| e.label()),
substitutions: p
.axes()
.substitutions()
.into_iter()
.map(|(facet, missing, with)| BootSubstitution {
facet: facet.to_string(),
missing: missing.to_string(),
with: with.to_string(),
})
.collect(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_are_unique_and_lookup_is_by_word() {
let mut seen = std::collections::BTreeSet::new();
for p in PRESETS {
assert!(seen.insert(p.name), "duplicate preset name {}", p.name);
assert_eq!(preset(p.name).map(|q| q.name), Some(p.name));
}
assert!(preset("coreboot-cloud").is_none());
}
/// Every preset that says QEMU can mimic it renders a full QEMU shape, and
/// every one that says it cannot names its reason.
#[test]
fn mimicable_presets_render_a_qemu_shape_and_the_rest_name_the_gap() {
for p in PRESETS {
match p.mimic {
Mimic::Qemu => {
let s = p.qemu_shape().unwrap_or_else(|| panic!("{}: claims Qemu but renders no shape", p.name));
assert!(!s.machine.is_empty());
assert_eq!(s.serial, p.has_serial());
assert!(p.gap().is_none());
}
Mimic::NeedsRealCloud(why) => {
assert!(p.qemu_shape().is_none(), "{}: NeedsRealCloud must not render", p.name);
assert_eq!(p.gap(), Some(why));
assert!(!why.is_empty());
}
}
assert!(!p.consoles.is_empty(), "{}: a boot nobody can see", p.name);
}
}
/// UpCloud is the shape that hurt: SeaBIOS, no UART, VGA only, IDE cdrom +
/// virtio-blk, two virtio NICs — and it IS mimicable.
#[test]
fn upcloud_is_the_measured_shape() {
let s = UPCLOUD.qemu_shape().unwrap();
assert_eq!(s.machine, "pc");
assert!(!s.serial && s.vga && s.medium_as_cdrom);
assert_eq!(s.nics, 2);
assert_eq!(s.disk_device, "virtio-blk-pci");
assert_eq!(s.nic_device, "virtio-net-pci");
assert_eq!(UPCLOUD.uart_ttys0(), Tri::No);
assert_eq!(s.uarts, [false, true, true, false], "ttyS1/ttyS2 exist, ttyS0 does not");
let t = UPCLOUD.target().unwrap();
assert_eq!(t.firmware, Firmware::SeaBios);
assert_eq!(t.consoles, vec![Console::Vga]);
assert_eq!(t.uarts, vec![UartPort::Com2, UartPort::Com3]);
assert_eq!(
t.buses,
vec![Bus::VirtioBlk],
"the cdrom is how the PLATFORM hands the medium over, not a bus the preset requires the kernel to drive \
(the appliance donors read it anyway since tunnr 3f7c2ff; the virtio twin is the fallback)"
);
assert_eq!(t.cpu, Cpu::Epyc);
assert_eq!(t.nics, 2);
t.validate("upcloud").unwrap();
// The one preset whose evidence is a REAL instance, and the whole point
// of splitting the word: this is not the same claim `qemu-local-bios`
// makes.
assert_eq!(UPCLOUD.status(), PresetStatus::CloudMeasured);
assert_eq!(UPCLOUD.measured.unwrap().on, MeasuredOn::RealCloud);
assert_eq!(QEMU_LOCAL_BIOS.status(), PresetStatus::MimicMeasured);
assert_eq!(QEMU_LOCAL_BIOS.measured.unwrap().on, MeasuredOn::QemuRig);
}
/// The `target` group cannot spell gVNIC yet, and the preset says so with
/// `None` rather than a wrong default. NVMe + ENA ARE spellable (the
/// aws-nitro lane taught the closure), so `AWS_NITRO` derives a group —
/// `nvme` on the bus, `ena` on the NIC — and since 2026-09-05 so does
/// `AZURE_GEN2`: `hv-storvsc` on the bus, `hv-netvsc` on the NIC. `GCE_GVNIC`
/// still fails, on its NIC (`nvme` disk is fine, `gvnic` is not), and
/// `AZURE_GEN1` on its IDE OS disk — the group spells `ide-cdrom` only.
#[test]
fn a_shape_the_target_group_cannot_spell_yields_no_target() {
let nitro = AWS_NITRO.target().expect("nvme + ena is spellable since the aws-nitro lane");
assert!(nitro.has_bus(Bus::Nvme), "aws-nitro boot disk is nvme");
assert_eq!(nitro.nic, Nic::Ena, "aws-nitro nic is ena");
assert!(!nitro.has_bus(Bus::IdeCdrom), "the Nitro medium is an imported disk, never a cdrom");
nitro.validate("aws-nitro").unwrap();
let gen2 = AZURE_GEN2.target().expect("hv-storvsc + hv-netvsc is spellable since the azure lane");
assert!(gen2.has_bus(Bus::HvStorvsc), "azure-gen2 boot disk is Hyper-V synthetic SCSI");
assert_eq!(gen2.nic, Nic::HvNetvsc, "azure-gen2 nic is the synthetic NIC");
assert!(!gen2.has_bus(Bus::IdeCdrom), "the Azure medium is an imported VHD, never a cdrom");
gen2.validate("azure-gen2").unwrap();
// The W2 lane (2026-09-05) taught the closure gVNIC and the two Xen
// frontends, so these two derive a group now. Both stay
// `needs-real-cloud`: the KERNEL can spell the device, QEMU still
// cannot present it — the two questions the axes keep apart.
let gvnic = GCE_GVNIC.target().expect("gvnic is spellable since the W2 device-words lane");
assert!(gvnic.has_bus(Bus::Nvme), "gce-gvnic boots off NVMe");
assert_eq!(gvnic.nic, Nic::Gvnic);
gvnic.validate("gce-gvnic").unwrap();
assert_eq!(GCE_GVNIC.status(), PresetStatus::NeedsRealCloud, "spellable is not mimicable");
let xen = AWS_XEN_HVM.target().expect("xen blkfront + netfront are spellable since the W2 lane");
assert!(xen.has_bus(Bus::XenBlk), "aws-xen-hvm boots off blkfront");
assert_eq!(xen.nic, Nic::XenNetfront);
assert!(!xen.has_bus(Bus::IdeCdrom), "the Xen HVM medium is an imported image, never a cdrom");
xen.validate("aws-xen-hvm").unwrap();
assert_eq!(AWS_XEN_HVM.status(), PresetStatus::NeedsRealCloud);
// `azure-gen1` is the one shape still unspellable, and on its OS disk:
// a Gen1 VM boots a real IDE HARD DISK, which the group spells only as
// a cdrom. That is a genuine gap, not an oversight.
assert!(AZURE_GEN1.target().unwrap_err().contains("ide"), "a Gen1 OS disk rides IDE, which the group spells only as a cdrom");
assert!(GCE_VIRTIO.target().is_ok(), "virtio-scsi + virtio-net is spellable");
assert!(QEMU_LOCAL.target().is_ok());
}
/// **Every preset's derivation holds**: a spellable one yields a `Target`
/// that validates (no serial console without a port, a bus, a NIC), an
/// unspellable one names the device the group cannot spell, and the
/// status is derived — `measured` only ever on a mimicable shape.
#[test]
fn every_preset_derives_a_valid_target_or_names_why_not() {
for p in PRESETS {
match p.target() {
Ok(t) => {
t.validate(p.name).unwrap_or_else(|e| panic!("{}: {e}", p.name));
assert_eq!(t.preset, None, "{}: a derived shape names no preset", p.name);
assert_eq!(t.uarts, p.uarts.ports().to_vec(), "{}: uarts are the declared ports", p.name);
assert_eq!(t.cpu == Cpu::Epyc, matches!(p.cpu, CpuModel::Named(m) if m.starts_with("EPYC")), "{}", p.name);
assert_eq!(
t.has_bus(Bus::IdeCdrom),
p.medium.kernel_medium_bus() == Some(Bus::IdeCdrom),
"{}: a bus in the derived group is a driver the KERNEL must carry, never the way the platform hands the medium over",
p.name
);
}
Err(why) => {
assert!(p.boot_disk.target_bus().is_none() || p.nic.target_nic().is_none(), "{}: {why}", p.name);
assert_eq!(p.gap().is_some(), true, "{}: unspellable implies not mimicable today", p.name);
}
}
// A RIG measurement is only possible on a shape the rig can render;
// a CLOUD measurement is possible on anything (that is the point of
// a real deploy) and must NOT be gated on `Mimic::Qemu` — that
// conflation is what the split undoes.
if let Some(e) = p.measured {
match e.on {
MeasuredOn::QemuRig => assert!(
p.axes().is_renderable(),
"{}: claims a qemu-rig measurement of a shape the rig cannot even launch",
p.name
),
MeasuredOn::RealCloud => {}
}
assert!(!e.what.is_empty(), "{}: evidence with no observation", p.name);
}
assert_eq!(
p.status(),
match (p.measured.map(|e| e.on), p.mimic) {
(Some(MeasuredOn::QemuRig), _) => PresetStatus::MimicMeasured,
(Some(MeasuredOn::RealCloud), _) => PresetStatus::CloudMeasured,
(None, Mimic::Qemu) => PresetStatus::Stub,
_ => PresetStatus::NeedsRealCloud,
}
);
}
// **The split, pinned by name.** ONE cloud has actually been booted;
// the other two "measured" rows are our own QEMU box. Before 2026-09-05
// this read `measured = [qemu-local, qemu-local-bios, upcloud]` and
// said "3 measured", which sounded like three clouds.
let cloud: Vec<_> = PRESETS.iter().filter(|p| p.status() == PresetStatus::CloudMeasured).map(|p| p.name).collect();
assert_eq!(cloud, vec!["upcloud"], "the machines we have actually booted on the real platform");
let rig: Vec<_> = PRESETS.iter().filter(|p| p.status() == PresetStatus::MimicMeasured).map(|p| p.name).collect();
assert_eq!(rig, vec!["qemu-local", "qemu-local-bios"], "the machines measured on our own QEMU");
// And the old question still has an answer, for a caller that only
// wants "is there evidence at all".
let any: Vec<_> = PRESETS.iter().filter(|p| p.status().is_measured()).map(|p| p.name).collect();
assert_eq!(any, vec!["qemu-local", "qemu-local-bios", "upcloud"]);
}
/// **The Kconfig consequence per preset** — what `Target::kconfig_needs`
/// says for every spellable machine, symbol by symbol. UpCloud is the row
/// that matters, and both of its surprises are 2026-09-07 rulings written
/// down: `SERIAL_8250` is `y` (ttyS1/ttyS2 at 0x2f8/0x3e8 are REAL — only
/// the ttyS0 nobody has is missing) while `SERIAL_8250_CONSOLE` is ABSENT
/// (VNC is what anyone reads); and NO `ATA` REQUIREMENT, because the IDE
/// cdrom is how the PLATFORM hands the medium over and the `nuked` donor
/// would refuse a consumer row for it. The kernel reads that cdrom
/// all the same: since tunnr `3f7c2ff` the donor carries PIIX/AHCI + SR in
/// its own fragment. The virtio twin is the fallback. VT + VGA_CONSOLE, VIRTIO_BLK +
/// VIRTIO_NET, CPU_SUP_AMD for the EPYC.
#[test]
fn kconfig_needs_per_preset() {
// (preset, symbols that must be `y`, symbols that must be `n`, symbols that must be ABSENT)
let table: &[(&str, &[&str], &[&str], &[&str])] = &[
(
"upcloud",
&["CONFIG_SERIAL_8250", "CONFIG_SERIAL_EARLYCON", "CONFIG_VT", "CONFIG_VT_CONSOLE", "CONFIG_VGA_CONSOLE", "CONFIG_VIRTIO_BLK", "CONFIG_VIRTIO_PCI", "CONFIG_VIRTIO_NET", "CONFIG_CPU_SUP_AMD"],
&[],
&["CONFIG_SERIAL_8250_CONSOLE", "CONFIG_ATA", "CONFIG_ATA_PIIX", "CONFIG_BLK_DEV_SR", "CONFIG_ISO9660_FS", "CONFIG_E1000", "CONFIG_SCSI_VIRTIO"],
),
(
"qemu-local",
&["CONFIG_SERIAL_8250", "CONFIG_SERIAL_8250_CONSOLE", "CONFIG_VT", "CONFIG_VGA_CONSOLE", "CONFIG_VIRTIO_BLK", "CONFIG_VIRTIO_NET"],
&[],
&["CONFIG_CPU_SUP_AMD", "CONFIG_E1000", "CONFIG_SCSI_VIRTIO", "CONFIG_ATA"],
),
(
"qemu-local-bios",
&["CONFIG_SERIAL_8250", "CONFIG_VT", "CONFIG_VGA_CONSOLE", "CONFIG_VIRTIO_BLK", "CONFIG_VIRTIO_NET"],
&[],
&["CONFIG_CPU_SUP_AMD", "CONFIG_ATA"],
),
(
"gce-virtio",
&["CONFIG_SERIAL_8250", "CONFIG_VT", "CONFIG_SCSI", "CONFIG_BLK_DEV_SD", "CONFIG_SCSI_VIRTIO", "CONFIG_VIRTIO_NET"],
&[],
&["CONFIG_ATA", "CONFIG_VIRTIO_BLK", "CONFIG_CPU_SUP_AMD"],
),
(
"hetzner-cloud",
&["CONFIG_SERIAL_8250", "CONFIG_VT", "CONFIG_VIRTIO_BLK", "CONFIG_VIRTIO_NET"],
&[],
&["CONFIG_CPU_SUP_AMD", "CONFIG_SCSI_VIRTIO", "CONFIG_ATA"],
),
(
"ovh-public-cloud",
&["CONFIG_SERIAL_8250", "CONFIG_VT", "CONFIG_VIRTIO_BLK", "CONFIG_VIRTIO_NET"],
&[],
&["CONFIG_ATA", "CONFIG_CPU_SUP_AMD"],
),
(
"scaleway-instance",
&["CONFIG_SERIAL_8250", "CONFIG_VT", "CONFIG_VIRTIO_BLK", "CONFIG_VIRTIO_NET"],
&[],
&["CONFIG_ATA", "CONFIG_CPU_SUP_AMD"],
),
(
// The aws-nitro lane's row: NVMe block + ENA net, both `y`, and
// NO virtio/scsi/ata/e1000 at all — Nitro has none of those
// devices. Serial is real (ttyS0 = EC2 Serial Console), and the
// cpu makes no vendor claim (Native ⇒ Generic, no CPU_SUP_AMD).
"aws-nitro",
&["CONFIG_SERIAL_8250", "CONFIG_SERIAL_8250_CONSOLE", "CONFIG_VT", "CONFIG_VGA_CONSOLE", "CONFIG_NVME_CORE", "CONFIG_BLK_DEV_NVME", "CONFIG_NET", "CONFIG_ETHERNET", "CONFIG_NET_VENDOR_AMAZON", "CONFIG_PCI_MSI", "CONFIG_ENA_ETHERNET", "CONFIG_EFI", "CONFIG_ACPI"],
&[],
&["CONFIG_ATA", "CONFIG_VIRTIO_BLK", "CONFIG_SCSI_VIRTIO", "CONFIG_VIRTIO_NET", "CONFIG_E1000", "CONFIG_VIRTIO_MENU", "CONFIG_CPU_SUP_AMD"],
),
(
// The azure lane's row (2026-09-05): Hyper-V synthetic SCSI +
// synthetic NIC, both over VMBus (HYPERVISOR_GUEST + HYPERV),
// and NO virtio/nvme/ata/e1000 — a Gen2 VM has none of those.
// Serial is real (ttyS0 = Azure Serial Console), UEFI only.
"azure-gen2",
&["CONFIG_SERIAL_8250", "CONFIG_SERIAL_8250_CONSOLE", "CONFIG_VT", "CONFIG_VGA_CONSOLE", "CONFIG_HYPERVISOR_GUEST", "CONFIG_HYPERV", "CONFIG_SCSI", "CONFIG_BLK_DEV_SD", "CONFIG_HYPERV_STORAGE", "CONFIG_NET", "CONFIG_HYPERV_NET", "CONFIG_EFI", "CONFIG_ACPI"],
&[],
&["CONFIG_ATA", "CONFIG_VIRTIO_BLK", "CONFIG_SCSI_VIRTIO", "CONFIG_NVME_CORE", "CONFIG_VIRTIO_NET", "CONFIG_E1000", "CONFIG_ENA_ETHERNET", "CONFIG_VIRTIO_MENU", "CONFIG_CPU_SUP_AMD"],
),
(
// The W2 lane's first row (2026-09-05): NVMe boot disk + gVNIC.
// The whole Kconfig MENU CHAIN above GVE is named, because
// `olddefconfig` does not refuse a symbol whose parent is
// missing — it drops it silently. MEASURED on the shipped
// tunnr-kernel-hyperv config: PCI_MSI=n there, and CONFIG_GVE
// is absent from the built .config ENTIRELY (not even
// `# … is not set`), because `depends on PCI_MSI` left it
// unoffered. NET_VENDOR_GOOGLE=y is in that same file.
"gce-gvnic",
&["CONFIG_SERIAL_8250", "CONFIG_SERIAL_8250_CONSOLE", "CONFIG_VT", "CONFIG_VGA_CONSOLE", "CONFIG_NVME_CORE", "CONFIG_BLK_DEV_NVME", "CONFIG_NET", "CONFIG_NETDEVICES", "CONFIG_ETHERNET", "CONFIG_NET_VENDOR_GOOGLE", "CONFIG_PCI_MSI", "CONFIG_GVE", "CONFIG_EFI", "CONFIG_ACPI"],
&[],
&["CONFIG_ATA", "CONFIG_VIRTIO_BLK", "CONFIG_SCSI_VIRTIO", "CONFIG_VIRTIO_NET", "CONFIG_E1000", "CONFIG_ENA_ETHERNET", "CONFIG_NET_VENDOR_AMAZON", "CONFIG_HYPERV", "CONFIG_XEN", "CONFIG_VIRTIO_MENU", "CONFIG_CPU_SUP_AMD"],
),
(
// The W2 lane's second row: the Xen PV frontends, and the guest
// menu chain they hang off — HYPERVISOR_GUEST → PARAVIRT → XEN,
// read off linux-6.12.104 (arch/x86/Kconfig:783/795,
// arch/x86/xen/Kconfig:6). `# CONFIG_XEN is not set` appears in
// the built tunnr-kernel-hyperv config, which proves XEN is
// OFFERED there (its deps are met) and merely off — exactly the
// symbol a requirement can turn on.
//
// NO ethernet-vendor rows: netfront is drivers/net/xen-netfront.c,
// sourced straight from drivers/net/Kconfig, not under ETHERNET.
"aws-xen-hvm",
&["CONFIG_SERIAL_8250", "CONFIG_SERIAL_8250_CONSOLE", "CONFIG_VT", "CONFIG_VGA_CONSOLE", "CONFIG_HYPERVISOR_GUEST", "CONFIG_PARAVIRT", "CONFIG_XEN", "CONFIG_XEN_BLKDEV_FRONTEND", "CONFIG_NET", "CONFIG_NETDEVICES", "CONFIG_XEN_NETDEV_FRONTEND", "CONFIG_EFI", "CONFIG_ACPI"],
&[],
&["CONFIG_ATA", "CONFIG_VIRTIO_BLK", "CONFIG_SCSI_VIRTIO", "CONFIG_NVME_CORE", "CONFIG_VIRTIO_NET", "CONFIG_E1000", "CONFIG_ENA_ETHERNET", "CONFIG_ETHERNET", "CONFIG_HYPERV", "CONFIG_GVE", "CONFIG_VIRTIO_MENU", "CONFIG_CPU_SUP_AMD"],
),
];
let mut covered = std::collections::BTreeSet::new();
for (name, yes, no, absent) in table {
covered.insert(*name);
let needs = preset(name).unwrap().target().unwrap().kconfig_needs();
let find = |s: &str| needs.iter().find(|(sym, _, _)| *sym == s).map(|(_, on, _)| *on);
for s in *yes {
assert_eq!(find(s), Some(true), "{name}: {s} must be y — rows: {needs:?}");
}
for s in *no {
assert_eq!(find(s), Some(false), "{name}: {s} must be n — rows: {needs:?}");
}
for s in *absent {
assert_eq!(find(s), None, "{name}: {s} must not be asked for — rows: {needs:?}");
}
for (sym, _, why) in &needs {
assert!(!why.is_empty(), "{name}: {sym} has no why");
}
assert!(
!needs.iter().any(|(s, _, _)| *s == "CONFIG_ISO9660_FS"),
"{name}: the medium is ISO-9660 whichever DEVICE it arrives on — that row belongs to the appliance-boot contract, never to a bus, or a machine that reads the ISO off virtio silently loses it"
);
}
// Every spellable preset has a row in this table; nothing is silently untested.
let spellable: std::collections::BTreeSet<_> = PRESETS.iter().filter(|p| p.target().is_ok()).map(|p| p.name).collect();
assert_eq!(spellable, covered, "a spellable preset without a Kconfig expectation");
}
/// **A preset whose closure the donor kernel vetoes is refused BY NAME.**
///
/// The same shape as the `CONFIG_ATA` / ide-cdrom refusal in
/// `requires.rs`, asserted here on the preset roster rather than on a
/// hand-spelled `target` group. The `nuked` overlay states its absences
/// (`# CONFIG_SCSI is not set`, `# CONFIG_ATA is not set`), and a
/// requirement is never merged over a veto.
///
/// Honest about what this can and cannot show (LAW 7): `nuked` vetoes
/// `SCSI` and `ATA` and nothing else that these presets need, so
/// **azure-gen2 is the only one of the three that this kernel can refuse**.
/// For `gce-gvnic` and `aws-xen-hvm` the assertion is the other half of the
/// same contract — that the closure NAMES the symbol at all, which is what
/// makes a future veto refusable. A test that claimed all three were
/// refused would be asserting a red that cannot fire.
#[test]
fn a_preset_closure_the_nuked_kernel_vetoes_is_refused_by_name() {
use crate::kernel::KernelBuild;
use crate::kernel_requires::{KconfigReq, KernelRequirements};
// The closure of a preset, as the requirement machinery would place it.
let reqs_of = |name: &str| {
let t = preset(name).unwrap().target().unwrap();
let mut r = KernelRequirements::default();
for (sym, on, why) in t.kconfig_needs() {
let req = if on { KconfigReq::y(sym, &why) } else { KconfigReq::n(sym, &why) };
r.require(&format!("gunnar/{name}"), "target", &req);
}
r
};
let nuked = || KernelBuild {
name: "tunnr-kernel".into(),
version: "6.12.104".into(),
tarball_sha256: "0".repeat(64),
preset: "nuked".into(),
..Default::default()
};
// RED: azure-gen2 rides Hyper-V synthetic SCSI, and `nuked` says
// `# CONFIG_SCSI is not set`. The refusal must NAME the symbol.
let err = format!("{:#}", nuked().apply_requirements(&reqs_of("azure-gen2")).unwrap_err());
assert!(err.contains("CONFIG_SCSI"), "the refusal must name the symbol: {err}");
assert!(err.contains("nuked"), "the refusal must name the preset that vetoes it: {err}");
// GREEN, same input, kernel that grants it: proof the red above is the
// VETO firing and not the closure being malformed.
let mut normal = KernelBuild { preset: "normal".into(), ..nuked() };
normal.apply_requirements(&reqs_of("azure-gen2")).unwrap();
assert!(normal.requirements.as_ref().unwrap().rows.contains_key("CONFIG_HYPERV_STORAGE"));
// The other two: `nuked` vetoes nothing they need, so they are ACCEPTED
// — stated out loud so nobody later reads this test as proving a
// refusal it never saw.
for (name, symbol) in [("gce-gvnic", "CONFIG_GVE"), ("aws-xen-hvm", "CONFIG_XEN_BLKDEV_FRONTEND")] {
let r = reqs_of(name);
assert!(r.rows.contains_key(symbol), "{name}: the closure must name {symbol}");
let mut k = nuked();
k.apply_requirements(&r).unwrap_or_else(|e| panic!("{name}: nuked vetoes none of its symbols, so this must pass: {e:#}"));
}
}
/// The historical shape renders no `-cpu` at all (qemu64), so nothing
/// built before presets changes its argv.
#[test]
fn qemu_local_has_no_cpu_flag() {
assert_eq!(QEMU_LOCAL.qemu_shape().unwrap().cpu, None);
assert_eq!(UPCLOUD.qemu_shape().unwrap().cpu, Some("EPYC-Rome"));
}
/// **The two verdicts must agree.** The declared `mimic` word and the axes
/// derived from the preset's own device words are two sources of truth, and
/// this is where they are made to face each other.
///
/// It found a real lie the day it was written: `aws-xen-hvm` declared
/// `NeedsRealCloud("xen blkfront/netfront need a Xen hypervisor, not
/// QEMU/KVM")` while QEMU 10.2.1 on oden lists `xen-disk`, `xen-net-device`,
/// `xen-cdrom`, `xen-console` and `xen-platform`. One row, unprovable for a
/// year, on a sentence nobody re-measured.
#[test]
fn the_declared_mimic_agrees_with_the_derived_axes() {
let mut wrong = Vec::new();
for p in PRESETS {
if !p.mimic_agrees_with_axes() {
wrong.push(format!(
"{}: declared {:?} but axes are {:?} (substitutions {:?}, absent {:?})",
p.name,
p.mimic,
p.axes().is_exact(),
p.axes().substitutions(),
p.axes().absent(),
));
}
}
assert!(wrong.is_empty(), "declared mimic disagrees with the device words:\n{}", wrong.join("\n"));
}
/// A preset QEMU models exactly must have NOTHING to substitute — that is
/// what "exactly" means, and it is the property a KVM green rests on.
#[test]
fn an_exactly_mimicable_preset_substitutes_nothing() {
for p in PRESETS {
if p.axes().is_exact() {
assert!(
p.axes().substitutions().is_empty(),
"{} is exact but substitutes {:?}",
p.name,
p.axes().substitutions()
);
assert!(p.axes().is_renderable(), "{} is exact but not renderable", p.name);
}
}
}
/// **Every substitution names a device QEMU really has.** A stand-in that is
/// itself not a QEMU device would make a row claim a boot it cannot launch.
/// The `with` words are checked against the ones the exact arms produce, so
/// the two halves of the table cannot drift apart.
#[test]
fn every_substitute_names_a_device_qemu_actually_models() {
let real: std::collections::BTreeSet<&str> = PRESETS
.iter()
.flat_map(|p| [p.boot_disk.qemu_mimic(), p.nic.qemu_mimic(), p.chipset.qemu_machine()])
.flatten()
.chain(["virtio-scsi-pci", "virtio-net-pci", "q35"])
.collect();
for p in PRESETS {
for (facet, missing, with) in p.axes().substitutions() {
assert!(
real.contains(with),
"{}: {facet} stands {missing} in as `{with}`, which is not a device this \
table ever renders",
p.name
);
assert_ne!(missing, with, "{}: {facet} substitutes a device for itself", p.name);
}
}
}
/// Bare metal is the one shape nothing stands in for, and it must say so on
/// more than one axis — a `Metal` preset whose only complaint is its medium
/// would read as "almost mimicable", which it is not.
#[test]
fn bare_metal_is_absent_not_substituted() {
let metal = preset("scaleway-elastic-metal").expect("the metal row");
let axes = metal.axes();
assert!(!axes.is_renderable(), "metal cannot be rendered at all");
assert!(
axes.absent().len() >= 3,
"firmware, chipset and the devices are all the vendor's: {:?}",
axes.absent()
);
assert!(axes.substitutions().is_empty(), "nothing stands in for metal");
}
/// **The roster, pinned BY NAME.** Counts move for the wrong reasons — a
/// row added, a row renamed — and `left: 7 / right: 6` says a number changed
/// and nothing about WHICH machine did. These three sets are the answer to
/// "what can this box actually prove", and the day one moves it should be
/// because someone measured something.
///
/// The shape as of 2026-09-05, and it is the whole argument of the proof
/// ladder: **exactly one** preset cannot be rendered under QEMU at all.
/// Every other cloud is reachable with its missing devices NAMED.
#[test]
fn the_mimicable_roster_is_pinned_by_name() {
let exact: Vec<&str> = PRESETS.iter().filter(|p| p.axes().is_exact()).map(|p| p.name).collect();
assert_eq!(
exact,
vec![
"qemu-local",
"qemu-local-bios",
"upcloud",
"gce-virtio",
"hetzner-cloud",
"ovh-public-cloud",
],
"the machines a KVM green means literally"
);
let substituted: Vec<&str> = PRESETS
.iter()
.filter(|p| p.axes().is_renderable() && !p.axes().is_exact())
.map(|p| p.name)
.collect();
assert_eq!(
substituted,
vec![
"scaleway-instance",
"aws-xen-hvm",
"aws-nitro",
"gce-gvnic",
"azure-gen1",
"azure-gen2",
],
"the machines a KVM run proves the SHAPE of, substitutions named"
);
let absent: Vec<&str> = PRESETS.iter().filter(|p| !p.axes().is_renderable()).map(|p| p.name).collect();
assert_eq!(absent, vec!["scaleway-elastic-metal"], "metal is the only true wall");
}
/// The two facts this rung rests on, each asserted alone so a regression
/// names itself rather than moving a count.
#[test]
fn nitros_boot_disk_and_azures_are_proven_devices_not_stand_ins() {
let nitro = preset("aws-nitro").unwrap().axes();
assert_eq!(
nitro.disk,
MimicAxis::Exact("nvme"),
"QEMU models NVMe; Nitro's boot disk drives the guest's real \
CONFIG_BLK_DEV_NVME path and must never be counted as a stand-in"
);
assert!(
nitro.substitutions().iter().any(|(f, m, _)| *f == "nic" && *m == "ena"),
"ENA is the one Nitro device that genuinely needs the real cloud"
);
// **The correction OF the correction, measured 2026-09-05.** A previous
// lane read `-device help`, saw `xen-disk` and `xen-net-device`, and
// made both axes `Exact`. They are not: this QEMU has no `xen`
// accelerator and no machine with `xen-version`, so no `xen-bus` is
// ever created and neither device can be instantiated
// (`No 'xen-bus' bus found for device 'xen-disk'`). A device in a
// listing is not a device on a bus.
let xen = preset("aws-xen-hvm").unwrap().axes();
assert_eq!(xen.disk, MimicAxis::Substitute { missing: "xen-blk", with: "virtio-scsi-pci" });
assert_eq!(xen.nic, MimicAxis::Substitute { missing: "xen-netfront", with: "virtio-net-pci" });
assert_eq!(
xen.substitutions().len(),
3,
"chipset, disk AND nic all stand in for aws-xen-hvm: {:?}",
xen.substitutions()
);
}
/// **Ask QEMU, do not trust the comment.**
///
/// Every device word this table calls `Exact` must appear in the local
/// `qemu-system-x86_64 -device help`, and every word it calls missing must
/// NOT. This is the guard that would have caught `aws-xen-hvm` on the day it
/// was written: its `NeedsRealCloud` sentence asserted a fact about QEMU
/// that a one-line command disproves, and for a year nobody ran the command.
///
/// `#[ignore]` because it needs a QEMU on PATH and this crate must build on
/// hosts without one — but it SKIPs loudly rather than passing when QEMU is
/// missing, so an absent binary can never look like a clean result.
///
/// ```bash
/// cargo test -p nornir-build-thing --lib target::presets -- --ignored --nocapture
/// ```
#[test]
#[ignore = "needs qemu-system-x86_64 on PATH: asks the real binary which devices it models"]
fn the_device_words_match_what_this_qemu_actually_models() {
let out = match std::process::Command::new("qemu-system-x86_64")
.arg("-device")
.arg("help")
.output()
{
Ok(o) => o,
Err(e) => {
eprintln!("SKIP: no qemu-system-x86_64 on PATH ({e})");
return;
}
};
let listing = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
listing.contains("name \""),
"SKIP-PROOF: `-device help` produced nothing that looks like a device \
listing, so a green here would mean nothing:\n{listing}"
);
let models = |dev: &str| listing.contains(&format!("name \"{dev}\""));
/// Every name a device we call MISSING might appear under in
/// `-device help`. Without this the probe can only confirm what we
/// already claim, which is the shape of a guard that cannot report red.
///
/// Measured on oden 2026-09-05 (QEMU 10.2.1): none of these exist, but
/// `vmbus-bridge` and `hv-balloon` DO — the VMBus itself is emulated,
/// so a guest can be proven to find and enumerate the bus even though
/// neither synthetic leaf driver has a model. That is why Azure is two
/// missing devices and not a missing platform.
fn qemu_names_to_watch_for(missing: &str) -> &'static [&'static str] {
match missing {
"ena" => &["ena", "ena-pci"],
"gvnic" => &["gve", "gvnic", "gve-pci"],
"hv-netvsc" => &["netvsc", "hv-netvsc", "vmbus-netvsc"],
"hv-scsi" => &["storvsc", "hv-storvsc", "vmbus-scsi", "hv-scsi"],
_ => &[],
}
}
// Machine types are not devices; the chipset axis is checked by
// `-machine help` elsewhere and is deliberately not asserted here.
for p in PRESETS {
if let MimicAxis::Exact(d) = p.axes().disk {
assert!(models(d), "{}: disk `{d}` is called Exact but this QEMU has no such device", p.name);
}
if let MimicAxis::Exact(n) = p.axes().nic {
assert!(models(n), "{}: nic `{n}` is called Exact but this QEMU has no such device", p.name);
}
for (facet, missing, with) in p.axes().substitutions() {
if facet == "chipset" {
continue;
}
assert!(models(with), "{}: stand-in `{with}` is not a device this QEMU has", p.name);
// The word `missing` is OUR name for the device, not QEMU's, so
// `!models(missing)` proves nothing on its own — that blind spot
// is exactly how the Xen row survived. Check every name this
// device could plausibly go by instead, so the day QEMU grows a
// model the substitution stops being free.
for candidate in qemu_names_to_watch_for(missing) {
assert!(
!models(candidate) || !attachable(candidate),
"{}: {facet} substitutes `{missing}` -> `{with}`, but THIS QEMU models \
`{candidate}` AND can attach it. The substitution is costing real \
coverage — measure it and make the axis Exact.",
p.name
);
}
}
}
}
/// **A device in `-device help` is not a device on a bus.**
///
/// This is the guard the listing-only probe above could never be, and the
/// reason it exists is that the listing-only probe RATIFIED A LIE. On
/// 2026-09-05 a lane grepped `-device help`, found `xen-disk` and
/// `xen-net-device`, concluded that a year-old
/// `NeedsRealCloud("xen blkfront/netfront need a Xen hypervisor")` sentence
/// was folklore, and made both axes `Exact`. The listing was real and the
/// conclusion was wrong: `-device help` enumerates every compiled-in model
/// whether or not this build can create the bus it hangs on.
///
/// MEASURED on oden, QEMU 10.2.1 (`-accel help` → `tcg mshv kvm`, no
/// `xen`; `-machine q35,xen-version=…` → property not found):
///
/// ```text
/// -device virtio-blk-pci,zzprobe=1 → Property 'virtio-blk-pci.zzprobe' not found ← attachable
/// -device xen-disk,zzprobe=1 → No 'xen-bus' bus found for device 'xen-disk' ← NOT
/// ```
///
/// So the probe asks QEMU to actually BUILD each device, with a property
/// name that cannot exist. A device that resolves its bus gets as far as
/// rejecting the property; one that does not, names the missing bus. The
/// bogus property is what makes this fast and side-effect-free — QEMU exits
/// on the error instead of starting a machine.
///
/// `#[ignore]` for the same reason as its sibling: it needs a QEMU on PATH,
/// and it SKIPs loudly rather than passing when there is none.
///
/// ```bash
/// cargo test -p nornir-build-thing --lib target::presets -- --ignored --nocapture
/// ```
#[test]
#[ignore = "needs qemu-system-x86_64 on PATH: asks the real binary to attach each device"]
fn the_device_words_are_attachable_on_this_qemu() {
if qemu_device_help().is_none() {
eprintln!("SKIP: no qemu-system-x86_64 on PATH");
return;
}
// Self-check FIRST: the probe must be able to say both words. If
// `virtio-blk-pci` (a device this QEMU certainly has) does not come back
// attachable, the probe is broken and every green below is meaningless
// — the exact failure mode that let the Xen row through.
assert!(
attachable("virtio-blk-pci"),
"PROBE-PROOF: virtio-blk-pci must be attachable on any QEMU; the probe cannot report green"
);
assert!(
!attachable("no-such-device-zz"),
"PROBE-PROOF: a device that does not exist must not read as attachable"
);
for p in PRESETS {
if let MimicAxis::Exact(d) = p.axes().disk {
assert!(
attachable(d),
"{}: disk `{d}` is called Exact but this QEMU cannot attach it — a model in \
`-device help` with no bus is not a device a guest can bind to",
p.name
);
}
if let MimicAxis::Exact(n) = p.axes().nic {
assert!(attachable(n), "{}: nic `{n}` is called Exact but this QEMU cannot attach it", p.name);
}
for (facet, _missing, with) in p.axes().substitutions() {
if facet == "chipset" {
continue;
}
assert!(attachable(with), "{}: stand-in `{with}` cannot be attached on this QEMU", p.name);
}
}
}
/// **A dual-attach medium is a PLATFORM fact; `target.buses` is a KERNEL
/// fact. Deriving the second from the first is what this pins against.**
///
/// RED until 2026-09-07: [`TargetPreset::target`] inserted `Bus::IdeCdrom`
/// for every `MediumBring::IsoCdrom` row, so naming such a machine put
/// `CONFIG_ATA`, `ATA_PIIX` and `BLK_DEV_SR` on the donor kernel's edge —
/// and the shared donor `tunnr/tunnr-kernel` is the `nuked` preset, whose
/// fragment says `# CONFIG_ATA is not set`. The build did not get a bigger
/// kernel; it got *"a consumer requires what the preset vetoes"* and
/// stopped. Four gunnar installer rows carried `preset: None` for a month
/// because of it.
///
/// **The live appliance is the disproof.** `tunnr-kernel`'s shipped
/// `kernel.config` says `# CONFIG_ATA is not set`, and that kernel booted
/// off this very ISO on UpCloud (measured 2026-09-07, gunnar.rs). It can,
/// because the medium rides TWICE: as an IDE cdrom, which only SeaBIOS ever
/// reads — firmware reading an El-Torito cdrom needs no kernel driver at
/// all — and as a second copy of the same ISO on virtio, which is where
/// Linux and `korp-installer` find the ISO-9660 `CD001` magic
/// (`gunnar/deploy/upcloud/src/machine.rs`: the installer probes ONLY
/// virtio, and `INSTALLER: volume: no block device carries the ISO-9660
/// magic (CD001) … (probed: loop1 … vda)` is what a cdrom-only attach
/// prints before it dies).
///
/// **That was the state until 2026-09-21, and what still holds of it is
/// the requirement, not the kernel.** The probe never looked only at virtio;
/// what was missing was the kernel. Since tunnr `3f7c2ff` the appliance
/// donors carry `ATA_PIIX`, `SATA_AHCI` and `BLK_DEV_SR` in their OWN
/// fragment. That is a donor choice merged after the preset, not a consumer
/// row, so the `nuked` veto is not contradicted. korp-installer ≥ 0.1.14 asks
/// `sr*` first, and a cdrom-only medium installs under `-machine pc` (IDE)
/// and q35 (AHCI) on mock-upcloud-kvm. The virtio copy remains a fallback
/// for older media. What this test pins is unchanged and still true: no
/// PRESET derives `ide-cdrom`, so no consumer places ATA against `nuked`.
#[test]
fn an_iso_attach_medium_asks_the_kernel_for_no_ata() {
// The bus the kernel must drive to read the medium — `None` for every
// way a medium reaches a platform today, and each arm says why.
assert_eq!(MediumBring::IsoCdrom.kernel_medium_bus(), None);
assert_eq!(MediumBring::ImportQcow2.kernel_medium_bus(), None);
assert_eq!(MediumBring::Metal("dd").kernel_medium_bus(), None);
// UpCloud: the machine that cost the two lanes. virtio-blk alone.
let t = UPCLOUD.target().unwrap();
assert_eq!(t.buses, vec![Bus::VirtioBlk], "the cdrom is the firmware's, not the kernel's");
for (sym, on, _) in t.kconfig_needs() {
assert!(
!matches!(sym, "CONFIG_ATA" | "CONFIG_ATA_PIIX" | "CONFIG_BLK_DEV_SR"),
"{sym} = {on} would meet the `nuked` donor's veto"
);
}
// And it is not one row's exemption: NO preset derives an ide-cdrom
// from the way its platform hands a medium over.
for p in PRESETS {
if let Ok(t) = p.target() {
assert!(
!t.has_bus(Bus::IdeCdrom),
"{}: `{}` is how the PLATFORM brings the medium, not a driver the kernel carries",
p.name,
p.medium.describe()
);
}
}
// The platform fact itself is UNCHANGED and still asked by name — the
// deploy door attaches two storages off exactly this word.
assert!(UPCLOUD.medium.is_iso_attach() && UPCLOUD.qemu_shape().unwrap().medium_as_cdrom);
}
/// `-device help`, or `None` when there is no QEMU to ask.
#[cfg(test)]
fn qemu_device_help() -> Option<String> {
let out = std::process::Command::new("qemu-system-x86_64").arg("-device").arg("help").output().ok()?;
let listing = format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
listing.contains("name \"").then_some(listing)
}
/// **Can this QEMU actually create the device?** Asks it to, with a
/// property that cannot exist, and reads which error comes back:
/// a `Property '<dev>.zzprobe' not found` means the device resolved its bus
/// and got as far as its properties; anything else (`No '<x>-bus' bus
/// found`, `requires a Xen guest`, an unknown device) means it did not.
#[cfg(test)]
fn attachable(device: &str) -> bool {
let Ok(out) = std::process::Command::new("qemu-system-x86_64")
.args(["-machine", "q35", "-display", "none", "-S", "-monitor", "none", "-serial", "none"])
.arg("-device")
.arg(format!("{device},zzprobe=1"))
.output()
else {
return false;
};
let said = format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
said.contains(&format!("Property '{device}.zzprobe' not found"))
}
}