use crate::{
capability::{CommittedAllocations, ValidatedAllocations},
declaration::DeclarationSnapshot,
ledger::{
AllocationLedger, AllocationStageError, LedgerCommitError, LedgerCommitStore,
stage_validated_generation,
},
policy::AllocationPolicy,
validation::{AllocationValidationError, validate_allocations},
};
use std::borrow::Cow;
#[derive(Debug)]
pub struct AllocationBootstrap<'store> {
store: &'store mut LedgerCommitStore,
}
impl<'store> AllocationBootstrap<'store> {
pub const fn new(store: &'store mut LedgerCommitStore) -> Self {
Self { store }
}
pub(crate) fn validate_against<P>(
&mut self,
prior: crate::RecoveredLedger,
snapshot: DeclarationSnapshot,
policy: &P,
) -> Result<PendingBootstrapCommit, BootstrapError<P::Error>>
where
P: AllocationPolicy,
{
let validated =
validate_allocations(&prior, snapshot, policy).map_err(BootstrapError::Validation)?;
let staged = stage_validated_generation(Cow::Owned(prior.into_ledger()), &validated)
.map_err(BootstrapError::Staging)?;
self.store
.commit_generation(&staged)
.map_err(BootstrapError::Ledger)?;
Ok(PendingBootstrapCommit {
validated,
ledger: staged,
})
}
}
#[derive(Debug, Eq, PartialEq)]
#[must_use = "persist the owning ledger record, then confirm_persisted before opening allocations"]
pub struct PendingBootstrapCommit {
ledger: AllocationLedger,
validated: ValidatedAllocations,
}
impl PendingBootstrapCommit {
#[must_use]
#[cfg(test)]
pub const fn ledger(&self) -> &AllocationLedger {
&self.ledger
}
#[must_use]
pub fn confirm_persisted(self) -> CommittedAllocations {
self.validated
.confirm_persisted(self.ledger.current_generation())
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
pub enum BootstrapError<P> {
#[error(transparent)]
Ledger(LedgerCommitError),
#[error(transparent)]
Validation(AllocationValidationError<P>),
#[error(transparent)]
Staging(AllocationStageError),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AllocationDeclaration, 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 validate_reserved_slot(
&self,
_key: &crate::StableKey,
_slot: &MemoryManagerSlot,
) -> Result<(), Self::Error> {
Ok(())
}
}
fn ledger() -> AllocationLedger {
AllocationLedger::empty_genesis()
}
fn declaration() -> AllocationDeclaration {
AllocationDeclaration::new(
"app.users.v1",
MemoryManagerSlot::new(100).expect("usable slot"),
None,
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 = AllocationBootstrap::new(&mut store)
.validate_against(recovered, snapshot, &GenericAllocationPolicy)
.unwrap();
assert_eq!(commit.ledger().current_generation(), 1);
assert_eq!(commit.ledger().records().len(), 1);
assert_eq!(store.recover().unwrap().ledger(), commit.ledger());
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 = AllocationBootstrap::new(&mut store)
.validate_against(recovered, snapshot, &RejectActivePolicy)
.expect_err("active validation must run");
assert!(matches!(
err,
BootstrapError::Validation(AllocationValidationError::Policy("active slot rejected"))
));
assert_eq!(store, before);
assert_eq!(
store.recover().unwrap().ledger().records()[0].state(),
crate::AllocationState::Reserved
);
}
}