use std::sync::Arc;
use chrono::Utc;
use tracing::{info, warn};
use super::{ComputerFlows, Launch};
use crate::agent::{GuestAgent, StartCommand};
use crate::error::{ComputerError, Result};
use crate::lifecycle::actor::{Command, Mailbox, WorkloadOutcome};
use crate::lifecycle::tasks::boot::{do_boot, wait_for_agent};
use crate::lifecycle::tasks::{TaskFailure, TaskResult};
use crate::sandbox::boot::run_ready_probe;
use crate::sandbox::warm::{WarmPublishTicket, publish_after_boot};
use crate::sandbox::workload::{WorkloadClaim, WorkloadSlot, start_run_workload};
use crate::sandbox::{ComputerId, ComputerSpec, ComputerState};
impl ComputerFlows {
pub(super) async fn boot_vm(
&self,
handed_off: tokio::sync::oneshot::Sender<()>,
) -> TaskResult<Arc<dyn GuestAgent>> {
let attachment = match self.take_attachment() {
Some(attachment) => attachment,
None => return Err(self.wrong_launch("boot")),
};
let attachment = attachment.into_inner();
let (spec, vm_dir) = {
let computer = self.computer.lock().unwrap();
(computer.spec.clone(), computer.vm_dir.clone())
};
let services = &self.services;
let outcome = do_boot(
&self.id,
&spec,
attachment.as_ref(),
&vm_dir,
services.driver.as_ref(),
services.agents.as_ref(),
&services.config,
&services.cow_manager,
&self.computer,
handed_off,
)
.await;
let (handle, ready_gate) = match outcome {
Ok(booted) => booted,
Err(failure) => {
let mut computer = self.computer.lock().unwrap();
if let Some(prepared) = failure.prepared {
computer.prepared = Some(prepared);
}
if let Some(cow_handle) = failure.cow_handle {
computer.cow_handle = Some(cow_handle);
}
return Err(TaskFailure::recoverable(failure.error));
}
};
let agent = wait_for_agent(
&self.id,
&handle,
attachment.as_ref(),
ready_gate,
services.agents.as_ref(),
)
.await
.map_err(TaskFailure::recoverable)?;
let mut computer = self.computer.lock().unwrap();
computer.handle = Some(handle);
computer.net_identity = attachment.map(|net| net.identity);
computer.ready_at = Some(Utc::now());
drop(computer);
Ok(agent)
}
pub(super) async fn run_gate(&self) -> TaskResult {
let spec = self.computer.lock().unwrap().spec.clone();
let agent = self.agent().map_err(TaskFailure::recoverable)?;
if let Some(ticket) = self.take_warm_ticket() {
let services = &self.services;
publish_after_boot(
&self.id,
&ticket,
&self.computer,
&services.cow_manager,
ComputerState::Starting,
)
.await
.map_err(TaskFailure::frozen)?;
}
let started = self.start_initial_cmd(&spec, agent.as_ref()).await;
if let Some(probe) = spec.ready_probe.clone() {
run_ready_probe(&probe, agent.as_ref()).await.map_err(|e| {
TaskFailure::recoverable(ComputerError::FailedPrecondition(format!(
"ready probe failed: {e}"
)))
})?;
}
if !started {
self.start_initial_cmd(&spec, agent.as_ref()).await;
}
Ok(())
}
pub(super) async fn start_initial_cmd(
&self,
spec: &ComputerSpec,
agent: &dyn GuestAgent,
) -> bool {
if spec.cmd.is_empty() {
return false;
}
let start = StartCommand {
cmd: spec.cmd.clone(),
env: spec.env.clone(),
working_dir: spec.working_dir.clone(),
user: spec.user.clone(),
tty: false,
tty_width: 80,
tty_height: 24,
timeout_seconds: 0,
};
let slot = match self.workload_slot() {
Ok(slot) => slot,
Err(error) => {
warn!(sandbox_id = %self.id, %error, "the initial cmd found no computer to claim");
return false;
}
};
match start_run_workload(agent, start, slot, WorkloadClaim::Initial).await {
Ok(mut rx) => {
info!(sandbox_id = %self.id, "initial cmd started");
tokio::spawn(async move { while rx.recv().await.is_some() {} });
true
}
Err(error) => {
warn!(sandbox_id = %self.id, %error, "initial cmd failed to start");
false
}
}
}
fn take_warm_ticket(&self) -> Option<WarmPublishTicket> {
match &mut *self.launch.lock().unwrap() {
Launch::Boot(launch) => launch.warm_publish.take(),
_ => None,
}
}
fn take_attachment(&self) -> Option<Attachment> {
match &mut *self.launch.lock().unwrap() {
Launch::Boot(launch) => Some(Attachment(launch.attachment.take())),
_ => None,
}
}
pub fn workload_slot(&self) -> Result<Arc<dyn WorkloadSlot>> {
Ok(Arc::new(ActorSlot {
id: self.id.clone(),
mailbox: self.mailbox()?,
}))
}
}
struct Attachment(Option<crate::sandbox::NetworkAttachment>);
impl Attachment {
fn into_inner(self) -> Option<crate::sandbox::NetworkAttachment> {
self.0
}
}
pub struct ActorSlot {
pub id: ComputerId,
pub mailbox: Mailbox,
}
#[async_trait::async_trait]
impl WorkloadSlot for ActorSlot {
async fn claim(&self, claim: WorkloadClaim) -> Result<()> {
self.mailbox
.ask(&self.id, |reply| Command::ClaimWorkload { claim, reply })
.await
}
fn release(&self) {
self.mailbox.tell(Command::ReleaseWorkload);
}
fn exited(&self, outcome: WorkloadOutcome) {
self.mailbox.tell(Command::WorkloadExited { outcome });
}
}