Quick Start
Getting Started with Qlean
Welcome to Qlean!
Qlean is a system-level isolation testing library built on QEMU/KVM. It spins up lightweight VMs in your Rust tests so privileged or risky operations stay off the host.
Overview
Qlean targets two common needs in system-level testing:
1. Complete Resource Isolation
Some tests need root privileges or direct access to kernel interfaces. Running them on the host can leave the machine in a bad state when a test fails. Qlean runs each test in its own VM so failures stay contained and the host stays stable.
2. Convenient Distributed Testing
For distributed or multi-node scenarios, Qlean lets you create and coordinate several VMs from test codeβno separate cluster setup or orchestration layer required.
Key Features
- π Complete Isolation: Based on QEMU/KVM, providing full virtual machine isolation
- π Distributed Testing: Easily create and manage multiple virtual machines
- π‘οΈ RAII-style Interface: Automatic resource management ensures VMs are properly cleaned up
- π¦ Out-of-the-Box: Automated image downloading with verification, no manual configuration needed
- π§ Linux Native: Native support for Linux hosts with multiple guest distributions and architectures
System Prerequisites
Qlean requires proper host setup, including installing QEMU and libvirt, and configuring KVM support. The following section outlines the essential system prerequisites. If you are using a Debian- or Ubuntu-based system, refer to the detailed setup guide for step-by-step host configuration instructions.
Install CLI tools
Install and configure QEMU, libvirt, and xorriso on your Linux host before using Qlean.
Configure qemu-bridge-helper
Qlean uses qemu-bridge-helper to manage networking for multiple virtual machines, so it requires proper configuration.
Grant CAP_NET_ADMIN to the default network helper:
sudo chmod u-s /usr/lib/qemu/qemu-bridge-helper
sudo setcap cap_net_admin+ep /usr/lib/qemu/qemu-bridge-helperqemu-bridge-helper denies all bridges by default, so you must allow the qlbr0 bridge that Qlean creates:
sudo mkdir -p /etc/qemu
sudo sh -c 'echo "allow qlbr0" > /etc/qemu/bridge.conf'
sudo chmod 644 /etc/qemu/bridge.confGetting Started
Add the dependency to your Cargo.toml:
[dev-dependencies]
qlean = "0.3"
tokio = { version = "1", features = ["full"] }
tracing-indicatif = "0.3"
tracing-subscriber = { version = "0.3", features = ["env-filter", "local-time"] }Qlean uses tracing and indicatif for structured logs and progress bars (for example while downloading images). To see that output in your own tests, add tracing-indicatif and tracing-subscriber as above and install a global subscriber once per process. A helper guarded with std::sync::Once works well when many tests share the same setup:
use std::sync::Once;
use tracing_indicatif::IndicatifLayer;
use tracing_subscriber::{
EnvFilter, fmt::time::LocalTime, layer::SubscriberExt, util::SubscriberInitExt,
};
static INIT: Once = Once::new();
pub fn init_tracing() {
INIT.call_once(|| {
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,qlean=info"));
let indicatif_layer = IndicatifLayer::new();
tracing_subscriber::registry()
.with(env_filter)
.with(
tracing_subscriber::fmt::layer()
.with_timer(LocalTime::rfc_3339())
.with_writer(indicatif_layer.get_stderr_writer()),
)
.with(indicatif_layer)
.try_init()
.ok();
});
}Call init_tracing() at the start of each test (or from a shared test harness). Adjust verbosity with RUST_LOG, for example RUST_LOG=debug,qlean=trace.
Basic Example
A minimal single-VM test:
use anyhow::Result;
use qlean::{Image, ImageConfig, MachineConfig, with_machine};
#[tokio::test]
async fn test_with_vm() -> Result<()> {
// Create VM image and config
let image = Image::new(ImageConfig::default()).await?;
let config = MachineConfig::default();
// Execute tests in the virtual machine
with_machine(&image, &config, |vm| {
Box::pin(async {
// Execute a command
let result = vm.exec("whoami").await?;
assert!(result.status.success());
assert_eq!(str::from_utf8(&result.stdout)?.trim(), "root");
Ok(())
})
})
.await?;
Ok(())
}A distributed test with two VMs on the same virtual network:
use anyhow::Result;
use qlean::{Image, ImageConfig, MachineConfig, with_pool};
#[tokio::test]
async fn test_ping() -> Result<()> {
with_pool(|pool| {
Box::pin(async {
// Create VM image and config
let image = Image::new(ImageConfig::default()).await?;
let config = MachineConfig::default();
// Add machines to the pool and initialize them concurrently
pool.add("alice", &image, &config).await?;
pool.add("bob", &image, &config).await?;
pool.init_all().await?;
// Get mutable references to both machines by name
let mut alice = pool.get("alice").await.expect("Alice machine not found");
let mut bob = pool.get("bob").await.expect("Bob machine not found");
// Test ping from Alice to Bob and vice versa
let alice_ip = alice.get_ip().await?;
let result = bob.exec(format!("ping -c 4 {}", alice_ip)).await?;
assert!(result.status.success());
let bob_ip = bob.get_ip().await?;
let result = alice.exec(format!("ping -c 4 {}", bob_ip)).await?;
assert!(result.status.success());
Ok(())
})
})
.await?;
Ok(())
}More examples live in the tests directory.
Repos using Qlean
- rk8s-dev/rk8s: A lightweight Kubernetes-compatible container orchestration system written in Rust.
- web3infra-foundation/mega: A Git-compatible monorepo engine built for the AI Agent era.