use super::json::create_encoded;
use super::{
AttemptJournalError, BackupLayoutGuard, JournalLockError, OperationPlanPersistenceError,
PersistenceError,
};
use crate::{
model::{
artifacts::ArtifactChecksumRecord,
execution_settlement::{
ExecutionSettlementError, ExecutionSettlementJournalRecord, ExecutionSettlementRecord,
MAX_EXECUTION_SETTLEMENT_BYTES,
},
},
policy::execution_settlement::{ExecutionSettlementPolicyError, validate_journals},
};
use std::{io, path::Path};
use thiserror::Error;
pub fn replay_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
let derived = derive_execution_settlement(layout, expected_plan)?;
Ok(read_execution_settlement(
layout,
expected_plan,
&derived.digest(),
)?)
}
pub fn reconcile_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
let derived = derive_execution_settlement(layout, expected_plan)?;
match read_execution_settlement(layout, expected_plan, &derived.digest()) {
Ok(existing) => Ok(existing),
Err(ExecutionSettlementPersistenceError::Persistence(PersistenceError::Io(error)))
if error.kind() == io::ErrorKind::NotFound =>
{
create_execution_settlement(layout, &derived)?;
Ok(read_execution_settlement(
layout,
expected_plan,
&derived.digest(),
)?)
}
Err(error) => Err(error.into()),
}
}
fn derive_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
ExecutionSettlementPersistenceError,
>(layout, expected_plan, None)?;
let rows = journals
.iter()
.map(ExecutionSettlementJournalRecord::from_journal)
.collect();
Ok(ExecutionSettlementRecord::new(plan.digest(), rows)?)
}
fn create_execution_settlement(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
) -> Result<(), ExecutionSettlementPersistenceError> {
create_with(layout, record, create_encoded)
}
fn create_with(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
writer: impl FnOnce(&Path, &[u8]) -> Result<(), PersistenceError>,
) -> Result<(), ExecutionSettlementPersistenceError> {
super::immutable_record::create_with(
layout,
"execution-settlement.json",
MAX_EXECUTION_SETTLEMENT_BYTES,
record,
|| validate_retained(layout, record),
writer,
)
}
pub fn read_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
expected: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
super::immutable_record::read_with(
layout,
"execution-settlement.json",
MAX_EXECUTION_SETTLEMENT_BYTES,
|record: &ExecutionSettlementRecord| {
if record.plan_intent() != expected_plan || &record.digest() != expected {
return Err(ExecutionSettlementPersistenceError::DigestMismatch);
}
validate_retained(layout, record)
},
)
}
fn validate_retained(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
) -> Result<(), ExecutionSettlementPersistenceError> {
let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
ExecutionSettlementPersistenceError,
>(layout, record.plan_intent(), None)?;
validate_journals(&plan, journals.iter(), record)?;
layout.check_root()?;
Ok(())
}
#[derive(Debug, Error)]
pub enum ExecutionSettlementCheckpointError {
#[error(transparent)]
Persistence(#[from] ExecutionSettlementPersistenceError),
#[error(transparent)]
Record(#[from] ExecutionSettlementError),
}
#[derive(Debug, Error)]
pub enum ExecutionSettlementPersistenceError {
#[error("execution settlement digest mismatch")]
DigestMismatch,
#[error(transparent)]
Plan(#[from] OperationPlanPersistenceError),
#[error(transparent)]
Authority(#[from] crate::model::operation_plan::OperationPlanError),
#[error(transparent)]
Journal(#[from] AttemptJournalError),
#[error(transparent)]
Policy(#[from] ExecutionSettlementPolicyError),
#[error(transparent)]
Lock(#[from] JournalLockError),
#[error(transparent)]
Persistence(#[from] PersistenceError),
}
#[cfg(all(test, unix))]
mod tests;