use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use derivative::Derivative;
use serde::{Deserialize, Serialize};
mod drive;
mod jailer;
mod machine;
pub mod network;
pub use drive::*;
pub use jailer::*;
pub use machine::*;
use uuid::Uuid;
use crate::Error;
const KERNEL_IMAGE_FILENAME: &str = "kernel";
#[derive(Debug)]
pub struct Config<'c> {
pub(crate) socket_path: Cow<'c, Path>,
log_path: Option<Cow<'c, Path>>,
log_fifo: Option<Cow<'c, Path>>,
log_level: Option<LogLevel>,
metrics_path: Option<Cow<'c, Path>>,
metrics_fifo: Option<Cow<'c, Path>>,
pub(crate) src_kernel_image_path: Cow<'c, Path>,
pub(crate) src_initrd_path: Option<Cow<'c, Path>>,
kernel_args: Option<Cow<'c, str>>,
pub(crate) drives: Vec<Drive<'c>>,
machine_cfg: Machine<'c>,
pub(crate) jailer_cfg: Option<Jailer<'c>>,
vm_id: Uuid,
net_ns: Option<Cow<'c, str>>,
network_interfaces: Vec<network::Interface<'c>>,
}
impl<'c> Config<'c> {
pub fn builder<P>(vm_id: Option<Uuid>, src_kernel_image_path: P) -> Builder<'c>
where
P: Into<Cow<'c, Path>>,
{
Builder(Self {
socket_path: Path::new("/run/firecracker.socket").into(),
log_path: None,
log_fifo: None,
log_level: None,
metrics_path: None,
metrics_fifo: None,
src_kernel_image_path: src_kernel_image_path.into(),
src_initrd_path: None,
kernel_args: None,
drives: Vec::new(),
machine_cfg: Machine::default(),
jailer_cfg: None,
vm_id: vm_id.unwrap_or_else(Uuid::new_v4),
net_ns: None,
network_interfaces: Vec::new(),
})
}
pub(crate) fn boot_source(&self) -> Result<BootSource, Error> {
let relative_kernel_image_path = Path::new("/").join(KERNEL_IMAGE_FILENAME);
let relative_initrd_path: Result<Option<PathBuf>, Error> =
match self.src_initrd_path.as_ref() {
Some(initrd_path) => {
let initrd_filename =
initrd_path.file_name().ok_or(Error::InvalidInitrdPath)?;
Ok(Some(Path::new("/").join(initrd_filename)))
}
None => Ok(None),
};
Ok(BootSource {
kernel_image_path: relative_kernel_image_path,
initrd_path: relative_initrd_path?,
boot_args: self.kernel_args.as_ref().map(AsRef::as_ref).map(Into::into),
})
}
pub fn socket_path(&self) -> &Path {
self.socket_path.as_ref()
}
pub fn host_socket_path(&self) -> PathBuf {
let socket_path = self.socket_path.as_ref();
let relative_path = socket_path.strip_prefix("/").unwrap_or(socket_path);
self.jailer().workspace_dir().join(relative_path)
}
pub fn log_path(&self) -> Option<&Path> {
self.log_path.as_ref().map(AsRef::as_ref)
}
pub fn log_fifo(&self) -> Option<&Path> {
self.log_fifo.as_ref().map(AsRef::as_ref)
}
pub fn metrics_path(&self) -> Option<&Path> {
self.metrics_path.as_ref().map(AsRef::as_ref)
}
pub fn metrics_fifo(&self) -> Option<&Path> {
self.metrics_fifo.as_ref().map(AsRef::as_ref)
}
pub fn src_kernel_image_path(&self) -> &Path {
self.src_kernel_image_path.as_ref()
}
pub fn kernel_image_path(&self) -> PathBuf {
self.jailer().workspace_dir().join(KERNEL_IMAGE_FILENAME)
}
pub fn src_initrd_path(&self) -> Option<&Path> {
self.src_initrd_path.as_ref().map(AsRef::as_ref)
}
pub fn initrd_path(&self) -> Result<Option<PathBuf>, Error> {
match self.src_initrd_path.as_ref() {
Some(initrd_path) => {
let initrd_filename = initrd_path
.file_name()
.ok_or(Error::InvalidInitrdPath)?
.to_owned();
Ok(Some(self.jailer().workspace_dir().join(&initrd_filename)))
}
None => Ok(None),
}
}
pub fn kernel_args(&self) -> Option<&str> {
self.kernel_args.as_ref().map(AsRef::as_ref)
}
pub fn drives(&self) -> &[Drive<'c>] {
&self.drives
}
pub fn machine_cfg(&self) -> &Machine<'c> {
&self.machine_cfg
}
pub fn jailer_cfg(&self) -> Option<&Jailer<'c>> {
self.jailer_cfg.as_ref()
}
pub fn vm_id(&self) -> &Uuid {
&self.vm_id
}
pub fn net_ns(&self) -> Option<&str> {
self.net_ns.as_ref().map(AsRef::as_ref)
}
pub fn network_interfaces(&self) -> &[network::Interface<'c>] {
&self.network_interfaces
}
pub(crate) fn jailer(&self) -> &Jailer {
self.jailer_cfg.as_ref().expect("no jailer config")
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BootSource<'b> {
pub kernel_image_path: PathBuf,
pub boot_args: Option<&'b str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initrd_path: Option<PathBuf>,
}
#[derive(Derivative)]
#[derivative(Debug, Default)]
pub enum LogLevel {
Error,
Warning,
#[derivative(Default)]
Info,
Debug,
}
#[derive(Debug)]
pub struct Builder<'c>(Config<'c>);
impl<'c> Builder<'c> {
pub fn socket_path<P>(mut self, socket_path: P) -> Self
where
P: Into<Cow<'c, Path>>,
{
self.0.socket_path = socket_path.into();
self
}
pub fn log_path<P>(mut self, log_path: P) -> Self
where
P: Into<Cow<'c, Path>>,
{
self.0.log_path = Some(log_path.into());
self
}
pub fn log_fifo<P>(mut self, log_fifo: P) -> Self
where
P: Into<Cow<'c, Path>>,
{
self.0.log_fifo = Some(log_fifo.into());
self
}
pub fn log_level(mut self, log_level: LogLevel) -> Self {
self.0.log_level = Some(log_level);
self
}
pub fn metrics_path<P>(mut self, metrics_path: P) -> Self
where
P: Into<Cow<'c, Path>>,
{
self.0.metrics_path = Some(metrics_path.into());
self
}
pub fn metrics_fifo<P>(mut self, metrics_fifo: P) -> Self
where
P: Into<Cow<'c, Path>>,
{
self.0.metrics_fifo = Some(metrics_fifo.into());
self
}
pub fn initrd_path<P>(mut self, initrd_path: P) -> Self
where
P: Into<Cow<'c, Path>>,
{
self.0.src_initrd_path = Some(initrd_path.into());
self
}
pub fn kernel_args<P>(mut self, kernel_args: P) -> Self
where
P: Into<Cow<'c, str>>,
{
self.0.kernel_args = Some(kernel_args.into());
self
}
pub fn add_drive<I, P>(self, drive_id: I, src_path: P) -> DriveBuilder<'c>
where
I: Into<Cow<'c, str>>,
P: Into<Cow<'c, Path>>,
{
DriveBuilder::new(self, drive_id, src_path)
}
pub fn machine_cfg(self) -> MachineBuilder<'c> {
MachineBuilder::new(self)
}
pub fn jailer_cfg(self) -> JailerBuilder<'c> {
JailerBuilder::new(self)
}
pub fn net_ns<N>(mut self, net_ns: N) -> Self
where
N: Into<Cow<'c, str>>,
{
self.0.net_ns = Some(net_ns.into());
self
}
pub fn add_network_interface(mut self, network_interface: network::Interface<'c>) -> Self {
self.0.network_interfaces.push(network_interface);
self
}
pub fn build(self) -> Config<'c> {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use Uuid;
#[test]
fn config_host_values() {
let id = Uuid::new_v4();
let config = Config::builder(Some(id), Path::new("/tmp/kernel.path"))
.jailer_cfg()
.chroot_base_dir(Path::new("/chroot"))
.exec_file(Path::new("/usr/bin/firecracker"))
.mode(JailerMode::Daemon)
.build()
.initrd_path(Path::new("/tmp/initrd.img"))
.add_drive("root", Path::new("/tmp/debian.ext4"))
.is_root_device(true)
.build()
.socket_path(Path::new("/firecracker.socket"))
.build();
assert_eq!(
config.src_initrd_path.as_ref().unwrap().as_os_str(),
"/tmp/initrd.img"
);
assert_eq!(
config
.initrd_path()
.unwrap()
.unwrap()
.as_os_str()
.to_string_lossy(),
format!("/chroot/firecracker/{}/root/initrd.img", id)
);
assert_eq!(
config.src_kernel_image_path.as_ref().as_os_str(),
"/tmp/kernel.path"
);
assert_eq!(
config.kernel_image_path().as_os_str().to_string_lossy(),
format!("/chroot/firecracker/{}/root/kernel", id)
);
assert_eq!(
config.socket_path.as_ref().as_os_str(),
"/firecracker.socket"
);
assert_eq!(
config.host_socket_path().as_os_str().to_string_lossy(),
format!("/chroot/firecracker/{}/root/firecracker.socket", id)
);
let boot_source = config.boot_source().unwrap();
assert_eq!(boot_source.boot_args, None);
assert_eq!(boot_source.kernel_image_path.as_os_str(), "/kernel");
assert_eq!(boot_source.initrd_path.unwrap().as_os_str(), "/initrd.img");
}
}