use std::collections::HashMap;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use tokio::sync::oneshot;
use super::fake_prepared::Preparer;
use super::fake_staging::StagingArea;
use super::fake_vm::{FakeVm, VmInner};
use super::fake_vsock::Inbound;
use super::lock;
use crate::capability::{Adopt, CheckpointImage, Prepare, PreparedVm as _};
use crate::driver::{
DriverCapabilities, ExitStatus, NestedVirt, RestoreSpec, ShutdownMode, VmDriver, VmHandle,
VmRecord, VmState,
};
use crate::error::{Error, Result};
use crate::spec::{IsolationSpec, VmId, VmSpec};
#[derive(Debug, Default)]
pub(super) struct Knobs {
pub(super) fail_boot: AtomicBool,
pub(super) fail_checkpoint: AtomicBool,
pub(super) freeze_checkpoint: AtomicBool,
}
#[derive(Clone)]
pub struct FakeDriver {
inner: Arc<DriverInner>,
adopter: Adopter,
preparer: Preparer,
}
pub(super) const NAME: &str = "fake";
const FIRST_PID: u32 = 100_000;
const SIGKILL: i32 = 9;
pub(super) struct DriverInner {
pub(super) caps: DriverCapabilities,
pub(super) knobs: Arc<Knobs>,
next_pid: AtomicU32,
jailed_id_budget: Option<usize>,
vms: Mutex<HashMap<VmId, Arc<VmInner>>>,
discarded_areas: Mutex<Vec<(VmId, IsolationSpec)>>,
pub(super) park_boot: Mutex<Option<oneshot::Sender<()>>>,
pub(super) discarded_processes: Mutex<Vec<VmId>>,
}
#[derive(Debug)]
pub struct FakeDriverBuilder {
caps: DriverCapabilities,
knobs: Knobs,
jailed_id_budget: Option<usize>,
}
impl FakeDriverBuilder {
pub fn capabilities(mut self, caps: DriverCapabilities) -> Self {
self.caps = caps;
self
}
pub fn jailed_id_budget(mut self, budget: usize) -> Self {
self.jailed_id_budget = Some(budget);
self
}
pub fn build(self) -> FakeDriver {
let inner = Arc::new(DriverInner {
caps: self.caps,
knobs: Arc::new(self.knobs),
next_pid: AtomicU32::new(FIRST_PID),
jailed_id_budget: self.jailed_id_budget,
vms: Mutex::new(HashMap::new()),
discarded_areas: Mutex::new(Vec::new()),
park_boot: Mutex::new(None),
discarded_processes: Mutex::new(Vec::new()),
});
FakeDriver {
adopter: Adopter(Arc::clone(&inner)),
preparer: Preparer(Arc::clone(&inner)),
inner,
}
}
}
impl FakeDriver {
pub fn new() -> Self {
Self::builder().build()
}
pub fn builder() -> FakeDriverBuilder {
FakeDriverBuilder {
caps: DriverCapabilities {
vsock: true,
vsock_listen: true,
checkpoint: true,
diff_checkpoint: true,
adopt: true,
prepare: true,
staging: true,
balloon: true,
console: true,
debug: true,
nested_virt: NestedVirt::unsupported("the fake driver runs no hypervisor"),
},
knobs: Knobs::default(),
jailed_id_budget: None,
}
}
pub fn guest_dial(&self, vm: &VmId, port: u32) -> Result<UnixStream> {
let vm = self.inner.live(vm)?;
let (host, guest) = UnixStream::pair()?;
vm.push_inbound(port, host);
Ok(guest)
}
pub fn fail_next_boot(&self) -> &Self {
self.inner.knobs.fail_boot.store(true, Ordering::Release);
self
}
#[must_use = "the receiver is how a test learns a boot reached the park"]
pub fn park_next_boot(&self) -> oneshot::Receiver<()> {
let (reached, park) = oneshot::channel();
*lock(&self.inner.park_boot) = Some(reached);
park
}
pub fn discarded_processes(&self) -> Vec<VmId> {
lock(&self.inner.discarded_processes).clone()
}
pub fn fail_next_checkpoint(&self) -> &Self {
self.inner
.knobs
.fail_checkpoint
.store(true, Ordering::Release);
self
}
pub fn freeze_next_checkpoint(&self) -> &Self {
self.inner
.knobs
.freeze_checkpoint
.store(true, Ordering::Release);
self
}
pub fn shutdowns(&self, vm: &VmId) -> Vec<ShutdownMode> {
self.inner
.registered(vm)
.map(|vm| vm.shutdowns())
.unwrap_or_default()
}
pub fn kill(&self, vm: &VmId) -> Option<ExitStatus> {
self.inner
.registered(vm)
.map(|vm| vm.exit(ExitStatus::signaled(SIGKILL)))
}
pub fn restored_vms(&self) -> Vec<VmId> {
self.inner.filtered(VmInner::is_restored)
}
pub fn owned_vms(&self) -> Vec<VmId> {
self.inner.owned_vms()
}
pub fn discarded_areas(&self) -> Vec<(VmId, IsolationSpec)> {
lock(&self.inner.discarded_areas).clone()
}
pub fn push_console(&self, vm: &VmId, bytes: &[u8]) -> Result<()> {
self.inner.live(vm)?.push_console(bytes);
Ok(())
}
}
impl Default for FakeDriver {
fn default() -> Self {
Self::new()
}
}
impl DriverInner {
pub(super) fn next_pid(&self) -> u32 {
self.next_pid.fetch_add(1, Ordering::Relaxed)
}
pub(super) fn register(
&self,
spec: VmSpec,
record: VmRecord,
balloon_target_bytes: u64,
inbound: Arc<Inbound>,
restored: bool,
) -> Result<Arc<VmInner>> {
let mut vms = lock(&self.vms);
if let Some(existing) = vms.get(&spec.id) {
match existing.state() {
VmState::Exited(_) => {}
state => {
return Err(Error::WrongState {
id: spec.id.clone(),
state,
expected: "no vm with this id",
});
}
}
}
let vm = VmInner::new(
spec,
record,
self.caps.clone(),
Arc::clone(&self.knobs),
balloon_target_bytes,
inbound,
restored,
);
vms.insert(vm.id().clone(), Arc::clone(&vm));
Ok(vm)
}
fn owned_vms(&self) -> Vec<VmId> {
self.filtered(|vm| !matches!(vm.state(), VmState::Exited(_)) && vm.is_owned())
}
fn filtered(&self, keep: impl Fn(&VmInner) -> bool) -> Vec<VmId> {
let mut ids: Vec<VmId> = lock(&self.vms)
.values()
.filter(|vm| keep(vm))
.map(|vm| vm.id().clone())
.collect();
ids.sort();
ids
}
fn registered(&self, id: &VmId) -> Option<Arc<VmInner>> {
lock(&self.vms).get(id).map(Arc::clone)
}
fn live(&self, id: &VmId) -> Result<Arc<VmInner>> {
let mut vms = lock(&self.vms);
match vms.get(id) {
Some(vm) if !matches!(vm.state(), VmState::Exited(_)) => Ok(Arc::clone(vm)),
Some(_) => {
vms.remove(id);
Err(Error::NotFound(id.clone()))
}
None => Err(Error::NotFound(id.clone())),
}
}
}
#[async_trait]
impl VmDriver for FakeDriver {
fn name(&self) -> &'static str {
NAME
}
fn capabilities(&self) -> DriverCapabilities {
self.inner.caps.clone()
}
async fn boot(&self, spec: VmSpec, runtime_dir: &Path) -> Result<Box<dyn VmHandle>> {
self.preparer
.prepare_sync(&spec.id, &spec.isolation, runtime_dir)
.boot(spec)
.await
}
async fn restore(
&self,
image: &CheckpointImage,
spec: RestoreSpec,
runtime_dir: &Path,
) -> Result<Box<dyn VmHandle>> {
self.preparer
.prepare_sync(&spec.id, &spec.isolation, runtime_dir)
.restore(image, spec)
.await
}
fn id_budget(&self, isolation: &IsolationSpec) -> Option<usize> {
match isolation {
IsolationSpec::Jailer { .. } => self.inner.jailed_id_budget,
IsolationSpec::None => None,
}
}
fn adopt(&self) -> Option<&dyn Adopt> {
self.inner.caps.adopt.then_some(&self.adopter)
}
fn prepare(&self) -> Option<&dyn Prepare> {
self.inner.caps.prepare.then_some(&self.preparer)
}
}
#[derive(Clone)]
struct Adopter(Arc<DriverInner>);
#[async_trait]
impl Adopt for Adopter {
async fn adopt(&self, record: &VmRecord) -> Result<Option<Box<dyn VmHandle>>> {
let vm = match self.0.live(&record.id) {
Ok(vm) => vm,
Err(Error::NotFound(_)) => return Ok(None),
Err(other) => return Err(other),
};
if vm.is_owned() {
return Err(Error::Driver {
driver: NAME,
message: format!("vm {} is still owned by a live handle", record.id),
source: None,
});
}
Ok(Some(Box::new(FakeVm::new(vm))))
}
async fn discard_area(&self, record: &VmRecord, isolation: &IsolationSpec) -> Result<()> {
if let Some(vm) = self.0.registered(&record.id) {
let state = vm.state();
if !matches!(state, VmState::Exited(_)) {
return Err(Error::WrongState {
id: record.id.clone(),
state,
expected: "a vm that is gone",
});
}
}
lock(&self.0.discarded_areas).push((record.id.clone(), isolation.clone()));
StagingArea::new(&record.runtime_dir).remove().await
}
}
#[cfg(test)]
mod tests;