arcbox_computer_runtime/config/
runtime.rs1use serde::{Deserialize, Serialize};
2
3use crate::error::{Result, VmmError};
4
5use super::JailerConfig;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RuntimeConfig {
11 pub firecracker: FirecrackerConfig,
12 pub network: NetworkConfig,
13 pub grpc: GrpcConfig,
14 pub defaults: DefaultVmConfig,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct FirecrackerConfig {
29 #[serde(default)]
31 pub jailer: Option<JailerConfig>,
32 pub data_dir: String,
34 #[serde(default = "default_pool_size")]
42 pub pool_size: usize,
43 #[serde(default = "default_warm_create")]
51 pub warm_create: bool,
52 #[serde(default)]
60 pub dmsetup_candidates: Option<Vec<String>>,
61}
62
63fn default_pool_size() -> usize {
64 1
65}
66
67fn default_warm_create() -> bool {
68 true
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct NetworkConfig {
74 pub cidr: String,
76 pub gateway: String,
78 pub dns: Vec<String>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct GrpcConfig {
85 pub unix_socket: String,
87 pub tcp_addr: String,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct DefaultVmConfig {
94 pub vcpus: u64,
95 pub memory_mib: u64,
96 pub kernel: String,
97 pub rootfs: String,
98 pub boot_args: String,
99}
100
101impl Default for RuntimeConfig {
102 fn default() -> Self {
103 Self {
104 firecracker: FirecrackerConfig {
105 jailer: None,
106 data_dir: "/var/lib/firecracker-vmm".into(),
107 pool_size: default_pool_size(),
108 warm_create: default_warm_create(),
109 dmsetup_candidates: None,
110 },
111 network: NetworkConfig {
112 cidr: "172.20.0.0/16".into(),
113 gateway: "172.20.0.1".into(),
114 dns: vec!["1.1.1.1".into(), "8.8.8.8".into()],
115 },
116 grpc: GrpcConfig {
117 unix_socket: "/run/firecracker-vmm/vmm.sock".into(),
118 tcp_addr: String::new(),
119 },
120 defaults: DefaultVmConfig {
121 vcpus: 1,
122 memory_mib: 512,
123 kernel: "/var/lib/firecracker-vmm/kernels/vmlinux".into(),
124 rootfs: "/var/lib/firecracker-vmm/images/ubuntu-22.04.ext4".into(),
125 boot_args: "console=ttyS0 reboot=k panic=1 pci=off".into(),
126 },
127 }
128 }
129}
130
131impl RuntimeConfig {
132 pub fn from_file(path: &str) -> Result<Self> {
134 let content = std::fs::read_to_string(path).map_err(|e| VmmError::Config(e.to_string()))?;
135 toml::from_str(&content).map_err(|e| VmmError::Config(e.to_string()))
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_default_config_has_sane_values() {
145 let cfg = RuntimeConfig::default();
146 assert_eq!(cfg.defaults.vcpus, 1);
147 assert_eq!(cfg.defaults.memory_mib, 512);
148 assert!(cfg.defaults.boot_args.contains("console=ttyS0"));
149 assert!(!cfg.network.cidr.is_empty());
150 assert!(!cfg.firecracker.data_dir.is_empty());
151 }
152
153 #[test]
154 fn pool_size_defaults_to_one_spare_slot() {
155 assert_eq!(RuntimeConfig::default().firecracker.pool_size, 1);
156 let cfg: FirecrackerConfig = toml::from_str("data_dir = \"/var/lib/vmm\"\n").unwrap();
158 assert_eq!(cfg.pool_size, 1);
159 }
160
161 #[test]
162 fn warm_create_defaults_on_and_parses_the_escape_hatch() {
163 assert!(RuntimeConfig::default().firecracker.warm_create);
164 let cfg: FirecrackerConfig = toml::from_str("data_dir = \"/var/lib/vmm\"\n").unwrap();
166 assert!(cfg.warm_create);
167 let cfg: FirecrackerConfig =
169 toml::from_str("data_dir = \"/var/lib/vmm\"\nwarm_create = false\n").unwrap();
170 assert!(!cfg.warm_create);
171 }
172
173 #[test]
174 fn dmsetup_candidates_absent_is_none_and_explicit_lists_parse_as_written() {
175 let cfg: FirecrackerConfig = toml::from_str("data_dir = \"/var/lib/vmm\"\n").unwrap();
178 assert_eq!(cfg.dmsetup_candidates, None);
179 let cfg: FirecrackerConfig = toml::from_str(
181 "data_dir = \"/var/lib/vmm\"\n\
182 dmsetup_candidates = [\"/opt/arcbox/dmsetup\", \"/sbin/dmsetup\"]\n",
183 )
184 .unwrap();
185 assert_eq!(
186 cfg.dmsetup_candidates.as_deref(),
187 Some(
188 &[
189 "/opt/arcbox/dmsetup".to_string(),
190 "/sbin/dmsetup".to_string()
191 ][..]
192 )
193 );
194 let cfg: FirecrackerConfig = toml::from_str(
196 "data_dir = \"/var/lib/vmm\"\n\
197 dmsetup_candidates = []\n",
198 )
199 .unwrap();
200 assert_eq!(cfg.dmsetup_candidates.as_deref(), Some(&[][..]));
201 }
202
203 #[test]
204 fn test_vmm_config_json_roundtrip() {
205 let cfg = RuntimeConfig::default();
206 let json = serde_json::to_string(&cfg).unwrap();
207 let decoded: RuntimeConfig = serde_json::from_str(&json).unwrap();
208 assert_eq!(decoded.defaults.vcpus, cfg.defaults.vcpus);
209 assert_eq!(decoded.defaults.memory_mib, cfg.defaults.memory_mib);
210 assert_eq!(decoded.network.cidr, cfg.network.cidr);
211 assert_eq!(decoded.network.gateway, cfg.network.gateway);
212 }
213
214 #[test]
215 fn test_from_file_missing_returns_config_error() {
216 let result = RuntimeConfig::from_file("/nonexistent/arcbox-test-config.toml");
217 assert!(result.is_err());
218 assert!(matches!(
219 result.unwrap_err(),
220 crate::error::VmmError::Config(_)
221 ));
222 }
223}