libvirt-renderer — Architecture
Architecture
Section titled “Architecture”Data flow
Section titled “Data flow” ┌─────────────────────────────────────────────────────────────────┐ │ POST /api/v1/vms/from-profile │ │ body: { name, uuid, profile_id, security_overlay, │ │ networking_overlay, vcpu_count, memory_mb, │ │ disks, networks, install_iso, auto_start } │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ │ vm_profiles::resolve(...) │ │ → ResolvedProfile │ │ (base + security overlay │ │ + networking overlay) │ └──────────────┬───────────────┘ │ ▼ ┌──────────────────────────────┐ │ vm_profiles::validate(...) │ │ → host-cap check │ │ (nested virt, OVMF, swtpm, │ │ machine type, RAM, vcpu) │ └──────────────┬───────────────┘ │ ▼ ┌──────────────────────────────┐ │ libvirt_renderer:: │ │ render_domain_xml(p, ctx) │ │ │ │ writer::XmlWriter (indent) │ │ elements::domain::render │ │ ├── metadata │ │ ├── memory │ │ ├── cpu │ │ ├── iothreads │ │ ├── os │ │ ├── features │ │ ├── clock │ │ ├── on_poweroff/reboot/... │ │ ├── pm │ │ ├── devices │ │ │ ├── controller │ │ │ ├── disk │ │ │ ├── network │ │ │ ├── input │ │ │ ├── graphics + video │ │ │ ├── sound │ │ │ ├── tpm │ │ │ ├── rng │ │ │ ├── channel │ │ │ ├── memballoon │ │ │ └── serial/console │ │ └── seclabel │ │ → String (UTF-8 XML) │ └──────────────┬───────────────┘ │ ▼ ┌──────────────────────────────┐ │ synvirt_libvirt::virsh:: │ │ define_with_stdin(xml) │ │ → libvirt RNG validation │ │ → domain registered │ └──────────────┬───────────────┘ │ ▼ ┌──────────────────────────────┐ │ optional: virsh start <name> │ │ if auto_start │ └──────────────────────────────┘Element ordering
Section titled “Element ordering”The libvirt RNG schema in domain.rng puts the children of <domain>
inside an <interleave> block, but in practice libvirt + virt-manager
emit them in a stable order. The renderer matches that order so a
virsh dumpxml round-trip diffs cleanly against tools the operator
might already be using:
- Identifying:
name,uuid,title,description,metadata - Memory:
memory,currentMemory,memoryBacking - Compute:
vcpu,cpu,iothreads - OS:
os(withloader+nvramfor UEFI) - Features:
acpi,apic,hyperv,smm,vmport - Clock:
clockwithtimerchildren - Lifecycle:
on_poweroff,on_reboot,on_crash - Power:
pm - Devices:
devicesblock (see below) - Security:
seclabel
Inside <devices> the order is:
emulator- controllers (
pcie-root+ ports → USB → virtio-scsi → virtio-serial) - disks (regular → cdrom → floppy)
- network interfaces
- input devices (tablet, keyboard)
- graphics (SPICE → VNC)
- video
- sound + audio
- TPM
- RNG
- channels (qemu-guest-agent → SPICE agent)
- memballoon
- serial + console
Trait surfaces
Section titled “Trait surfaces”The renderer is a flat function pipeline; there is no dynamic dispatch.
Each elements/* module exposes one render(...) function with the
same signature shape:
pub fn render(w: &mut XmlWriter, profile: &ResolvedProfile, …) -> Result<()>;The XmlWriter is the only piece of state the modules share. It
maintains depth, escapes attribute and text content, and produces a
deterministic byte sequence so insta snapshots stay stable.
Profile-driven decisions
Section titled “Profile-driven decisions”| Profile field | Renderer effect |
|---|---|
chipset = Q35 |
emit pcie-root + pcie-root-port controllers |
chipset = I440fx |
emit pci-root index 0 |
firmware = Uefi { secure_boot=true } |
OVMF.secboot loader + <smm state='on'/> |
firmware = Uefi { secure_boot=false } |
OVMF_CODE_4M.fd loader |
firmware = Bios |
omit <loader> and <nvram> |
vtpm = Some |
emit <tpm><backend type='emulator'/></tpm> |
hyperv = Some(flags) |
emit only the flags set on flags |
clock.utc = false |
offset=‘localtime’ (pre-Vista Windows) |
acpi = false |
omit <acpi/> and <apic/> (Win 95) |
floppy = true |
emit <disk device='floppy'> |
usb.controller = None |
<controller model='none' type='usb'/> |
input.ps2_only = true |
PS/2 keyboard + mouse, no USB tablet |
network.queues = MinVcpusCapped(N) |
<driver name='vhost' queues='min(vcpu,N)'/> |
disk.iothread = PerDisk |
<iothreads>N</iothreads> + per-disk reference |
disk.iothread = Pool(N) |
<iothreads>N</iothreads> |
video.spice = true |
<graphics type='spice'> + SPICE channel |
video.vnc = true |
additionally <graphics type='vnc'> |
audio = None |
omit <sound> (NT 4.0) |
Determinism
Section titled “Determinism”Snapshots only stay stable if the renderer is order-deterministic. The writer enforces this:
- Attributes are passed in alphabetical order at every callsite.
- The orchestrator visits children in a fixed order.
- The
<interface>block emits exactly: mac → source → model → driver → vlan → mtu (only whenmtu_default != 1500). - Optional sections that depend on profile flags expand or contract, but never reshuffle.
If snapshot diffs include attribute reorderings on bumps, the change
violates the contract and should not be accepted. cargo insta review is the human gate.
Error mapping
Section titled “Error mapping”| Error | HTTP | Notes |
|---|---|---|
E_RENDER_INVALID_PROFILE |
404 | unknown profile_id |
E_RENDER_MISSING_OVMF |
503 | host needs ovmf installed |
E_RENDER_MISSING_OVMF_VARS |
503 | host needs ovmf VARS template |
E_RENDER_SWTPM_MISSING |
503 | host needs swtpm installed |
E_RENDER_INVALID_DISK |
400 | bad source (file path missing, …) |
E_RENDER_INVALID_NIC |
400 | bad NIC source |
E_RENDER_LIBVIRT_REJECTED |
400 | libvirt RNG validation failed |
E_RENDER_WRITER |
500 | writer panic (should not happen) |
Where this lives in the daemon
Section titled “Where this lives in the daemon”crates/daemon/src/main.rs mod libvirt_renderer; ← module declaration └── crates/daemon/src/libvirt_renderer/ ├── mod.rs ← public functions ├── api.rs ← axum routes (merged in web/routes.rs) ├── error.rs ← E_RENDER_* enum ├── types.rs ← VmContext, DiskAttachment, … ├── writer.rs ← XmlWriter └── elements/ ← per-section modulesSee also
Section titled “See also”ELEMENTS.md— element-by-element profile-field map.EXAMPLES.md— full XML for representative profiles.API.md— REST surface details.