use super::RuntimeBootstrapError;
use crate::{
capability::{CommittedAllocations, ValidatedAllocations},
declaration::DeclarationSnapshot,
ledger::{LedgerCommitStore, stage_validated_generation},
policy::AllocationPolicy,
validation::validate_allocations,
};
use std::borrow::Cow;
pub fn prepare_commit<P>(
store: &mut LedgerCommitStore,
prior: crate::RecoveredLedger,
snapshot: DeclarationSnapshot,
policy: &P,
) -> Result<PendingBootstrapCommit, RuntimeBootstrapError<P::Error>>
where
P: AllocationPolicy,
{
let validated = validate_allocations(&prior, snapshot, policy)?;
let staged = stage_validated_generation(Cow::Owned(prior.into_ledger()), &validated)?;
store.commit_generation(&staged)?;
Ok(PendingBootstrapCommit {
validated,
generation: staged.current_generation(),
})
}
#[derive(Debug, Eq, PartialEq)]
#[must_use = "persist the owning ledger record, then confirm_persisted before opening allocations"]
pub struct PendingBootstrapCommit {
generation: u64,
validated: ValidatedAllocations,
}
impl PendingBootstrapCommit {
#[must_use]
pub fn confirm_persisted(self) -> CommittedAllocations {
self.validated.confirm_persisted(self.generation)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AllocationDeclaration, AllocationLedger, AllocationValidationError,
GenericAllocationPolicy, MemoryManagerSlot, SchemaMetadata,
};
#[derive(Debug, Eq, PartialEq)]
struct RejectActivePolicy;
impl AllocationPolicy for RejectActivePolicy {
type Error = &'static str;
fn validate_key(&self, _key: &crate::StableKey) -> Result<(), Self::Error> {
Ok(())
}
fn validate_slot(
&self,
_key: &crate::StableKey,
_slot: &MemoryManagerSlot,
) -> Result<(), Self::Error> {
Err("active slot rejected")
}
}
fn ledger() -> AllocationLedger {
AllocationLedger::empty_genesis()
}
fn declaration() -> AllocationDeclaration {
AllocationDeclaration::new(
"app.users.v1",
MemoryManagerSlot::new(100).expect("usable slot"),
SchemaMetadata::default(),
)
.expect("declaration")
}
#[test]
fn validated_commit_matches_recovered_generation() {
let mut store = LedgerCommitStore::default();
let recovered = store.recover_or_initialize(&ledger()).unwrap();
let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
let commit = crate::runtime::bootstrap::prepare_commit(
&mut store,
recovered,
snapshot,
&GenericAllocationPolicy,
)
.unwrap();
let recovered = store.recover().unwrap();
assert_eq!(recovered.current_generation(), 1);
assert_eq!(recovered.ledger().records().len(), 1);
assert_eq!(
recovered.ledger().records()[0],
crate::AllocationRecord::active(&declaration())
);
assert_eq!(commit.confirm_persisted().generation(), 1);
}
#[test]
fn reservation_policy_alone_does_not_activate_reserved_allocation() {
let mut store = LedgerCommitStore::default();
let reserved = ledger()
.stage_reservation_generation(&[declaration()])
.unwrap();
store.commit(&reserved).unwrap();
let before = store.clone();
let recovered = store.recover().unwrap();
let snapshot = DeclarationSnapshot::new(vec![declaration()]).unwrap();
let err = crate::runtime::bootstrap::prepare_commit(
&mut store,
recovered,
snapshot,
&RejectActivePolicy,
)
.expect_err("active validation must run");
assert!(matches!(
err,
RuntimeBootstrapError::Validation(AllocationValidationError::Policy(
"active slot rejected"
))
));
assert_eq!(store, before);
assert_eq!(
store.recover().unwrap().ledger().records()[0].state(),
crate::AllocationState::Reserved
);
}
}