use super::*;
use crate::lifecycle::actor::PauseReason;
pub const PAUSE_SNAPSHOT_NAME: &str = "arcbox-pause";
pub const PAUSED_ROOTFS_FILE: &str = "paused-rootfs.ext4";
pub mod reason {
pub const PAUSE: &str = "pause";
pub const IDLE_TIMEOUT: &str = "idle_timeout";
pub const RESUME: &str = "resume";
pub const AUTO_RESUME: &str = "auto_resume";
}
pub fn delete_pause_snapshots(
snapshots: &crate::snapshot::SnapshotCatalog,
sandbox_id: &str,
) -> Result<()> {
for info in snapshots.list(sandbox_id)? {
if info.name.as_deref() == Some(PAUSE_SNAPSHOT_NAME) {
snapshots.delete_by_id(&info.id)?;
}
}
Ok(())
}
pub struct ResumedRuntime {
pub prepared: Arc<dyn PreparedVm>,
pub handle: Arc<dyn VmHandle>,
pub network: Option<NetworkLease>,
pub net_identity: Option<NetworkIdentity>,
pub cow_handle: Option<CowHandle>,
}
pub struct ResumeFailure {
pub error: VmmError,
pub unwound: bool,
}
impl SandboxManager {
pub async fn pause_sandbox(&self, id: &SandboxId) -> Result<()> {
self.pause_sandbox_with_reason(id, PauseReason::Requested)
.await
}
pub(super) async fn pause_sandbox_with_reason(
&self,
id: &SandboxId,
reason: PauseReason,
) -> Result<()> {
self.await_reconcile().await?;
let computer = self.computer(id)?;
if computer.snapshot.borrow().state == SandboxState::Ready
&& self.config.firecracker.jailer.is_none()
{
return Err(VmmError::Config(
"sandbox pause requires jailer isolation; direct mode cannot resume".into(),
));
}
computer
.mailbox
.ask(id, |reply| Command::Pause { reason, reply })
.await
}
pub async fn resume_sandbox(&self, id: &SandboxId, resume_reason: &str) -> Result<String> {
self.await_reconcile().await?;
let computer = self.computer(id)?;
computer
.mailbox
.ask(id, |reply| Command::Resume {
reason: resume_reason.to_owned(),
reply,
})
.await?;
Ok(computer
.snapshot
.borrow()
.lease
.as_ref()
.map(|lease| lease.ip.to_string())
.unwrap_or_default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::snapshot::{SnapshotCatalog, SnapshotDraft};
fn publish(catalog: &SnapshotCatalog, vm_id: &str, name: &str) -> String {
let pending = catalog.begin(vm_id).unwrap();
std::fs::write(pending.dir().join("vmstate"), b"vmstate").unwrap();
pending
.commit(SnapshotDraft {
name: Some(name.to_owned()),
labels: HashMap::new(),
snapshot_type: crate::config::SnapshotType::Full,
parent_id: None,
kernel_path: None,
rootfs_path: None,
net_invariant: false,
geometry: None,
format: super::checkpoint::CHECKPOINT_FORMAT.to_owned(),
})
.unwrap()
.id
}
#[test]
fn the_pause_sweep_deletes_every_reserved_checkpoint_and_nothing_else() {
let dir = tempfile::tempdir().unwrap();
let catalog = SnapshotCatalog::new(dir.path().to_str().unwrap());
let recorded = publish(&catalog, "box", PAUSE_SNAPSHOT_NAME);
let leaked = publish(&catalog, "box", PAUSE_SNAPSHOT_NAME);
let user = publish(&catalog, "box", "warm");
let other = publish(&catalog, "elsewhere", PAUSE_SNAPSHOT_NAME);
delete_pause_snapshots(&catalog, "box").unwrap();
assert!(catalog.find_by_id(&recorded).is_err());
assert!(catalog.find_by_id(&leaked).is_err());
assert!(catalog.find_by_id(&user).is_ok(), "a user checkpoint stays");
assert!(
catalog.find_by_id(&other).is_ok(),
"another computer's pause state is not this one's to delete"
);
}
}