1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! What is left of the manager's teardown once the computer's actor owns it.
//!
//! `remove_sandbox_impl` and its parts — the busy gate, the bounded wait on a
//! boot's resource handoff, the epoch-stamped expiry retry, the `Arc::ptr_eq`
//! generation guard — are the actor's now: `Effect::AbortInflight` plus the
//! machine's remove arms, tested in `crate::lifecycle::tests`. The release
//! bodies themselves live in `crate::lifecycle::tasks::release`.
#[allow(unused_imports, reason = "the teardown surface is test-only now")]
use super::*;
#[cfg(test)]
mod tests {
use arcbox_vm_driver::testkit::FakeDriver;
use super::*;
use crate::snapshot_cow::{CowOptions, CowTestProbe};
/// A forced Remove of a boot wedged with its resources already handed
/// over must take both of them: the VMM the crash journal names, and
/// the dm-snapshot overlay the guest was going to run on.
///
/// The wedge is the driver's `boot` parking
/// ([`FakeDriver::park_next_boot`]) — after the prepared VMM is
/// journalled and the CoW staged, which is the window where a Remove
/// can strand either. That the discard the release issues really ends
/// a VMM process is the driver contract's
/// (`discard_kills_a_prepared_vm`, run against Firecracker in
/// `arcbox-fc-driver`'s `tests/contract.rs`); what this test owns is
/// that the release issues it at all, and drops the overlay with it.
#[tokio::test]
async fn force_remove_tears_down_cow_after_blocked_boot() {
let data_dir = tempfile::tempdir().unwrap();
let mut config = RuntimeConfig::default();
config.firecracker.data_dir = data_dir.path().to_string_lossy().into_owned();
config.defaults.kernel = data_dir
.path()
.join("kernel")
.to_string_lossy()
.into_owned();
config.defaults.rootfs = data_dir
.path()
.join("rootfs")
.to_string_lossy()
.into_owned();
let cow_probe = Arc::new(CowTestProbe::default());
let driver = FakeDriver::new();
let manager = SandboxManager::new(
config.clone(),
crate::NodeEnvironment {
driver: Arc::new(driver.clone()),
cow_manager: Arc::new(
CowManager::new_with_test_probe(
CowOptions::new(&config.firecracker.data_dir),
Arc::clone(&cow_probe),
)
.unwrap(),
),
..crate::testkit::fake_environment(&config).unwrap()
},
)
.unwrap();
manager.await_reconcile().await.unwrap();
let boot_parked = driver.park_next_boot();
let (id, _) = manager
.create_sandbox_keyed(
SandboxSpec {
id: Some("job".into()),
network: SandboxNetworkSpec {
mode: "none".into(),
},
..Default::default()
},
"create-key",
)
.await
.unwrap();
tokio::time::timeout(Duration::from_secs(5), boot_parked)
.await
.expect("boot must reach the parked driver call")
.unwrap();
assert_eq!(cow_probe.setup_count(), 1);
// The crash journal is what a restart would reclaim this VMM
// through, and it is written before the CoW is staged — so a
// journalled pid plus a staged overlay is the state the removal has
// to preempt without stranding either.
let vm_dir = data_dir.path().join("sandboxes/job");
let state: serde_json::Value =
serde_json::from_slice(&std::fs::read(vm_dir.join("state.json")).unwrap()).unwrap();
assert!(state["pid"].as_u64().is_some(), "the vmm pid is journalled");
tokio::time::timeout(Duration::from_secs(5), manager.remove_sandbox(&id, true))
.await
.expect("force removal must cancel the parked boot")
.unwrap();
assert_eq!(
driver.discarded_processes(),
[arcbox_vm_driver::VmId::new("job").unwrap()],
"the journalled VMM must be discarded, not left to a restart"
);
assert_eq!(cow_probe.teardown_count(), 1);
assert!(manager.records.load(&id).unwrap().is_none());
assert!(!vm_dir.exists());
}
}