use std::{net::Ipv4Addr, path::PathBuf};
use typed_path::Utf8UnixPathBuf;
use crate::{
config::{EnvPair, PathPair, PortPair, DEFAULT_NUM_VCPUS},
MonocoreResult,
};
use super::{LinuxRlimit, LogLevel, MicroVm, MicroVmConfig};
pub struct MicroVmConfigBuilder<RootPath, RamMib> {
log_level: LogLevel,
root_path: RootPath,
num_vcpus: Option<u8>,
ram_mib: RamMib,
mapped_dirs: Vec<PathPair>,
port_map: Vec<PortPair>,
rlimits: Vec<LinuxRlimit>,
workdir_path: Option<Utf8UnixPathBuf>,
exec_path: Option<Utf8UnixPathBuf>,
args: Vec<String>,
env: Vec<EnvPair>,
console_output: Option<Utf8UnixPathBuf>,
assigned_ip: Option<Ipv4Addr>,
local_only: bool,
}
pub struct MicroVmBuilder<RootPath, RamMib> {
inner: MicroVmConfigBuilder<RootPath, RamMib>,
}
impl<RootPath, RamMib> MicroVmConfigBuilder<RootPath, RamMib> {
pub fn log_level(mut self, log_level: LogLevel) -> Self {
self.log_level = log_level;
self
}
pub fn root_path(self, root_path: impl Into<PathBuf>) -> MicroVmConfigBuilder<PathBuf, RamMib> {
MicroVmConfigBuilder {
log_level: self.log_level,
root_path: root_path.into(),
num_vcpus: self.num_vcpus,
ram_mib: self.ram_mib,
mapped_dirs: self.mapped_dirs,
port_map: self.port_map,
rlimits: self.rlimits,
workdir_path: self.workdir_path,
exec_path: self.exec_path,
args: self.args,
env: self.env,
console_output: self.console_output,
assigned_ip: self.assigned_ip,
local_only: self.local_only,
}
}
pub fn num_vcpus(mut self, num_vcpus: u8) -> Self {
self.num_vcpus = Some(num_vcpus);
self
}
pub fn ram_mib(self, ram_mib: u32) -> MicroVmConfigBuilder<RootPath, u32> {
MicroVmConfigBuilder {
log_level: self.log_level,
root_path: self.root_path,
num_vcpus: self.num_vcpus,
ram_mib,
mapped_dirs: self.mapped_dirs,
port_map: self.port_map,
rlimits: self.rlimits,
workdir_path: self.workdir_path,
exec_path: self.exec_path,
args: self.args,
env: self.env,
console_output: self.console_output,
assigned_ip: self.assigned_ip,
local_only: self.local_only,
}
}
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 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(mut self, exec_path: impl Into<Utf8UnixPathBuf>) -> Self {
self.exec_path = Some(exec_path.into());
self
}
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
}
pub fn assigned_ip(mut self, assigned_ip: Ipv4Addr) -> Self {
self.assigned_ip = Some(assigned_ip);
self
}
pub fn local_only(mut self, local_only: bool) -> Self {
self.local_only = local_only;
self
}
}
impl<RootPath, RamMib> MicroVmBuilder<RootPath, RamMib> {
pub fn log_level(mut self, log_level: LogLevel) -> Self {
self.inner = self.inner.log_level(log_level);
self
}
pub fn root_path(self, root_path: impl Into<PathBuf>) -> MicroVmBuilder<PathBuf, RamMib> {
MicroVmBuilder {
inner: self.inner.root_path(root_path),
}
}
pub fn num_vcpus(mut self, num_vcpus: u8) -> Self {
self.inner = self.inner.num_vcpus(num_vcpus);
self
}
pub fn ram_mib(self, ram_mib: u32) -> MicroVmBuilder<RootPath, u32> {
MicroVmBuilder {
inner: self.inner.ram_mib(ram_mib),
}
}
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 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(mut self, exec_path: impl Into<Utf8UnixPathBuf>) -> Self {
self.inner = self.inner.exec_path(exec_path);
self
}
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
}
pub fn assigned_ip(mut self, assigned_ip: Ipv4Addr) -> Self {
self.inner = self.inner.assigned_ip(assigned_ip);
self
}
pub fn local_only(mut self, local_only: bool) -> Self {
self.inner = self.inner.local_only(local_only);
self
}
}
impl MicroVmConfigBuilder<PathBuf, u32> {
pub fn build(self) -> MicroVmConfig {
MicroVmConfig {
log_level: self.log_level,
root_path: self.root_path,
num_vcpus: self.num_vcpus.unwrap_or(DEFAULT_NUM_VCPUS),
ram_mib: self.ram_mib,
mapped_dirs: self.mapped_dirs,
port_map: self.port_map,
rlimits: self.rlimits,
workdir_path: self.workdir_path,
exec_path: self.exec_path,
args: self.args,
env: self.env,
console_output: self.console_output,
assigned_ip: self.assigned_ip,
local_only: self.local_only,
}
}
}
impl MicroVmBuilder<PathBuf, u32> {
pub fn build(self) -> MonocoreResult<MicroVm> {
MicroVm::from_config(MicroVmConfig {
log_level: self.inner.log_level,
root_path: self.inner.root_path,
num_vcpus: self.inner.num_vcpus.unwrap_or(DEFAULT_NUM_VCPUS),
ram_mib: self.inner.ram_mib,
mapped_dirs: self.inner.mapped_dirs,
port_map: self.inner.port_map,
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,
assigned_ip: self.inner.assigned_ip,
local_only: self.inner.local_only,
})
}
}
impl Default for MicroVmConfigBuilder<(), ()> {
fn default() -> Self {
Self {
log_level: LogLevel::default(),
root_path: (),
num_vcpus: Some(DEFAULT_NUM_VCPUS),
ram_mib: (),
mapped_dirs: vec![],
port_map: vec![],
rlimits: vec![],
workdir_path: None,
exec_path: None,
args: vec![],
env: vec![],
console_output: None,
assigned_ip: None,
local_only: true,
}
}
}
impl Default for MicroVmBuilder<(), ()> {
fn default() -> Self {
Self {
inner: MicroVmConfigBuilder::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn test_microvm_builder() -> anyhow::Result<()> {
let root_path = "/tmp";
let workdir_path = "/workdir";
let exec_path = "/bin/example";
let assigned_ip = Ipv4Addr::new(10, 0, 2, 15);
let builder = MicroVmBuilder::default()
.log_level(LogLevel::Debug)
.root_path(root_path)
.num_vcpus(2)
.ram_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")
.assigned_ip(assigned_ip)
.local_only(true);
assert_eq!(builder.inner.log_level, LogLevel::Debug);
assert_eq!(builder.inner.root_path, PathBuf::from(root_path));
assert_eq!(builder.inner.num_vcpus, Some(2));
assert_eq!(builder.inner.ram_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,
Some(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"))
);
assert_eq!(builder.inner.assigned_ip, Some(assigned_ip));
assert!(builder.inner.local_only);
Ok(())
}
#[test]
fn test_microvm_builder_minimal() -> anyhow::Result<()> {
let root_path = "/tmp";
let ram_mib = 512;
let builder = MicroVmBuilder::default()
.root_path(root_path)
.ram_mib(ram_mib);
assert_eq!(builder.inner.root_path, PathBuf::from(root_path));
assert_eq!(builder.inner.ram_mib, ram_mib);
assert_eq!(builder.inner.log_level, LogLevel::default());
assert_eq!(builder.inner.num_vcpus, Some(DEFAULT_NUM_VCPUS));
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, None);
assert!(builder.inner.args.is_empty());
assert!(builder.inner.env.is_empty());
assert_eq!(builder.inner.console_output, None);
assert_eq!(builder.inner.assigned_ip, None);
assert!(builder.inner.local_only); Ok(())
}
#[test]
fn test_network_configuration() -> anyhow::Result<()> {
let root_path = "/tmp";
let assigned_ip = Ipv4Addr::new(10, 0, 2, 15);
let restricted_builder = MicroVmBuilder::default()
.root_path(root_path)
.ram_mib(512)
.assigned_ip(assigned_ip)
.local_only(true);
assert_eq!(restricted_builder.inner.assigned_ip, Some(assigned_ip));
assert!(restricted_builder.inner.local_only);
let unrestricted_builder = MicroVmBuilder::default()
.root_path(root_path)
.ram_mib(512)
.assigned_ip(assigned_ip)
.local_only(false);
assert_eq!(unrestricted_builder.inner.assigned_ip, Some(assigned_ip));
assert!(!unrestricted_builder.inner.local_only);
let no_ip_builder = MicroVmBuilder::default()
.root_path(root_path)
.ram_mib(512)
.local_only(true);
assert_eq!(no_ip_builder.inner.assigned_ip, None);
assert!(no_ip_builder.inner.local_only);
Ok(())
}
}