use serde::{Deserialize, Serialize};
use crate::error::{Result, VmmError};
use super::JailerConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmmConfig {
pub firecracker: FirecrackerConfig,
pub network: NetworkConfig,
pub grpc: GrpcConfig,
pub defaults: DefaultVmConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirecrackerConfig {
pub binary: String,
#[serde(default)]
pub jailer: Option<JailerConfig>,
pub data_dir: String,
#[serde(default)]
pub log_level: Option<String>,
#[serde(default)]
pub no_seccomp: bool,
#[serde(default)]
pub seccomp_filter: Option<String>,
#[serde(default)]
pub http_api_max_payload_size: Option<usize>,
#[serde(default)]
pub mmds_size_limit: Option<usize>,
#[serde(default)]
pub socket_timeout_secs: Option<u64>,
#[serde(default)]
pub sandbox_datapath: SandboxDatapath,
#[serde(default = "default_pool_size")]
pub pool_size: usize,
#[serde(default = "default_warm_create")]
pub warm_create: bool,
}
fn default_pool_size() -> usize {
1
}
fn default_warm_create() -> bool {
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SandboxDatapath {
#[default]
Ebpf,
Iptables,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkConfig {
pub cidr: String,
pub gateway: String,
pub dns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrpcConfig {
pub unix_socket: String,
pub tcp_addr: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefaultVmConfig {
pub vcpus: u64,
pub memory_mib: u64,
pub kernel: String,
pub rootfs: String,
pub boot_args: String,
}
impl Default for VmmConfig {
fn default() -> Self {
Self {
firecracker: FirecrackerConfig {
binary: "/usr/bin/firecracker".into(),
jailer: None,
data_dir: "/var/lib/firecracker-vmm".into(),
log_level: None,
no_seccomp: false,
seccomp_filter: None,
http_api_max_payload_size: None,
mmds_size_limit: None,
socket_timeout_secs: None,
sandbox_datapath: SandboxDatapath::default(),
pool_size: default_pool_size(),
warm_create: default_warm_create(),
},
network: NetworkConfig {
cidr: "172.20.0.0/16".into(),
gateway: "172.20.0.1".into(),
dns: vec!["1.1.1.1".into(), "8.8.8.8".into()],
},
grpc: GrpcConfig {
unix_socket: "/run/firecracker-vmm/vmm.sock".into(),
tcp_addr: String::new(),
},
defaults: DefaultVmConfig {
vcpus: 1,
memory_mib: 512,
kernel: "/var/lib/firecracker-vmm/kernels/vmlinux".into(),
rootfs: "/var/lib/firecracker-vmm/images/ubuntu-22.04.ext4".into(),
boot_args: "console=ttyS0 reboot=k panic=1 pci=off".into(),
},
}
}
}
impl VmmConfig {
pub fn from_file(path: &str) -> Result<Self> {
let content = std::fs::read_to_string(path).map_err(|e| VmmError::Config(e.to_string()))?;
toml::from_str(&content).map_err(|e| VmmError::Config(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_has_sane_values() {
let cfg = VmmConfig::default();
assert_eq!(cfg.defaults.vcpus, 1);
assert_eq!(cfg.defaults.memory_mib, 512);
assert!(cfg.defaults.boot_args.contains("console=ttyS0"));
assert!(!cfg.network.cidr.is_empty());
assert!(!cfg.firecracker.binary.is_empty());
}
#[test]
fn pool_size_defaults_to_one_spare_slot() {
assert_eq!(VmmConfig::default().firecracker.pool_size, 1);
let cfg: FirecrackerConfig =
toml::from_str("binary = \"/usr/bin/firecracker\"\ndata_dir = \"/var/lib/vmm\"\n")
.unwrap();
assert_eq!(cfg.pool_size, 1);
}
#[test]
fn warm_create_defaults_on_and_parses_the_escape_hatch() {
assert!(VmmConfig::default().firecracker.warm_create);
let cfg: FirecrackerConfig =
toml::from_str("binary = \"/usr/bin/firecracker\"\ndata_dir = \"/var/lib/vmm\"\n")
.unwrap();
assert!(cfg.warm_create);
let cfg: FirecrackerConfig = toml::from_str(
"binary = \"/usr/bin/firecracker\"\ndata_dir = \"/var/lib/vmm\"\nwarm_create = false\n",
)
.unwrap();
assert!(!cfg.warm_create);
}
#[test]
fn sandbox_datapath_defaults_to_ebpf_and_parses_the_fallback() {
assert_eq!(
VmmConfig::default().firecracker.sandbox_datapath,
SandboxDatapath::Ebpf
);
let cfg: FirecrackerConfig =
toml::from_str("binary = \"/usr/bin/firecracker\"\ndata_dir = \"/var/lib/vmm\"\n")
.unwrap();
assert_eq!(cfg.sandbox_datapath, SandboxDatapath::Ebpf);
let cfg: FirecrackerConfig = toml::from_str(
"binary = \"/usr/bin/firecracker\"\ndata_dir = \"/var/lib/vmm\"\nsandbox_datapath = \"iptables\"\n",
)
.unwrap();
assert_eq!(cfg.sandbox_datapath, SandboxDatapath::Iptables);
}
#[test]
fn test_vmm_config_json_roundtrip() {
let cfg = VmmConfig::default();
let json = serde_json::to_string(&cfg).unwrap();
let decoded: VmmConfig = serde_json::from_str(&json).unwrap();
assert_eq!(decoded.defaults.vcpus, cfg.defaults.vcpus);
assert_eq!(decoded.defaults.memory_mib, cfg.defaults.memory_mib);
assert_eq!(decoded.network.cidr, cfg.network.cidr);
assert_eq!(decoded.network.gateway, cfg.network.gateway);
}
#[test]
fn test_from_file_missing_returns_config_error() {
let result = VmmConfig::from_file("/nonexistent/arcbox-test-config.toml");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
crate::error::VmmError::Config(_)
));
}
}