use std::collections::HashSet;
use crate::sandbox::ComputerState;
use crate::sandbox::record::PersistPhase;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JournalEvidence {
Swept,
Unjournaled,
Unchecked,
Adopted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecoveryAction {
LeaveResumable,
Fail,
Reinstate(ComputerState),
FinishRemove,
RefuseUnjournaled,
RefuseAdopted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::sandbox) enum SweepAction {
DropStaleJournal,
Reclaim,
}
pub fn plan(phase: PersistPhase, evidence: JournalEvidence) -> RecoveryAction {
match (phase, evidence) {
(PersistPhase::Ready, JournalEvidence::Adopted) => {
RecoveryAction::Reinstate(ComputerState::Ready)
}
(_, JournalEvidence::Adopted) => RecoveryAction::RefuseAdopted,
(PersistPhase::Creating, _) => RecoveryAction::LeaveResumable,
(
PersistPhase::Starting | PersistPhase::Ready | PersistPhase::Stopping,
JournalEvidence::Unjournaled,
) => RecoveryAction::RefuseUnjournaled,
(PersistPhase::Starting | PersistPhase::Ready | PersistPhase::Stopping, _) => {
RecoveryAction::Fail
}
(PersistPhase::Pausing | PersistPhase::Resuming, _) => RecoveryAction::Fail,
(PersistPhase::Paused, _) => RecoveryAction::Reinstate(ComputerState::Paused),
(PersistPhase::Stopped, _) => RecoveryAction::Reinstate(ComputerState::Stopped),
(PersistPhase::Failed, _) => RecoveryAction::Reinstate(ComputerState::Failed),
(PersistPhase::Removing, _) => RecoveryAction::FinishRemove,
}
}
pub(in crate::sandbox) fn retained_ids<'a>(
records: impl IntoIterator<Item = (&'a str, PersistPhase)>,
) -> HashSet<String> {
records
.into_iter()
.filter(|(_, phase)| *phase == PersistPhase::Paused)
.map(|(id, _)| id.to_owned())
.collect()
}
pub(in crate::sandbox) fn sweep_action(id: &str, retained: &HashSet<String>) -> SweepAction {
if retained.contains(id) {
SweepAction::DropStaleJournal
} else {
SweepAction::Reclaim
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_durable_phase_has_one_recovery_action() {
let table = [
(
PersistPhase::Creating,
RecoveryAction::LeaveResumable,
RecoveryAction::LeaveResumable,
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Starting,
RecoveryAction::Fail,
RecoveryAction::RefuseUnjournaled,
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Ready,
RecoveryAction::Fail,
RecoveryAction::RefuseUnjournaled,
RecoveryAction::Reinstate(ComputerState::Ready),
),
(
PersistPhase::Stopping,
RecoveryAction::Fail,
RecoveryAction::RefuseUnjournaled,
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Stopped,
RecoveryAction::Reinstate(ComputerState::Stopped),
RecoveryAction::Reinstate(ComputerState::Stopped),
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Failed,
RecoveryAction::Reinstate(ComputerState::Failed),
RecoveryAction::Reinstate(ComputerState::Failed),
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Removing,
RecoveryAction::FinishRemove,
RecoveryAction::FinishRemove,
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Pausing,
RecoveryAction::Fail,
RecoveryAction::Fail,
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Paused,
RecoveryAction::Reinstate(ComputerState::Paused),
RecoveryAction::Reinstate(ComputerState::Paused),
RecoveryAction::RefuseAdopted,
),
(
PersistPhase::Resuming,
RecoveryAction::Fail,
RecoveryAction::Fail,
RecoveryAction::RefuseAdopted,
),
];
assert_eq!(table.len(), 10, "every durable phase is covered");
for (phase, journaled, unjournaled, adopted) in table {
for evidence in [JournalEvidence::Swept, JournalEvidence::Unchecked] {
assert_eq!(plan(phase, evidence), journaled, "{phase:?} {evidence:?}");
}
assert_eq!(
plan(phase, JournalEvidence::Unjournaled),
unjournaled,
"{phase:?} unjournaled"
);
assert_eq!(
plan(phase, JournalEvidence::Adopted),
adopted,
"{phase:?} adopted"
);
}
}
#[test]
fn only_a_paused_sandbox_retains_its_disk_state() {
let retained = retained_ids([
("paused", PersistPhase::Paused),
("ready", PersistPhase::Ready),
("pausing", PersistPhase::Pausing),
("resuming", PersistPhase::Resuming),
("stopped", PersistPhase::Stopped),
]);
assert_eq!(retained, HashSet::from(["paused".to_owned()]));
}
#[test]
fn a_retained_sandboxs_journal_is_stale_and_every_other_one_is_an_orphan() {
let retained = HashSet::from(["paused".to_owned()]);
assert_eq!(
sweep_action("paused", &retained),
SweepAction::DropStaleJournal
);
assert_eq!(sweep_action("live", &retained), SweepAction::Reclaim);
assert_eq!(sweep_action("pool-1f2e", &retained), SweepAction::Reclaim);
}
}