use boxlite_shared::errors::BoxliteError;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::str::FromStr;
pub mod controller;
pub mod engine;
pub mod exit_info;
pub mod factory;
pub mod guest_check;
#[cfg(feature = "krun")]
pub mod krun;
pub mod registry;
use crate::jailer::SecurityOptions;
use crate::rootfs::guest::GuestRootfs;
pub use engine::{Vmm, VmmConfig, VmmInstance};
pub use exit_info::ExitInfo;
pub use factory::VmmFactory;
pub use registry::create_engine;
#[derive(
Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum VmmKind {
#[default]
Libkrun,
Firecracker,
}
impl FromStr for VmmKind {
type Err = BoxliteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"libkrun" => Ok(VmmKind::Libkrun),
"firecracker" => Ok(VmmKind::Firecracker),
_ => Err(BoxliteError::Engine(format!(
"Unknown engine type: '{}'. Supported: libkrun, firecracker",
s
))),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsShare {
pub tag: String,
pub host_path: PathBuf,
pub read_only: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FsShares {
shares: Vec<FsShare>,
}
impl FsShares {
pub fn new() -> Self {
Self { shares: Vec::new() }
}
pub fn add(&mut self, tag: impl Into<String>, path: PathBuf, read_only: bool) {
self.shares.push(FsShare {
tag: tag.into(),
host_path: path,
read_only,
});
}
pub fn shares(&self) -> &[FsShare] {
&self.shares
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum DiskFormat {
Raw,
Qcow2,
}
impl DiskFormat {
pub fn as_str(&self) -> &'static str {
match self {
DiskFormat::Raw => "raw",
DiskFormat::Qcow2 => "qcow2",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockDevice {
pub block_id: String,
pub disk_path: PathBuf,
pub read_only: bool,
pub format: DiskFormat,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BlockDevices {
devices: Vec<BlockDevice>,
}
impl BlockDevices {
pub fn new() -> Self {
Self {
devices: Vec::new(),
}
}
pub fn add(&mut self, device: BlockDevice) {
self.devices.push(device);
}
pub fn devices(&self) -> &[BlockDevice] {
&self.devices
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct InstanceSpec {
#[serde(default)]
pub engine: VmmKind,
pub box_id: String,
#[serde(default)]
pub security: SecurityOptions,
pub cpus: Option<u8>,
pub memory_mib: Option<u32>,
pub fs_shares: FsShares,
pub block_devices: BlockDevices,
pub guest_entrypoint: Entrypoint,
pub transport: boxlite_shared::Transport,
pub ready_transport: boxlite_shared::Transport,
pub guest_rootfs: GuestRootfs,
pub network_config: Option<crate::net::NetworkBackendConfig>,
#[serde(skip)]
pub network_backend_endpoint: Option<crate::net::NetworkBackendEndpoint>,
#[serde(default)]
pub disable_network: bool,
pub home_dir: PathBuf,
pub console_output: Option<PathBuf>,
pub exit_file: PathBuf,
pub detach: bool,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Entrypoint {
pub executable: String,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_disk_format_as_str() {
assert_eq!(DiskFormat::Raw.as_str(), "raw");
assert_eq!(DiskFormat::Qcow2.as_str(), "qcow2");
}
#[test]
fn test_block_device_creation() {
let device = BlockDevice {
block_id: "vda".to_string(),
disk_path: PathBuf::from("/tmp/test.qcow2"),
read_only: false,
format: DiskFormat::Qcow2,
};
assert_eq!(device.block_id, "vda");
assert_eq!(device.disk_path, PathBuf::from("/tmp/test.qcow2"));
assert!(!device.read_only);
assert_eq!(device.format, DiskFormat::Qcow2);
}
#[test]
fn test_block_devices() {
let mut devices = BlockDevices::new();
assert_eq!(devices.devices().len(), 0);
devices.add(BlockDevice {
block_id: "vda".to_string(),
disk_path: PathBuf::from("/tmp/test.qcow2"),
read_only: false,
format: DiskFormat::Qcow2,
});
assert_eq!(devices.devices().len(), 1);
devices.add(BlockDevice {
block_id: "vdb".to_string(),
disk_path: PathBuf::from("/tmp/scratch.raw"),
read_only: true,
format: DiskFormat::Raw,
});
assert_eq!(devices.devices().len(), 2);
assert_eq!(devices.devices()[0].block_id, "vda");
assert_eq!(devices.devices()[0].format, DiskFormat::Qcow2);
assert_eq!(devices.devices()[1].block_id, "vdb");
assert_eq!(devices.devices()[1].format, DiskFormat::Raw);
assert!(devices.devices()[1].read_only);
}
#[test]
fn test_block_devices_default() {
let devices = BlockDevices::default();
assert_eq!(devices.devices().len(), 0);
}
#[test]
fn test_disk_format_serialization() {
let raw = DiskFormat::Raw;
let json = serde_json::to_string(&raw).unwrap();
let deserialized: DiskFormat = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, DiskFormat::Raw);
let qcow2 = DiskFormat::Qcow2;
let json = serde_json::to_string(&qcow2).unwrap();
let deserialized: DiskFormat = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, DiskFormat::Qcow2);
}
#[test]
fn test_block_device_serialization() {
let device = BlockDevice {
block_id: "vda".to_string(),
disk_path: PathBuf::from("/tmp/test.qcow2"),
read_only: true,
format: DiskFormat::Qcow2,
};
let json = serde_json::to_string(&device).unwrap();
let deserialized: BlockDevice = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.block_id, "vda");
assert_eq!(deserialized.disk_path, PathBuf::from("/tmp/test.qcow2"));
assert!(deserialized.read_only);
assert_eq!(deserialized.format, DiskFormat::Qcow2);
}
}