mod tasks;
mod types;
pub(crate) use crate::litebox::box_impl::LiveState;
use crate::litebox::BoxStatus;
use crate::litebox::config::BoxConfig;
use crate::metrics::BoxMetricsStorage;
use crate::pipeline::{
BoxedTask, ExecutionPlan, PipelineBuilder, PipelineExecutor, PipelineMetrics, Stage,
};
use crate::runtime::rt_impl::SharedRuntimeImpl;
use crate::runtime::types::BoxState;
use boxlite_shared::errors::{BoxliteError, BoxliteResult};
use std::sync::Arc;
use tokio::sync::Mutex;
use tasks::{
ContainerRootfsTask, FilesystemTask, GuestConnectTask, GuestInitTask, GuestRootfsTask, InitCtx,
VmmAttachTask, VmmSpawnTask,
};
use types::InitPipelineContext;
fn get_execution_plan(status: BoxStatus) -> ExecutionPlan<InitCtx> {
let stages: Vec<Stage<BoxedTask<InitCtx>>> = match status {
BoxStatus::Configured => vec![
Stage::sequential(vec![Box::new(FilesystemTask)]),
Stage::parallel(vec![
Box::new(ContainerRootfsTask),
Box::new(GuestRootfsTask),
]),
Stage::sequential(vec![Box::new(VmmSpawnTask)]),
Stage::sequential(vec![Box::new(GuestConnectTask)]),
Stage::sequential(vec![Box::new(GuestInitTask)]),
],
BoxStatus::Stopped => vec![
Stage::sequential(vec![Box::new(FilesystemTask)]),
Stage::parallel(vec![
Box::new(ContainerRootfsTask),
Box::new(GuestRootfsTask),
]),
Stage::sequential(vec![Box::new(VmmSpawnTask)]),
Stage::sequential(vec![Box::new(GuestConnectTask)]),
Stage::sequential(vec![Box::new(GuestInitTask)]),
],
BoxStatus::Running => vec![
Stage::sequential(vec![Box::new(VmmAttachTask)]),
Stage::sequential(vec![Box::new(GuestConnectTask)]),
],
_ => panic!("Invalid BoxStatus for initialization: {:?}", status),
};
ExecutionPlan::new(stages)
}
fn box_metrics_from_pipeline(pipeline_metrics: &PipelineMetrics) -> BoxMetricsStorage {
let mut metrics = BoxMetricsStorage::new();
if let Some(duration_ms) = pipeline_metrics.task_duration_ms("filesystem_setup") {
metrics.set_stage_filesystem_setup(duration_ms);
}
if let Some(duration_ms) = pipeline_metrics.task_duration_ms("container_rootfs_prep") {
metrics.set_stage_image_prepare(duration_ms);
}
if let Some(duration_ms) = pipeline_metrics.task_duration_ms("guest_rootfs_init") {
metrics.set_stage_guest_rootfs(duration_ms);
}
if let Some(duration_ms) = pipeline_metrics.task_duration_ms("vmm_spawn") {
metrics.set_stage_box_spawn(duration_ms);
}
if let Some(duration_ms) = pipeline_metrics.task_duration_ms("vmm_attach") {
metrics.set_stage_box_spawn(duration_ms);
}
if let Some(_duration_ms) = pipeline_metrics.task_duration_ms("guest_connect") {
}
if let Some(duration_ms) = pipeline_metrics.task_duration_ms("guest_init") {
metrics.set_stage_container_init(duration_ms);
}
metrics
}
pub(crate) struct BoxBuilder {
runtime: SharedRuntimeImpl,
config: BoxConfig,
state: BoxState,
}
impl BoxBuilder {
pub(crate) fn new(
runtime: SharedRuntimeImpl,
config: BoxConfig,
state: BoxState,
) -> BoxliteResult<Self> {
let options = &config.options;
options.sanitize()?;
Ok(Self {
runtime,
config,
state,
})
}
pub(crate) async fn build(self) -> BoxliteResult<(LiveState, types::CleanupGuard)> {
use std::time::Instant;
let total_start = Instant::now();
let BoxBuilder {
runtime,
config,
state,
} = self;
let status = state.status;
let reuse_rootfs = status == BoxStatus::Stopped;
let skip_guest_wait = status == BoxStatus::Running;
let ctx = InitPipelineContext::new(config, runtime.clone(), reuse_rootfs, skip_guest_wait);
let ctx = Arc::new(Mutex::new(ctx));
let plan = get_execution_plan(status);
let pipeline = PipelineBuilder::from_plan(plan);
let pipeline_metrics = PipelineExecutor::execute(pipeline, Arc::clone(&ctx)).await?;
let mut ctx = ctx.lock().await;
let total_create_duration_ms = total_start.elapsed().as_millis();
let handler = ctx
.guard
.take_handler()
.ok_or_else(|| BoxliteError::Internal("handler was not set".into()))?;
let mut metrics = box_metrics_from_pipeline(&pipeline_metrics);
metrics.set_total_create_duration(total_create_duration_ms);
metrics.log_init_stages();
let guest_session = ctx
.guest_session
.take()
.ok_or_else(|| BoxliteError::Internal("guest_connect task must run first".into()))?;
let (container_disk, guest_disk) = if status == BoxStatus::Running {
use crate::disk::DiskFormat;
use crate::disk::constants::filenames;
let disk = crate::disk::Disk::new(
ctx.config.box_home.join(filenames::CONTAINER_DISK),
DiskFormat::Qcow2,
true,
);
(disk, None)
} else {
let container_disk = ctx
.container_disk
.take()
.ok_or_else(|| BoxliteError::Internal("rootfs task must run first".into()))?;
(container_disk, ctx.guest_disk.take())
};
#[cfg(target_os = "linux")]
let bind_mount = ctx.bind_mount.take();
let mut placeholder = types::CleanupGuard::new(ctx.runtime.clone(), ctx.config.id.clone());
placeholder.disarm();
let guard = std::mem::replace(&mut ctx.guard, placeholder);
let live_state = LiveState::new(
handler,
guest_session,
metrics,
container_disk,
guest_disk,
#[cfg(target_os = "linux")]
bind_mount,
);
Ok((live_state, guard))
}
}