Virtual Oxide Labs: Emuleren van een volledig Oxide-rack
Het probleem met het testen en ontwikkelen voor hardware op rack-schaal is dat dergelijke schaal niet gemakkelijk verkrijgbaar is. Om niet eens te spreken over het feit dat DDR5 momenteel bijna als valuta wordt gebruikt; zelfs als alle onderdelen in overvloed beschikbaar zouden zijn, gaan voltooide racks razendsnel naar het front (met andere woorden: de datacenters van onze klanten).
Als lid van het Product Assurance Team bij Oxide vind ik dat onrechtvaardig, maar tegelijkertijd volledig begrijpelijk. Daarom moeten we creatief worden. We moeten in staat zijn om:
- Diverse netwerktopologieën van klanten te repliceren.
- Ons daadwerkelijke control plane te draaien.
- Up-to-date versies van onze software te testen.
- Hardwarecomponenten te representeren (we maken immers racks), inclusief onze switches en microcontrollers.
En het belangrijkste: we moeten dit kunnen doen zonder speciale "testversies" van releases te maken. We willen onze CI (Continuous Integration) strak houden, en bovendien zou het niet eerlijk zijn om met aangepaste versies te testen.
Dat betekent niet dat we geen trucjes hebben. Toen ik vorig jaar bij Oxide begon, werd ik als een van de eerste zaken gewezen op onze racklettes: racks die kleiner zijn dan gemiddeld, bestaande uit vier sleds en twee switches. Deze racklettes kunnen worden geïnitialiseerd en gebruikt als een echt rack, simpelweg omdat ze dat ook zijn. Ze hebben alleen een form factor die economisch niet interessant is om te verkopen, maar voor testen werken ze uitstekend. We hebben er momenteel vier, binnenkort acht, waarmee we elke combinatie van hardware- en softwarevereisten kunnen testen.
Daarnaast hebben we diverse gesimuleerde methoden om Omicron te draaien. De meeste delen van ons control plane kunnen onafhankelijk, in tandem en in verschillende gradaties van simulatie worden uitgevoerd.
Wat is een Voxel?
Herinner je je het spel Qbert? Het was een van de eerste populaire isometrische spellen. Elke kubus in dat beeld representeert een 'voxel', ofwel een punt in een 3D-ruimte. Het is in feite de 3D-versie van een pixel: Volume Element + Pixel = Voxel*.
Voor Oxide betekent Virtual OXidE Labs (of Voxel) dat er een enorme hoeveelheid getrouwheid (fidelity) is verpakt in één enkel stuk hardware. Het is de spirituele opvolger van onze eerdere virtuele labs (bekend als a4x2), waarmee we redelijke racks konden bouwen die in VM's draaiden.
Het woord "getrouwheid" is hierbij cruciaal. Zoals Bryan Cantrill stelt, is dit de Prime Directive: labs moeten standaard Oxide-software gebruiken, zonder hacks of modificaties.
Dit is wat Voxel ons biedt: een volledige software-emulatiestack, gebouwd op illumos in Rust, waarmee we diverse realistische configuraties kunnen bouwen en testen:
- Sled-virtualisatie: verzorgd door Propolis.
- Netwerken: BGP (genummerd en ongenummerd) of statisch netwerken met Debian en FRR.
- Switches: P4-powered switches via Oxide’s eigen SoftNPU (een softwarematige P4-switch).
- Microcontrollers: simulatie en emulatie voor onze Service Processor (SP) en Root of Trust (RoT).
Bovendien vereist dit alles een relatief kleine footprint. Thuis draai ik Voxel-labs op een Minisforum MS-A2 mini-PC met een 32-thread AMD Ryzen 9 9955HX en 96GB RAM. Op die computer kan ik met gemak 6 sleds opstarten, ofwel als één rack of in een multi-rack configuratie (twee aparte racks, één management plane). Alles wat nodig is, is Oxide Helios en een behoorlijk snelle NVMe-schijf. Binnen ongeveer 10 minuten heb je een volledig Oxide-rack waarbij elke "sled" in zijn eigen Propolis-VM draait, samen met switches en andere infrastructuur.
Image-creatie
Omdat we standaard Oxide-software moeten gebruiken, wordt het proces eenvoudiger. De eerste stap is het maken van een herbruikbaar snapshot.
Oxide-sleds zijn altijd één van twee dingen: een normale sled (een "gimlet" voor de eerste generatie, "cosmo" voor de huidige) of een "scrimlet", wat een sled is die is aangesloten op de sidecar-switches en een switch-zone draait.
Om een snapshot voor Voxel te maken, creëren we een virtuele machine (sled), installeren we het control plane (Omicron), maken we een snapshot voordat de configuratie plaatsvindt, en voilà: een klaar-voor-gebruik sled. Het control plane kan op twee manieren worden geïnstalleerd:
- Vanuit source: Voxel kloneert de Omicron-repo op GitHub (of gebruikt een lokale bron voor snelle ontwikkeling) en gebruikt
cargoom te compileren. - Vanuit TUF-repo: We gebruiken Tufaceous om Omicron-releases te verpakken met The Update Framework (TUF) en ons CI-tool Buildomat.
Bij de tweede optie trekt voxel image create de belangrijkste onderdelen van de sled-configuratie naar binnen:
$ pfexec ./target/debug/voxel image create --from-tuf ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip 5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c
[voxel] TUF source ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip: system version 23.0.0-0.ci+git5d176e0be7e, omicron 5d176e0be7e
[voxel] extracted 12 zones from ../5d176e0be7e5cc0d3288c1bef10005ec31f2cd7c.zip
[voxel] extracted 3 measurement corpus artifacts
[voxel] extracting host artifacts -> /export/home/helios/voxel-rawfix/target/debug/../../voxel-image/.tuf-host
[voxel] host boot image 1258295296 bytes, phase 1 rom 33554432 bytes
[voxel] extracted SP/RoT firmware -> /export/home/helios/voxel-rawfix/target/debug/../../voxel-image/.tuf-fw/ca9184058bd7
Na het uitpakken wordt er een VM gestart waarin alles wordt geschreven. Ten slotte:
[voxel] ready: voxel-cp version=unknown unpacked_artifacts=18 built=2026-09-06T21:37:58
[voxel] clearing device-instance map, halting
[voxel] flushing, up to 25s
[voxel] stopping hypervisor
[voxel] writing voxel image voxel-cp-5d176e0be7e-tuf
Dit image is een ZFS-snapshot die herhaaldelijk kan worden gebruikt om ons rack te creëren.
Klaar voor lancering
Het lanceren van het rack volgt een vergelijkbaar proces, maar nu is het control plane al geïnstalleerd. We hebben alleen het volgende nodig:
- Een "frr" image (de Debian VM's voor netwerken).
- Een control plane image.
- De
voxel-initagent, die elke virtuele sled zijn opdracht geeft.
Het log-output is uitgebreid, maar in essentie gebeurt het volgende:
$ pfexec ./target/debug/voxel launch --emu
[voxel] sled-agent config schema (image properties): data_links=Tagged disks=Hardcoded
Writing key pair to "./cargo-bay/sprockets/test-root-a.key.pem"
...
[sp-emu] instance state dir: /export/home/helios/voxel-unzip/sp-fleet/r0/sp-emu/state/33300
[identity] SP UID 13120858064df80da1642f2b, RoT UUID 00a94a8e206f3f138a790fba1b12db36
[flash] Hubris archive ... -> /export/home/helios/voxel-unzip/sp-fleet/r0/sp-emu/state/33300/archives/sp.zip
...
[voxel] rack 0 SP fleet up on fdb0:a840:25ff:0::1 (4 SP(s): [33300, 33310, 33320, 33330])
Sep 06 22:15:47.126 INFO launching runner: deployment(voxel) persistent(true) ...
...
Sep 06 22:28:06.231 INFO rack-init: complete, rack initialized (RSS run b20a2f8d-e511-4a67-a666-7b74b2ce68d7)
Sep 06 22:28:06.231 INFO launch complete
Samenvatting van het proces:
- Trust-materiaal wordt gegenereerd in de vorm van sprockets PKI.
- Geëmulerde microcontrollers (STM32H753 en LPC55) worden geflasht met Hubris-firmware.
- Netwerktopologie: Onze geautomatiseerde suite (Falcon) verbindt de topologie met simnet en VNIC-paren voor elke link en bereidt de sleds voor.
- Propolis VM's: In dit voorbeeld worden er 6 gelanceerd: 3 voor het netwerk en 3 als Oxide-sleds.
- voxel-init: Een lichtgewicht Rust-agent op elke node voert de volgende taken uit:
- Instellen van NAT en uplink-adressering.
- Configureren van de underlay op elke sled.
- Schrijven van de M.2-partitielayout op geëmulerde boot-schijven en labelen van vijf U.2-dataschijven.
- Seeden van het host phase 2 image en de install dataset naar de M.2's.
- Toewijzen van de scrimlets en draaien van een switch enforcer zodat ze hun identiteit onthouden na herstarts.
- Activeren van de
sled-agentvoor de configuratielaag van het control plane. - Verbinden met de commission API voor
wicketd, wat het rack door zijn initialisatiefasen leidt.
Dit is een complex samenspel van stappen. Het zijn exact de stappen die voor elk Oxide-rack gelden, behalve dat de hardware hier is gerepresenteerd door code. Wil je bijvoorbeeld een nieuwe interconnect tussen twee virtuele racks? Dan voeg je simpelweg een paar regels code toe in rack.rs.
Er zijn enkele zaken die (nog) niet voldoen aan de Prime Directive. Zo worden de certificaten voor de sprockets PKI niet tijdens de productie ingesteld via de RoT, zoals bij onze productieracks. Voorlopig is dit acceptabel, omdat het de runtime van het rack niet beïnvloedt.
Emuleren van Service Processors
Een groot deel van wat onze racks uniek maakt, is de embedded RoT (Root of Trust) en SP (Service Processor) in elke sled. Voorheen hadden we een SpSim-construct dat diende als een stub voor de service processor en de juiste informatie teruggaf. Maar dat is simulatie, geen emulatie. Dat volgt altijd het "happy path", wat onvoldoende getrouwheid biedt voor echte testgevallen.
Daarom hebben we daadwerkelijk geëmulerde Cortex-M7 en Cortex-M33 apparaten gebouwd voor illumos en Linux (x86 en ARM). De repository voor deze tool (sp-emu) is open source. Elk van de twee apparaten draait in een enkele thread en emuleert (grotendeels) getrouw de drie hoofdeisen: instructies, tijd en interrupts.
Je kunt onze Hubris-firmware direct flashen naar deze geëmulerde apparaten en communiceren via een normale socket. De Voxel-lab communiceert met deze virtuele apparaten via de Management Gateway Service (MGS) en het Inter-Processor Communication Channel (IPCC).
De grootste uitdaging voor sp-emu is de tijd. De code is geschreven voor silicon. Hubris plant taken op basis van een 1ms SysTick, maar de emulator gebruikt geen wall time. We moesten daarom een klok uitvinden waarbij tijd gelijkstaat aan instructies.
Voorbeeld van de maybe_tick functie:
if self.systick == 0 {
self.systick = self.syst_rvr;
}
self.systick -= 1;
if self.systick == 0 {
self.systick = self.syst_rvr;
self.tick_events = self.tick_events.wrapping_add(1);
if self.syst_csr & 2 != 0
&& self.mode == Mode::Thread
&& !self.primask
{
self.exception_entry(15, bus); // SysTick exception
}
}
In dit geval tellen we instructies af tegen de reload-waarde van de kernel en triggeren we exceptions waar dat veilig is (in thread-modus, interrupts niet gemaskeerd). Toegang tot randapparatuur kost één tik van onze klok (één instructie), terwijl dit op silicon tientallen cycli op de bus zou kunnen kosten.
De functie exception_entry modelleert alles wat de hardware doet op een Cortex-M core wanneer er een exception optreedt. Hubris heeft een non-preemptible kernel, maar plant taken wel preemptief. SVCall, PendSV, SysTick en alle IRQ's draaien op hetzelfde prioriteitsniveau, terwijl faults een hogere prioriteit behouden.
De algemene hartslag van sp-emu kan als volgt worden weergegeven in een pseudo-loop:
loop {
cpu.step(&mut bus, host.as_mut())?; // één Thumb-2 instructie
cpu.maybe_tick(&mut bus); // SysTick: tijd is instructies
cpu.maybe_interrupt(&mut bus); // NVIC en PendSV levering
if cpu.cycles % 4096 == 0 {
bus.pump_eth(host.as_mut()); // MGS pakketten in en uit
bus.pump_uart(host.as_mut());
}
}
Gebruikerservaring en connectiviteit
Wanneer je zoveel componenten op één systeem draait, kan het verwarrend zijn om te bepalen waar welke onderdelen zich bevinden. Daarom dient het voxel-commando niet alleen om racks te lanceren en te vernietigen, maar ook als connectiviteitstool.
Bijvoorbeeld, om direct in te loggen op een sled:
$ pfexec ./target/debug/voxel host login g0
[voxel] ssh root@192.168.68.167 (g0 global zone)
Of om verbinding te maken met een switch-zone via de technician ports (tp):
$ pfexec ./target/debug/voxel tp login switch0
[voxel] ssh root@192.168.68.167 -> zlogin oxz_switch (g0 switch0)
Er zijn daarnaast talrijke commando's voor het beheren van images (export/import, verwijderen) en configuraties (netwerktopologie, sled-grootte, multi-rack, etc.).
Voxel is beschikbaar onder de MPL 2.0-licentie. Om Voxel te draaien, moet je Helios als besturingssysteem gebruiken. Aanbevolen hardware zijn 16+ threads, 64GB+ RAM en een hoogwaardige NVMe-schijf met onboard DRAM.
Groetjes,