use super::json::check_json_size;
use super::{
AttemptJournalError, BackupLayoutGuard, JournalLock, JournalLockError,
OperationPlanPersistenceError, PersistenceError, create_json_durable, read_json,
read_operation_plan,
};
use crate::{
model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::AttemptJournalRecord,
execution_settlement::{
ExecutionSettlementError, ExecutionSettlementJournalRecord, ExecutionSettlementRecord,
MAX_EXECUTION_SETTLEMENT_BYTES,
},
operation_plan::OperationPlanRecord,
},
policy::execution_settlement::{ExecutionSettlementPolicyError, validate},
};
use std::path::Path;
use thiserror::Error;
pub fn checkpoint_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
let (plan, journals) = read_retained_journals(layout, expected_plan)?;
let rows = journals
.iter()
.map(ExecutionSettlementJournalRecord::from_journal)
.collect();
let record = ExecutionSettlementRecord::new(plan.digest(), rows)?;
create_execution_settlement(layout, &record)?;
Ok(record)
}
pub fn create_execution_settlement(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
) -> Result<(), ExecutionSettlementPersistenceError> {
create_with(layout, record, create_json_durable)
}
fn create_with(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
) -> Result<(), ExecutionSettlementPersistenceError> {
layout.check_root()?;
let path = layout.root().join("execution-settlement.json");
let _lock = JournalLock::acquire(&path)?;
check_json_size(record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
validate_retained(layout, record)?;
writer(&path, record)?;
Ok(())
}
pub fn read_execution_settlement(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
expected: &ArtifactChecksumRecord,
) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
layout.check_root()?;
let path = layout.root().join("execution-settlement.json");
let _lock = JournalLock::acquire(&path)?;
let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
check_json_size(&record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
if record.plan_intent() != expected_plan || &record.digest() != expected {
return Err(ExecutionSettlementPersistenceError::DigestMismatch);
}
validate_retained(layout, &record)?;
Ok(record)
}
fn validate_retained(
layout: &BackupLayoutGuard,
record: &ExecutionSettlementRecord,
) -> Result<(), ExecutionSettlementPersistenceError> {
let (plan, journals) = read_retained_journals(layout, record.plan_intent())?;
let references: Vec<_> = journals.iter().collect();
validate(&plan, &references, record)?;
layout.check_root()?;
Ok(())
}
fn read_retained_journals(
layout: &BackupLayoutGuard,
expected_plan: &ArtifactChecksumRecord,
) -> Result<(OperationPlanRecord, Vec<AttemptJournalRecord>), ExecutionSettlementPersistenceError> {
let plan = read_operation_plan(layout, expected_plan)?;
let authorities = plan.attempt_authorities()?;
let journals = super::attempt_journal::read_original_journals(layout, &authorities, None)?;
Ok((plan, journals))
}
#[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;