|
| 1 | +//! Boot a Unikraft guest image and run a Python snippet through the |
| 2 | +//! `hyperlight-unikraft` backend of `hyperlight-sandbox`. |
| 3 | +//! |
| 4 | +//! This is the end-to-end smoke test. It needs a real Unikraft kernel + initrd whose entry |
| 5 | +//! interpreter accepts `-c <code>` (the upstream `examples/python` image is the reference) |
| 6 | +//! and a working hypervisor (`/dev/kvm` on Linux). |
| 7 | +//! |
| 8 | +//! The backend runs each call as `python3 -c <code>` (argv) — exactly like the |
| 9 | +//! `hyperlight-unikraft --exec` CLI — and captures the guest console as `stdout`. |
| 10 | +//! |
| 11 | +//! Usage: |
| 12 | +//! ```text |
| 13 | +//! cargo run --example run -- <kernel> <initrd.cpio> ["<code>"] |
| 14 | +//! ``` |
| 15 | +//! |
| 16 | +//! ## Using the upstream python image (console-enabled) |
| 17 | +//! ```text |
| 18 | +//! cd hyperlight-unikraft/examples/python |
| 19 | +//! just build && just rootfs # kernel + initrd (or pull the prebuilt kernel) |
| 20 | +//! cd ../../sandbox |
| 21 | +//! cargo run --example run -- \ |
| 22 | +//! ../examples/python/.unikraft/build/python-hyperlight_hyperlight-x86_64 \ |
| 23 | +//! ../examples/python/initrd.cpio \ |
| 24 | +//! "print('hello from a Unikraft micro-VM'); print(6 * 7)" |
| 25 | +//! ``` |
| 26 | +//! The snippet's stdout is printed to your terminal. The example runs the same code twice |
| 27 | +//! so you can see the cold evolve vs the warm restore in the two `[timing]` lines. |
| 28 | +//! |
| 29 | +//! ## Optional: a host filesystem mount (hostfs images only, e.g. `hostfs-posix-py`) |
| 30 | +//! ```text |
| 31 | +//! HL_UNIKRAFT_MOUNT=/tmp/work:/host cargo run --example run -- <kernel> <initrd> \ |
| 32 | +//! "open('/host/out.txt','w').write('hi')" |
| 33 | +//! ``` |
| 34 | +
|
| 35 | +use anyhow::{anyhow, Result}; |
| 36 | +use hyperlight_sandbox::{SandboxBuilder, ToolRegistry}; |
| 37 | +use hyperlight_unikraft_sandbox::Unikraft; |
| 38 | + |
| 39 | +fn main() -> Result<()> { |
| 40 | + let mut args = std::env::args().skip(1); |
| 41 | + let kernel = args |
| 42 | + .next() |
| 43 | + .ok_or_else(|| anyhow!("usage: run <kernel> <initrd> [code]"))?; |
| 44 | + let initrd = args |
| 45 | + .next() |
| 46 | + .ok_or_else(|| anyhow!("usage: run <kernel> <initrd> [code]"))?; |
| 47 | + let code = args |
| 48 | + .next() |
| 49 | + .unwrap_or_else(|| "print('hello from a Unikraft micro-VM')".to_string()); |
| 50 | + |
| 51 | + // The default 512 MiB guest heap suits the upstream `examples/python` image; override |
| 52 | + // via `HL_UNIKRAFT_HEAP_MIB` for heavier runtimes (numpy/pandas stacks want more). |
| 53 | + let mut guest = Unikraft::new(kernel).initrd(initrd); |
| 54 | + if let Ok(mib) = std::env::var("HL_UNIKRAFT_HEAP_MIB") { |
| 55 | + let mib: u64 = mib |
| 56 | + .parse() |
| 57 | + .map_err(|_| anyhow!("HL_UNIKRAFT_HEAP_MIB must be an integer number of MiB"))?; |
| 58 | + guest = guest.heap_size(mib * 1024 * 1024); |
| 59 | + } |
| 60 | + // `HL_UNIKRAFT_MOUNT=host_dir:guest_path` exposes a host directory over hostfs |
| 61 | + // (requires a hostfs-capable guest image, e.g. `hostfs-posix-py`). |
| 62 | + if let Ok(spec) = std::env::var("HL_UNIKRAFT_MOUNT") { |
| 63 | + let (host, guest_path) = spec |
| 64 | + .split_once(':') |
| 65 | + .ok_or_else(|| anyhow!("HL_UNIKRAFT_MOUNT must be 'host_dir:guest_path'"))?; |
| 66 | + guest = guest.mount(host, guest_path); |
| 67 | + } |
| 68 | + |
| 69 | + let mut sandbox = SandboxBuilder::new() |
| 70 | + .with_tools(ToolRegistry::new()) |
| 71 | + .guest(guest) |
| 72 | + .build()?; |
| 73 | + |
| 74 | + // Run the same code twice to show the model: the first call evolves a fresh VM (kernel |
| 75 | + // boot + interpreter start-up); the second call of the *same* code is a warm restore. |
| 76 | + let t = std::time::Instant::now(); |
| 77 | + let cold = sandbox.run(&code)?; |
| 78 | + eprintln!( |
| 79 | + "[timing] cold run={}ms exit={}", |
| 80 | + t.elapsed().as_millis(), |
| 81 | + cold.exit_code |
| 82 | + ); |
| 83 | + print!("{}", cold.stdout); |
| 84 | + eprint!("{}", cold.stderr); |
| 85 | + |
| 86 | + let t = std::time::Instant::now(); |
| 87 | + let warm = sandbox.run(&code)?; |
| 88 | + eprintln!( |
| 89 | + "[timing] warm run={}ms exit={}", |
| 90 | + t.elapsed().as_millis(), |
| 91 | + warm.exit_code |
| 92 | + ); |
| 93 | + print!("{}", warm.stdout); |
| 94 | + eprint!("{}", warm.stderr); |
| 95 | + |
| 96 | + std::process::exit(warm.exit_code); |
| 97 | +} |
0 commit comments