use std::net::Ipv4Addr;
use ipnetwork::Ipv4Network;
use microsandbox_utils::{DEFAULT_MEMORY_MIB, DEFAULT_NUM_VCPUS};
use typed_path::Utf8UnixPathBuf;
use crate::{
config::{EnvPair, NetworkScope, PathPair, PortPair},
MicrosandboxResult,
};
use super::{LinuxRlimit, LogLevel, MicroVm, MicroVmConfig, Rootfs};
#[derive(Debug)]
pub struct MicroVmConfigBuilder<R, E> {
log_level: LogLevel,
rootfs: R,
num_vcpus: u8,
memory_mib: u32,
mapped_dirs: Vec<PathPair>,
port_map: Vec<PortPair>,
scope: NetworkScope,
ip: Option<Ipv4Addr>,
subnet: Option<Ipv4Network>,
rlimits: Vec<LinuxRlimit>,
workdir_path: Option<Utf8UnixPathBuf>,
exec_path: E,
args: Vec<String>,
env: Vec<EnvPair>,
console_output: Option<Utf8UnixPathBuf>,
}
#[derive(Debug)]
pub struct MicroVmBuilder<R, E> {
inner: MicroVmConfigBuilder<R, E>,
}
impl<R, M> MicroVmConfigBuilder<R, M> {
pub fn log_level(mut self, log_level: LogLevel) -> Self {
self.log_level = log_level;
self
}
pub fn rootfs(self, rootfs: Rootfs) -> MicroVmConfigBuilder<Rootfs, M> {
MicroVmConfigBuilder {
log_level: self.log_level,
rootfs,
num_vcpus: self.num_vcpus,
memory_mib: self.memory_mib,
mapped_dirs: self.mapped_dirs,
port_map: self.port_map,
scope: self.scope,
ip: self.ip,
subnet: self.subnet,
rlimits: self.rlimits,
workdir_path: self.workdir_path,
exec_path: self.exec_path,
args: self.args,
env: self.env,
console_output: self.console_output,
}
}
pub fn num_vcpus(mut self, num_vcpus: u8) -> Self {
self.num_vcpus = num_vcpus;
self
}
pub fn memory_mib(mut self, memory_mib: u32) -> Self {
self.memory_mib = memory_mib;
self
}
pub fn mapped_dirs(mut self, mapped_dirs: impl IntoIterator<Item = PathPair>) -> Self {
self.mapped_dirs = mapped_dirs.into_iter().collect();
self
}
pub fn port_map(mut self, port_map: impl IntoIterator<Item = PortPair>) -> Self {
self.port_map = port_map.into_iter().collect();
self
}
pub fn scope(mut self, scope: NetworkScope) -> Self {
self.scope = scope;
self
}
pub fn ip(mut self, ip: Ipv4Addr) -> Self {
self.ip = Some(ip);
self
}
pub fn subnet(mut self, subnet: Ipv4Network) -> Self {
self.subnet = Some(subnet);
self
}
pub fn rlimits(mut self, rlimits: impl IntoIterator<Item = LinuxRlimit>) -> Self {
self.rlimits = rlimits.into_iter().collect();
self
}
pub fn workdir_path(mut self, workdir_path: impl Into<Utf8UnixPathBuf>) -> Self {
self.workdir_path = Some(workdir_path.into());
self
}
pub fn exec_path(
self,
exec_path: impl Into<Utf8UnixPathBuf>,
) -> MicroVmConfigBuilder<R, Utf8UnixPathBuf> {
MicroVmConfigBuilder {
log_level: self.log_level,
rootfs: self.rootfs,
num_vcpus: self.num_vcpus,
memory_mib: self.memory_mib,
mapped_dirs: self.mapped_dirs,
port_map: self.port_map,
scope: self.scope,
ip: self.ip,
subnet: self.subnet,
rlimits: self.rlimits,
workdir_path: self.workdir_path,
exec_path: exec_path.into(),
args: self.args,
env: self.env,
console_output: self.console_output,
}
}
pub fn args<'a>(mut self, args: impl IntoIterator<Item = &'a str>) -> Self {
self.args = args.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn env(mut self, env: impl IntoIterator<Item = EnvPair>) -> Self {
self.env = env.into_iter().collect();
self
}
pub fn console_output(mut self, console_output: impl Into<Utf8UnixPathBuf>) -> Self {
self.console_output = Some(console_output.into());
self
}
}
impl<R, M> MicroVmBuilder<R, M> {
pub fn log_level(mut self, log_level: LogLevel) -> Self {
self.inner = self.inner.log_level(log_level);
self
}
pub fn rootfs(self, rootfs: Rootfs) -> MicroVmBuilder<Rootfs, M> {
MicroVmBuilder {
inner: self.inner.rootfs(rootfs),
}
}
pub fn num_vcpus(mut self, num_vcpus: u8) -> Self {
self.inner = self.inner.num_vcpus(num_vcpus);
self
}
pub fn memory_mib(mut self, memory_mib: u32) -> Self {
self.inner = self.inner.memory_mib(memory_mib);
self
}
pub fn mapped_dirs(mut self, mapped_dirs: impl IntoIterator<Item = PathPair>) -> Self {
self.inner = self.inner.mapped_dirs(mapped_dirs);
self
}
pub fn port_map(mut self, port_map: impl IntoIterator<Item = PortPair>) -> Self {
self.inner = self.inner.port_map(port_map);
self
}
pub fn scope(mut self, scope: NetworkScope) -> Self {
self.inner = self.inner.scope(scope);
self
}
pub fn ip(mut self, ip: Ipv4Addr) -> Self {
self.inner = self.inner.ip(ip);
self
}
pub fn subnet(mut self, subnet: Ipv4Network) -> Self {
self.inner = self.inner.subnet(subnet);
self
}
pub fn rlimits(mut self, rlimits: impl IntoIterator<Item = LinuxRlimit>) -> Self {
self.inner = self.inner.rlimits(rlimits);
self
}
pub fn workdir_path(mut self, workdir_path: impl Into<Utf8UnixPathBuf>) -> Self {
self.inner = self.inner.workdir_path(workdir_path);
self
}
pub fn exec_path(
self,
exec_path: impl Into<Utf8UnixPathBuf>,
) -> MicroVmBuilder<R, Utf8UnixPathBuf> {
MicroVmBuilder {
inner: self.inner.exec_path(exec_path),
}
}
pub fn args<'a>(mut self, args: impl IntoIterator<Item = &'a str>) -> Self {
self.inner = self.inner.args(args);
self
}
pub fn env(mut self, env: impl IntoIterator<Item = EnvPair>) -> Self {
self.inner = self.inner.env(env);
self
}
pub fn console_output(mut self, console_output: impl Into<Utf8UnixPathBuf>) -> Self {
self.inner = self.inner.console_output(console_output);
self
}
}
impl MicroVmConfigBuilder<Rootfs, Utf8UnixPathBuf> {
pub fn build(self) -> MicroVmConfig {
MicroVmConfig {
log_level: self.log_level,
rootfs: self.rootfs,
num_vcpus: self.num_vcpus,
memory_mib: self.memory_mib,
mapped_dirs: self.mapped_dirs,
port_map: self.port_map,
scope: self.scope,
ip: self.ip,
subnet: self.subnet,
rlimits: self.rlimits,
workdir_path: self.workdir_path,
exec_path: self.exec_path,
args: self.args,
env: self.env,
console_output: self.console_output,
}
}
}
impl MicroVmBuilder<Rootfs, Utf8UnixPathBuf> {
pub fn build(self) -> MicrosandboxResult<MicroVm> {
MicroVm::from_config(MicroVmConfig {
log_level: self.inner.log_level,
rootfs: self.inner.rootfs,
num_vcpus: self.inner.num_vcpus,
memory_mib: self.inner.memory_mib,
mapped_dirs: self.inner.mapped_dirs,
port_map: self.inner.port_map,
scope: self.inner.scope,
ip: self.inner.ip,
subnet: self.inner.subnet,
rlimits: self.inner.rlimits,
workdir_path: self.inner.workdir_path,
exec_path: self.inner.exec_path,
args: self.inner.args,
env: self.inner.env,
console_output: self.inner.console_output,
})
}
}
impl Default for MicroVmConfigBuilder<(), ()> {
fn default() -> Self {
Self {
log_level: LogLevel::default(),
rootfs: (),
num_vcpus: DEFAULT_NUM_VCPUS,
memory_mib: DEFAULT_MEMORY_MIB,
mapped_dirs: vec![],
port_map: vec![],
scope: NetworkScope::default(),
ip: None,
subnet: None,
rlimits: vec![],
workdir_path: None,
exec_path: (),
args: vec![],
env: vec![],
console_output: None,
}
}
}
impl Default for MicroVmBuilder<(), ()> {
fn default() -> Self {
Self {
inner: MicroVmConfigBuilder::default(),
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[test]
fn test_microvm_builder() -> anyhow::Result<()> {
let rootfs = Rootfs::Overlayfs(vec![PathBuf::from("/tmp")]);
let workdir_path = "/workdir";
let exec_path = "/bin/example";
let builder = MicroVmBuilder::default()
.log_level(LogLevel::Debug)
.rootfs(rootfs.clone())
.num_vcpus(2)
.memory_mib(1024)
.mapped_dirs(["/guest/mount:/host/mount".parse()?])
.port_map(["8080:80".parse()?])
.rlimits(["RLIMIT_NOFILE=1024:1024".parse()?])
.workdir_path(workdir_path)
.exec_path(exec_path)
.args(["arg1", "arg2"])
.env(["KEY1=VALUE1".parse()?, "KEY2=VALUE2".parse()?])
.console_output("/tmp/console.log");
assert_eq!(builder.inner.log_level, LogLevel::Debug);
assert_eq!(builder.inner.rootfs, rootfs);
assert_eq!(builder.inner.num_vcpus, 2);
assert_eq!(builder.inner.memory_mib, 1024);
assert_eq!(
builder.inner.mapped_dirs,
["/guest/mount:/host/mount".parse()?]
);
assert_eq!(builder.inner.port_map, ["8080:80".parse()?]);
assert_eq!(builder.inner.rlimits, ["RLIMIT_NOFILE=1024:1024".parse()?]);
assert_eq!(
builder.inner.workdir_path,
Some(Utf8UnixPathBuf::from(workdir_path))
);
assert_eq!(builder.inner.exec_path, Utf8UnixPathBuf::from(exec_path));
assert_eq!(builder.inner.args, ["arg1", "arg2"]);
assert_eq!(
builder.inner.env,
["KEY1=VALUE1".parse()?, "KEY2=VALUE2".parse()?]
);
assert_eq!(
builder.inner.console_output,
Some(Utf8UnixPathBuf::from("/tmp/console.log"))
);
Ok(())
}
#[test]
fn test_microvm_builder_minimal() -> anyhow::Result<()> {
let rootfs = Rootfs::Native(PathBuf::from("/tmp"));
let memory_mib = 1024;
let builder = MicroVmBuilder::default()
.rootfs(rootfs.clone())
.exec_path("/bin/echo");
assert_eq!(builder.inner.rootfs, rootfs);
assert_eq!(builder.inner.memory_mib, memory_mib);
assert_eq!(builder.inner.log_level, LogLevel::default());
assert_eq!(builder.inner.num_vcpus, DEFAULT_NUM_VCPUS);
assert_eq!(builder.inner.memory_mib, DEFAULT_MEMORY_MIB);
assert!(builder.inner.mapped_dirs.is_empty());
assert!(builder.inner.port_map.is_empty());
assert!(builder.inner.rlimits.is_empty());
assert_eq!(builder.inner.workdir_path, None);
assert_eq!(builder.inner.exec_path, Utf8UnixPathBuf::from("/bin/echo"));
assert!(builder.inner.args.is_empty());
assert!(builder.inner.env.is_empty());
assert_eq!(builder.inner.console_output, None);
Ok(())
}
}