//! Boot a real Firecracker microVM through the reference [`FirecrackerVmm`] //! driver, meter the boot's CPU energy on real hardware (RAPL via MSR), and //! seal the reading as a Sandbox JCR-1 receipt — the S5 executor's full path, //! end to end, on a Linux KVM host. //! //! ```bash //! # on a Linux KVM host (e.g. AWS *.metal), as root for MSR access: //! sudo FC_BIN=/usr/local/bin/firecracker \ //! KERNEL=/tmp/vmlinux.bin ROOTFS=/tmp/rootfs.ext4 \ //! ./boot_microvm //! ``` //! //! It spawns `firecracker --api-sock `, drives `PUT /boot-source`, //! `/drives/rootfs`, `/machine-config`, and the `InstanceStart` action through //! our unix-socket HTTP/1.1 client, lets the guest run briefly, and reads the //! package energy counters (every package's, and every die's) before and //! after. A genuine `HwShunt` figure where the MSRs read and every package's //! counter proved live; an honest `ModelBased` vCPU-seconds estimate //! otherwise. use sandbox_exec_microvm::firecracker::{FirecrackerConfig, FirecrackerVmm, VmState, Vmm}; use sandbox_exec_microvm::power::PowerMeter; use sandbox_meter::{DeviceEnergy, Provenance, Receipt}; use sandbox_schema::{ContentId, DeviceClass, Tier}; use std::path::Path; use std::process::Command; use std::time::{Duration, Instant}; fn env(key: &str, default: &str) -> String { std::env::var(key).unwrap_or_else(|_| default.into()) } fn main() { let fc_bin = env("FC_BIN", "/usr/local/bin/firecracker"); let kernel = env("KERNEL", "/tmp/vmlinux.bin"); let rootfs = env("ROOTFS", "/tmp/rootfs.ext4"); let run_secs: f64 = env("RUN_SECS", "4").parse().unwrap_or(4.0); let sock = "/tmp/fc-demo.sock"; let _ = std::fs::remove_file(sock); // Spawn the Firecracker process listening on the API socket. let mut child = Command::new(&fc_bin) .args(["--api-sock", sock]) .spawn() .unwrap_or_else(|e| panic!("spawn {fc_bin}: {e}")); for _ in 0..100 { if Path::new(sock).exists() { break; } std::thread::sleep(Duration::from_millis(50)); } // The meter: real RAPL via MSR where available (root on Nitro metal), // every package's and every die's, once each has proved live; else None // → an honest vCPU-seconds ModelBased estimate. #[cfg(target_os = "linux")] let meter: Option = match sandbox_exec_microvm::power::MsrRaplMeter::host() { Ok(m) => Some(m), Err(why) => { println!("no MSR meter: {why}"); None } }; #[cfg(not(target_os = "linux"))] let meter: Option = None; let before = meter.as_ref().and_then(|m| m.sample_uj()); println!( "meter: {}", if before.is_some() { "RAPL via MSR (HwShunt)" } else { "none — ModelBased fallback" } ); let cfg = FirecrackerConfig { api_socket: sock.into(), kernel_image_path: kernel, rootfs_path: rootfs, boot_args: "console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw".into(), vcpus: 2, mem_mib: 256, }; let t0 = Instant::now(); let outcome = FirecrackerVmm.run(&cfg); match &outcome { Ok(o) => println!("boot: {:?} ({} vcpu-s attributed)", o.state, o.vcpu_seconds), Err(e) => println!("boot error: {e}"), } // Let the guest run so the metered window covers real execution. std::thread::sleep(Duration::from_secs_f64(run_secs)); let after = meter.as_ref().and_then(|m| m.sample_uj()); let wall = t0.elapsed(); let _ = child.kill(); let _ = child.wait(); let _ = std::fs::remove_file(sock); let booted = matches!(outcome.map(|o| o.state), Ok(VmState::Booted | VmState::Completed)); println!("guest ran {:.2}s; booted={booted}", wall.as_secs_f64()); // Build the energy record: HwShunt from the MSR delta, else ModelBased. let device = match (before, after) { (Some(a), Some(b)) => { let uj = b.saturating_sub(a); println!("CPU energy (RAPL, every package, MSR): {:.3} J over the window", uj as f64 / 1e6); DeviceEnergy { class: DeviceClass::Cpu, measured_uj: Some(uj), estimated_uj: uj, provenance: Provenance::HwShunt, } } _ => { // No hardware: vCPU-seconds × a coarse per-vCPU-second figure. let est = (cfg.vcpus as u64).saturating_mul(wall.as_secs().max(1)).saturating_mul(15_000_000); println!("CPU energy (estimate): {:.3} J", est as f64 / 1e6); DeviceEnergy { class: DeviceClass::Cpu, measured_uj: None, estimated_uj: est, provenance: Provenance::ModelBased, } } }; // Seal it as a Sandbox S5 receipt. let receipt = Receipt::build( ContentId::of_bytes(b"firecracker microvm boot: vmlinux + bionic rootfs"), Tier::S5, vec![device], 1, [9u8; 8], ) .expect("receipt builds"); let sk = ed25519_dalek::SigningKey::from_bytes(&[5u8; 32]); let envelope = receipt.seal("did:sandbox:metal-demo", &sk).expect("seal"); receipt .verify_sealed(&envelope, &sk.verifying_key(), Some("did:sandbox:metal-demo")) .expect("verify"); std::fs::write("/tmp/microvm-receipt.cose", &envelope).ok(); println!( "sealed S5 receipt {} ({} bytes) — verified ✓; written to /tmp/microvm-receipt.cose", receipt.content_id().0, envelope.len() ); }