mimobox_sdk/config.rs
1use std::path::PathBuf;
2use std::time::Duration;
3
4#[cfg(feature = "vm")]
5use crate::error::SdkError;
6use mimobox_core::{SandboxConfig, SeccompProfile};
7
8/// Isolation level selection strategy.
9///
10/// Controls which sandboxing backend is used. `Auto` enables smart routing
11/// based on command type and trust level; explicit variants force a specific backend.
12///
13/// # Smart Routing Rules (Auto)
14///
15/// - `.wasm` / `.wat` / `.wast` files → `Wasm`
16/// - `TrustLevel::Untrusted` on Linux + `vm` feature → `MicroVm`
17/// - All other commands → `Os`
18///
19/// # Examples
20///
21/// ```
22/// use mimobox_sdk::IsolationLevel;
23///
24/// let level = IsolationLevel::Auto;
25/// assert_eq!(level, IsolationLevel::default());
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum IsolationLevel {
29 /// Smart routing based on command type and `TrustLevel`.
30 #[default]
31 Auto,
32 /// OS-level isolation: Landlock + Seccomp + Namespaces (Linux) or Seatbelt (macOS).
33 Os,
34 /// Wasm-level isolation via Wasmtime. Sub-millisecond cold start.
35 Wasm,
36 /// microVM-level isolation via KVM. Hardware-enforced boundary.
37 MicroVm,
38}
39
40/// Trust level for code being executed.
41///
42/// Affects auto-routing decisions when `IsolationLevel::Auto` is used.
43/// `Untrusted` code is always routed to the strongest available isolation.
44///
45/// # Fail-Closed Behavior
46///
47/// When `TrustLevel::Untrusted` is set and the microVM backend is unavailable
48/// (non-Linux platform or `vm` feature not enabled), the SDK returns an error
49/// instead of silently downgrading to OS-level isolation.
50///
51/// # Examples
52///
53/// ```
54/// use mimobox_sdk::TrustLevel;
55///
56/// let level = TrustLevel::SemiTrusted;
57/// assert_eq!(level, TrustLevel::default());
58/// ```
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum TrustLevel {
61 /// Trusted code: self-authored or fully audited.
62 Trusted,
63 /// Semi-trusted code: third-party libraries or partially audited code.
64 #[default]
65 SemiTrusted,
66 /// Untrusted code: user-submitted, downloaded, or otherwise unverified.
67 Untrusted,
68}
69
70/// Network access policy for the sandbox.
71///
72/// Controls whether the sandbox can access the network and through which channels.
73/// The default policy denies all network access.
74///
75/// # Examples
76///
77/// ```
78/// use mimobox_sdk::NetworkPolicy;
79///
80/// let policy = NetworkPolicy::DenyAll;
81/// match policy {
82/// NetworkPolicy::DenyAll => {},
83/// _ => panic!("expected DenyAll"),
84/// }
85/// ```
86#[derive(Debug, Clone, Default)]
87pub enum NetworkPolicy {
88 /// Deny all network access. Default policy.
89 #[default]
90 DenyAll,
91 /// Keep direct sandbox network blocked, but allow HTTP requests through the
92 /// host-side controlled proxy to the specified domains.
93 AllowDomains(Vec<String>),
94 /// Allow unrestricted network access. Uses a permissive Seccomp profile.
95 AllowAll,
96}
97
98/// SDK-level configuration for sandbox creation and behavior.
99///
100/// Use [`Config::builder()`] to construct a `Config` with the builder pattern.
101/// All fields have sensible defaults that prioritize security.
102///
103/// # Key Behaviors
104///
105/// - **Memory**: For the microVM backend, effective guest memory is
106/// `min(memory_limit_mb, vm_memory_mb)`.
107/// - **Timeout**: Internally rounded up to whole seconds. For example,
108/// `1500ms` becomes `2s`.
109/// - **HTTP domains**: The `allowed_http_domains` list supports glob patterns
110/// like `*.openai.com`. It is combined with `NetworkPolicy::AllowDomains`.
111///
112/// # Examples
113///
114/// ```
115/// use mimobox_sdk::Config;
116///
117/// let config = Config::default();
118/// assert_eq!(config.vm_vcpu_count, 1);
119/// assert_eq!(config.vm_memory_mb, 256);
120/// ```
121#[derive(Debug, Clone)]
122pub struct Config {
123 /// Isolation level selection strategy. `Auto` enables smart routing.
124 pub isolation: IsolationLevel,
125 /// Trust level affecting auto-routing decisions.
126 pub trust_level: TrustLevel,
127 /// Network access policy.
128 pub network: NetworkPolicy,
129 /// Command execution timeout. `None` means no timeout.
130 pub timeout: Option<Duration>,
131 /// Memory limit in MiB. Applied via cgroups v2 or setrlimit.
132 pub memory_limit_mb: Option<u64>,
133 /// CPU time quota in microseconds. `None` means unlimited.
134 pub cpu_quota_us: Option<u64>,
135 /// CPU period in microseconds. Defaults to 100000 (100ms).
136 pub cpu_period_us: u64,
137 /// Read-only mount paths inside the sandbox.
138 pub fs_readonly: Vec<PathBuf>,
139 /// Read-write mount paths inside the sandbox.
140 pub fs_readwrite: Vec<PathBuf>,
141 /// HTTP proxy domain whitelist (supports glob patterns like `*.openai.com`).
142 pub allowed_http_domains: Vec<String>,
143 /// Whether to allow child process creation (fork/clone) inside the sandbox.
144 pub allow_fork: bool,
145 /// microVM vCPU count. Only affects the microVM backend.
146 pub vm_vcpu_count: u8,
147 /// microVM guest memory size in MiB. Capped by `memory_limit_mb` if set.
148 pub vm_memory_mb: u32,
149 /// Custom microVM kernel image path. Falls back to `~/.mimobox/assets/vmlinux` if unset.
150 pub kernel_path: Option<PathBuf>,
151 /// Custom microVM rootfs path. Falls back to `~/.mimobox/assets/rootfs.cpio.gz` if unset.
152 pub rootfs_path: Option<PathBuf>,
153}
154
155impl Default for Config {
156 fn default() -> Self {
157 Self {
158 isolation: IsolationLevel::Auto,
159 trust_level: TrustLevel::default(),
160 network: NetworkPolicy::default(),
161 timeout: Some(Duration::from_secs(30)),
162 memory_limit_mb: Some(512),
163 cpu_quota_us: None,
164 cpu_period_us: 100_000,
165 fs_readonly: vec![
166 "/usr".into(),
167 "/lib".into(),
168 "/lib64".into(),
169 "/bin".into(),
170 "/sbin".into(),
171 "/dev".into(),
172 "/proc".into(),
173 "/etc".into(),
174 ],
175 fs_readwrite: vec!["/tmp".into()],
176 allowed_http_domains: Vec::new(),
177 allow_fork: false,
178 vm_vcpu_count: 1,
179 vm_memory_mb: 256,
180 kernel_path: None,
181 rootfs_path: None,
182 }
183 }
184}
185
186impl Config {
187 /// Returns a [`ConfigBuilder`] for constructing a `Config` with the builder pattern.
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// use mimobox_sdk::{Config, IsolationLevel};
193 /// use std::time::Duration;
194 ///
195 /// let config = Config::builder()
196 /// .isolation(IsolationLevel::Os)
197 /// .timeout(Duration::from_secs(10))
198 /// .build();
199 ///
200 /// assert_eq!(config.isolation, IsolationLevel::Os);
201 /// ```
202 pub fn builder() -> ConfigBuilder {
203 ConfigBuilder::default()
204 }
205
206 /// Converts to the internal `mimobox_core::SandboxConfig`.
207 pub(crate) fn to_sandbox_config(&self) -> SandboxConfig {
208 let deny_network = resolve_deny_network(&self.network);
209
210 let mut config = SandboxConfig::default();
211 config.fs_readonly = self.fs_readonly.clone();
212 config.fs_readwrite = self.fs_readwrite.clone();
213 config.deny_network = deny_network;
214 config.memory_limit_mb = self.memory_limit_mb;
215 config.cpu_quota_us = self.cpu_quota_us;
216 config.cpu_period_us = self.cpu_period_us;
217 config.timeout_secs = self.timeout.map(round_up_timeout_secs);
218 config.seccomp_profile = resolve_seccomp_profile(deny_network, self.allow_fork);
219 config.allow_fork = self.allow_fork;
220 config.allowed_http_domains = resolve_allowed_http_domains(self);
221 config
222 }
223
224 #[cfg(feature = "vm")]
225 #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
226 pub(crate) fn to_microvm_config(&self) -> Result<mimobox_vm::MicrovmConfig, SdkError> {
227 let defaults = mimobox_vm::MicrovmConfig::default();
228 let memory_mb = resolve_vm_memory_mb(self)?;
229
230 Ok(mimobox_vm::MicrovmConfig {
231 vcpu_count: self.vm_vcpu_count,
232 memory_mb,
233 cpu_quota_us: self.cpu_quota_us,
234 kernel_path: self
235 .kernel_path
236 .clone()
237 .unwrap_or_else(|| defaults.kernel_path.clone()),
238 rootfs_path: self
239 .rootfs_path
240 .clone()
241 .unwrap_or_else(|| defaults.rootfs_path.clone()),
242 })
243 }
244}
245
246fn resolve_deny_network(network: &NetworkPolicy) -> bool {
247 match network {
248 NetworkPolicy::DenyAll => true,
249 NetworkPolicy::AllowDomains(_) => true,
250 NetworkPolicy::AllowAll => false,
251 }
252}
253
254fn resolve_allowed_http_domains(config: &Config) -> Vec<String> {
255 let mut domains = config.allowed_http_domains.clone();
256 if let NetworkPolicy::AllowDomains(network_domains) = &config.network {
257 for domain in network_domains {
258 if !domains.contains(domain) {
259 domains.push(domain.clone());
260 }
261 }
262 }
263 domains
264}
265
266fn round_up_timeout_secs(timeout: Duration) -> u64 {
267 let millis = timeout.as_millis();
268 let seconds = millis.div_ceil(1_000);
269 u64::try_from(seconds).unwrap_or(u64::MAX)
270}
271
272fn resolve_seccomp_profile(deny_network: bool, allow_fork: bool) -> SeccompProfile {
273 match (deny_network, allow_fork) {
274 (true, true) => SeccompProfile::EssentialWithFork,
275 (true, false) => SeccompProfile::Essential,
276 (false, true) => SeccompProfile::NetworkWithFork,
277 (false, false) => SeccompProfile::Network,
278 }
279}
280
281#[cfg(feature = "vm")]
282fn resolve_vm_memory_mb(config: &Config) -> Result<u32, SdkError> {
283 let requested_memory_mb = u64::from(config.vm_memory_mb);
284 let effective_memory_mb = match config.memory_limit_mb {
285 Some(memory_limit_mb) => requested_memory_mb.min(memory_limit_mb),
286 None => requested_memory_mb,
287 };
288
289 u32::try_from(effective_memory_mb).map_err(|_| {
290 SdkError::Config(format!(
291 "microVM guest memory 超出 u32 范围: {effective_memory_mb} MB"
292 ))
293 })
294}
295
296/// Fluent builder for constructing [`Config`] instances.
297///
298/// All methods consume and return `self`, enabling method chaining.
299/// Call [`build()`](ConfigBuilder::build) to produce the final `Config`.
300///
301/// # Examples
302///
303/// ```
304/// use mimobox_sdk::{Config, IsolationLevel, NetworkPolicy};
305/// use std::time::Duration;
306///
307/// let config = Config::builder()
308/// .isolation(IsolationLevel::MicroVm)
309/// .timeout(Duration::from_secs(60))
310/// .memory_limit_mb(256)
311/// .allowed_http_domains(["api.openai.com"])
312/// .build();
313///
314/// assert_eq!(config.isolation, IsolationLevel::MicroVm);
315/// ```
316#[derive(Debug, Clone, Default)]
317pub struct ConfigBuilder {
318 inner: Config,
319}
320
321impl ConfigBuilder {
322 /// Set the isolation level selection strategy.
323 ///
324 /// # Examples
325 ///
326 /// ```
327 /// use mimobox_sdk::{Config, IsolationLevel};
328 ///
329 /// let config = Config::builder()
330 /// .isolation(IsolationLevel::Wasm)
331 /// .build();
332 ///
333 /// assert_eq!(config.isolation, IsolationLevel::Wasm);
334 /// ```
335 pub fn isolation(mut self, level: IsolationLevel) -> Self {
336 self.inner.isolation = level;
337 self
338 }
339
340 /// Set the trust level, affecting auto-routing decisions.
341 ///
342 /// When set to `TrustLevel::Untrusted`, the SDK will fail-closed if the
343 /// microVM backend is not available, rather than silently downgrading.
344 ///
345 /// # Examples
346 ///
347 /// ```
348 /// use mimobox_sdk::{Config, TrustLevel};
349 ///
350 /// let config = Config::builder()
351 /// .trust_level(TrustLevel::Untrusted)
352 /// .build();
353 ///
354 /// assert_eq!(config.trust_level, TrustLevel::Untrusted);
355 /// ```
356 pub fn trust_level(mut self, level: TrustLevel) -> Self {
357 self.inner.trust_level = level;
358 self
359 }
360
361 /// Set the network access policy.
362 ///
363 /// # Examples
364 ///
365 /// ```
366 /// use mimobox_sdk::{Config, NetworkPolicy};
367 ///
368 /// let config = Config::builder()
369 /// .network(NetworkPolicy::AllowDomains(vec!["api.openai.com".to_string()]))
370 /// .build();
371 ///
372 /// assert!(matches!(config.network, NetworkPolicy::AllowDomains(_)));
373 /// ```
374 pub fn network(mut self, policy: NetworkPolicy) -> Self {
375 self.inner.network = policy;
376 self
377 }
378
379 /// Set the default command execution timeout.
380 ///
381 /// Internally rounded up to whole seconds. For example, `1500ms` maps to `2s`.
382 ///
383 /// # Examples
384 ///
385 /// ```
386 /// use mimobox_sdk::Config;
387 /// use std::time::Duration;
388 ///
389 /// let config = Config::builder()
390 /// .timeout(Duration::from_secs(10))
391 /// .build();
392 ///
393 /// assert_eq!(config.timeout, Some(Duration::from_secs(10)));
394 /// ```
395 pub fn timeout(mut self, timeout: Duration) -> Self {
396 self.inner.timeout = Some(timeout);
397 self
398 }
399
400 /// Set the memory limit in MiB.
401 ///
402 /// For the microVM backend, the effective guest memory is
403 /// `min(memory_limit_mb, vm_memory_mb)`.
404 ///
405 /// # Examples
406 ///
407 /// ```
408 /// use mimobox_sdk::Config;
409 ///
410 /// let config = Config::builder()
411 /// .memory_limit_mb(256)
412 /// .build();
413 ///
414 /// assert_eq!(config.memory_limit_mb, Some(256));
415 /// ```
416 pub fn memory_limit_mb(mut self, mb: u64) -> Self {
417 self.inner.memory_limit_mb = Some(mb);
418 self
419 }
420
421 /// Set the CPU time quota in microseconds.
422 ///
423 /// Linux OS backend maps this to cgroup v2 `cpu.max`; microVM stores the
424 /// value for backend-specific enforcement. Other backends ignore it.
425 pub fn cpu_quota(mut self, quota_us: u64) -> Self {
426 self.inner.cpu_quota_us = Some(quota_us);
427 self
428 }
429
430 /// Set the CPU period in microseconds.
431 pub fn cpu_period(mut self, period_us: u64) -> Self {
432 self.inner.cpu_period_us = period_us;
433 self
434 }
435
436 /// Set the read-only mount paths.
437 ///
438 /// Paths listed here are mounted read-only inside the sandbox.
439 ///
440 /// # Examples
441 ///
442 /// ```
443 /// use mimobox_sdk::Config;
444 ///
445 /// let config = Config::builder()
446 /// .fs_readonly(["/usr", "/lib"])
447 /// .build();
448 ///
449 /// assert_eq!(config.fs_readonly.len(), 2);
450 /// ```
451 pub fn fs_readonly(mut self, paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
452 self.inner.fs_readonly = paths.into_iter().map(Into::into).collect();
453 self
454 }
455
456 /// Set the read-write mount paths.
457 ///
458 /// # Examples
459 ///
460 /// ```
461 /// use mimobox_sdk::Config;
462 ///
463 /// let config = Config::builder()
464 /// .fs_readwrite(["/tmp", "/workspace"])
465 /// .build();
466 ///
467 /// assert_eq!(config.fs_readwrite.len(), 2);
468 /// ```
469 pub fn fs_readwrite(mut self, paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
470 self.inner.fs_readwrite = paths.into_iter().map(Into::into).collect();
471 self
472 }
473
474 /// Set whether child process creation (fork/clone) is allowed inside the sandbox.
475 ///
476 /// Default is `false`. Set to `true` for shell or interpreter workloads.
477 ///
478 /// # Examples
479 ///
480 /// ```
481 /// use mimobox_sdk::Config;
482 ///
483 /// let config = Config::builder()
484 /// .allow_fork(true)
485 /// .build();
486 ///
487 /// assert!(config.allow_fork);
488 /// ```
489 pub fn allow_fork(mut self, allow: bool) -> Self {
490 self.inner.allow_fork = allow;
491 self
492 }
493
494 /// Set the HTTP proxy domain whitelist.
495 ///
496 /// Supports glob patterns like `*.openai.com`. Combined with
497 /// `NetworkPolicy::AllowDomains` without duplicates.
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// use mimobox_sdk::Config;
503 ///
504 /// let config = Config::builder()
505 /// .allowed_http_domains(["api.openai.com", "*.openai.com"])
506 /// .build();
507 ///
508 /// assert_eq!(config.allowed_http_domains.len(), 2);
509 /// ```
510 pub fn allowed_http_domains(
511 mut self,
512 domains: impl IntoIterator<Item = impl Into<String>>,
513 ) -> Self {
514 self.inner.allowed_http_domains = domains.into_iter().map(Into::into).collect();
515 self
516 }
517
518 /// Set the microVM vCPU count.
519 ///
520 /// Only affects the microVM backend. Default is `1`.
521 ///
522 /// # Examples
523 ///
524 /// ```
525 /// use mimobox_sdk::Config;
526 ///
527 /// let config = Config::builder()
528 /// .vm_vcpu_count(4)
529 /// .build();
530 ///
531 /// assert_eq!(config.vm_vcpu_count, 4);
532 /// ```
533 pub fn vm_vcpu_count(mut self, count: u8) -> Self {
534 self.inner.vm_vcpu_count = count;
535 self
536 }
537
538 /// Set the microVM guest memory size in MiB.
539 ///
540 /// Capped by `memory_limit_mb` if set. Default is `256` MiB.
541 ///
542 /// # Examples
543 ///
544 /// ```
545 /// use mimobox_sdk::Config;
546 ///
547 /// let config = Config::builder()
548 /// .vm_memory_mb(512)
549 /// .build();
550 ///
551 /// assert_eq!(config.vm_memory_mb, 512);
552 /// ```
553 pub fn vm_memory_mb(mut self, mb: u32) -> Self {
554 self.inner.vm_memory_mb = mb;
555 self
556 }
557
558 /// Set the microVM kernel image path.
559 ///
560 /// If unset, falls back to `$VM_ASSETS_DIR/vmlinux` or `~/.mimobox/assets/vmlinux`.
561 ///
562 /// # Examples
563 ///
564 /// ```
565 /// use mimobox_sdk::Config;
566 ///
567 /// let config = Config::builder()
568 /// .kernel_path("/opt/mimobox/vmlinux")
569 /// .build();
570 ///
571 /// assert_eq!(config.kernel_path, Some(std::path::PathBuf::from("/opt/mimobox/vmlinux")));
572 /// ```
573 pub fn kernel_path(mut self, path: impl Into<PathBuf>) -> Self {
574 self.inner.kernel_path = Some(path.into());
575 self
576 }
577
578 /// Set the microVM rootfs path.
579 ///
580 /// If unset, falls back to `$VM_ASSETS_DIR/rootfs.cpio.gz` or `~/.mimobox/assets/rootfs.cpio.gz`.
581 ///
582 /// # Examples
583 ///
584 /// ```
585 /// use mimobox_sdk::Config;
586 ///
587 /// let config = Config::builder()
588 /// .rootfs_path("/opt/mimobox/rootfs.cpio.gz")
589 /// .build();
590 ///
591 /// assert_eq!(config.rootfs_path, Some(std::path::PathBuf::from("/opt/mimobox/rootfs.cpio.gz")));
592 /// ```
593 pub fn rootfs_path(mut self, path: impl Into<PathBuf>) -> Self {
594 self.inner.rootfs_path = Some(path.into());
595 self
596 }
597
598 /// Remove the default timeout, allowing commands to run indefinitely.
599 ///
600 /// # Examples
601 ///
602 /// ```
603 /// use mimobox_sdk::Config;
604 ///
605 /// let config = Config::builder()
606 /// .no_timeout()
607 /// .build();
608 ///
609 /// assert_eq!(config.timeout, None);
610 /// ```
611 pub fn no_timeout(mut self) -> Self {
612 self.inner.timeout = None;
613 self
614 }
615
616 /// Produce the final `Config`.
617 ///
618 /// # Examples
619 ///
620 /// ```
621 /// use mimobox_sdk::Config;
622 ///
623 /// let config = Config::builder().build();
624 /// assert_eq!(config.isolation, mimobox_sdk::IsolationLevel::Auto);
625 /// ```
626 pub fn build(self) -> Config {
627 self.inner
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn default_config_keeps_microvm_artifact_paths_unset() {
637 let config = Config::default();
638
639 assert_eq!(config.vm_vcpu_count, 1);
640 assert_eq!(config.vm_memory_mb, 256);
641 assert_eq!(config.kernel_path, None);
642 assert_eq!(config.rootfs_path, None);
643 }
644
645 #[test]
646 fn builder_can_override_microvm_resource_config() {
647 let config = Config::builder().vm_vcpu_count(4).vm_memory_mb(768).build();
648
649 assert_eq!(config.vm_vcpu_count, 4);
650 assert_eq!(config.vm_memory_mb, 768);
651 }
652
653 #[test]
654 fn builder_can_override_microvm_artifact_paths() {
655 let config = Config::builder()
656 .kernel_path("/opt/mimobox/vmlinux")
657 .rootfs_path("/opt/mimobox/rootfs.cpio.gz")
658 .build();
659
660 assert_eq!(
661 config.kernel_path,
662 Some(PathBuf::from("/opt/mimobox/vmlinux"))
663 );
664 assert_eq!(
665 config.rootfs_path,
666 Some(PathBuf::from("/opt/mimobox/rootfs.cpio.gz"))
667 );
668 }
669
670 #[test]
671 fn allow_domains_keep_direct_network_denied_and_forward_whitelist() {
672 let config = Config::builder()
673 .network(NetworkPolicy::AllowDomains(vec!["example.com".to_string()]))
674 .build();
675
676 assert!(config.to_sandbox_config().deny_network);
677 assert_eq!(
678 config.to_sandbox_config().allowed_http_domains,
679 vec!["example.com".to_string()]
680 );
681 }
682
683 #[test]
684 fn allow_all_opens_network_and_uses_network_seccomp_profile() {
685 let config = Config::builder()
686 .network(NetworkPolicy::AllowAll)
687 .allow_fork(true)
688 .build();
689 let sandbox_config = config.to_sandbox_config();
690
691 assert!(!sandbox_config.deny_network);
692 assert!(matches!(
693 sandbox_config.seccomp_profile,
694 SeccompProfile::NetworkWithFork
695 ));
696 }
697
698 #[test]
699 fn timeout_rounds_up_instead_of_truncating_subsecond_precision() {
700 let config = Config::builder()
701 .timeout(Duration::from_millis(1_500))
702 .build();
703 assert_eq!(config.to_sandbox_config().timeout_secs, Some(2));
704
705 let config = Config::builder().timeout(Duration::from_millis(1)).build();
706 assert_eq!(config.to_sandbox_config().timeout_secs, Some(1));
707 }
708
709 #[test]
710 fn explicit_allowed_http_domains_are_forwarded_to_sandbox_config() {
711 let config = Config::builder()
712 .allowed_http_domains(["api.openai.com", "*.openai.com"])
713 .build();
714 let sandbox_config = config.to_sandbox_config();
715
716 assert_eq!(
717 sandbox_config.allowed_http_domains,
718 vec!["api.openai.com".to_string(), "*.openai.com".to_string()]
719 );
720 }
721
722 #[test]
723 fn allow_domains_merge_with_explicit_http_whitelist_without_duplicates() {
724 let config = Config::builder()
725 .network(NetworkPolicy::AllowDomains(vec![
726 "api.openai.com".to_string(),
727 "example.com".to_string(),
728 ]))
729 .allowed_http_domains(["api.openai.com", "*.openai.com"])
730 .build();
731 let sandbox_config = config.to_sandbox_config();
732
733 assert_eq!(
734 sandbox_config.allowed_http_domains,
735 vec![
736 "api.openai.com".to_string(),
737 "*.openai.com".to_string(),
738 "example.com".to_string()
739 ]
740 );
741 }
742
743 #[cfg(feature = "vm")]
744 #[test]
745 fn microvm_config_uses_default_artifact_paths_when_not_overridden() {
746 let config = Config::default();
747 let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
748 let defaults = mimobox_vm::MicrovmConfig::default();
749
750 assert_eq!(microvm_config.kernel_path, defaults.kernel_path);
751 assert_eq!(microvm_config.rootfs_path, defaults.rootfs_path);
752 }
753
754 #[cfg(feature = "vm")]
755 #[test]
756 fn microvm_config_applies_resource_and_artifact_overrides() {
757 let config = Config::builder()
758 .vm_vcpu_count(4)
759 .vm_memory_mb(768)
760 .memory_limit_mb(1024)
761 .cpu_quota(50_000)
762 .cpu_period(100_000)
763 .kernel_path("/srv/mimobox/vmlinux")
764 .rootfs_path("/srv/mimobox/rootfs.cpio.gz")
765 .build();
766 let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
767
768 assert_eq!(microvm_config.vcpu_count, 4);
769 assert_eq!(microvm_config.memory_mb, 768);
770 assert_eq!(microvm_config.cpu_quota_us, Some(50_000));
771 assert_eq!(config.to_sandbox_config().cpu_period_us, 100_000);
772 assert_eq!(
773 microvm_config.kernel_path,
774 PathBuf::from("/srv/mimobox/vmlinux")
775 );
776 assert_eq!(
777 microvm_config.rootfs_path,
778 PathBuf::from("/srv/mimobox/rootfs.cpio.gz")
779 );
780 }
781
782 #[cfg(feature = "vm")]
783 #[test]
784 fn microvm_config_caps_vm_memory_with_memory_limit() {
785 let config = Config::builder()
786 .vm_memory_mb(768)
787 .memory_limit_mb(256)
788 .build();
789 let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
790
791 assert_eq!(microvm_config.memory_mb, 256);
792 }
793
794 #[cfg(feature = "vm")]
795 #[test]
796 fn microvm_config_ignores_out_of_range_memory_limit_when_vm_memory_is_lower() {
797 let config = Config::builder()
798 .vm_memory_mb(768)
799 .memory_limit_mb(u64::MAX)
800 .build();
801 let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
802
803 assert_eq!(microvm_config.memory_mb, 768);
804 }
805}