ic_backup/ops/persistence/execution_settlement/
mod.rs1use super::{
4 AttemptJournalError, AttemptJournalGuard, BackupLayoutGuard, JournalLock, JournalLockError,
5 OperationPlanPersistenceError, PersistenceError, create_json_durable, read_json,
6 read_operation_plan,
7};
8use crate::{
9 model::{
10 artifacts::ArtifactChecksumRecord,
11 execution_settlement::{ExecutionSettlementRecord, MAX_EXECUTION_SETTLEMENT_BYTES},
12 },
13 policy::execution_settlement::{ExecutionSettlementPolicyError, validate},
14};
15use std::path::Path;
16use thiserror::Error;
17
18pub fn create_execution_settlement(
28 layout: &BackupLayoutGuard,
29 record: &ExecutionSettlementRecord,
30) -> Result<(), ExecutionSettlementPersistenceError> {
31 create_with(layout, record, create_json_durable)
32}
33fn create_with(
34 layout: &BackupLayoutGuard,
35 record: &ExecutionSettlementRecord,
36 writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
37) -> Result<(), ExecutionSettlementPersistenceError> {
38 layout.check_root()?;
39 let path = layout.root().join("execution-settlement.json");
40 let _lock = JournalLock::acquire(&path)?;
41 check_size(record)?;
42 validate_retained(layout, record)?;
43 writer(&path, record)?;
44 Ok(())
45}
46pub fn read_execution_settlement(
54 layout: &BackupLayoutGuard,
55 expected_plan: &ArtifactChecksumRecord,
56 expected: &ArtifactChecksumRecord,
57) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
58 layout.check_root()?;
59 let path = layout.root().join("execution-settlement.json");
60 let _lock = JournalLock::acquire(&path)?;
61 let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
62 check_size(&record)?;
63 if record.plan_intent() != expected_plan || &record.digest() != expected {
64 return Err(ExecutionSettlementPersistenceError::DigestMismatch);
65 }
66 validate_retained(layout, &record)?;
67 Ok(record)
68}
69fn validate_retained(
70 layout: &BackupLayoutGuard,
71 record: &ExecutionSettlementRecord,
72) -> Result<(), ExecutionSettlementPersistenceError> {
73 let plan = read_operation_plan(layout, record.plan_intent())?;
74 let mut journals = Vec::with_capacity(plan.operations().len());
75 for authority in plan.attempt_authorities()? {
76 let guard = AttemptJournalGuard::open(layout, &authority)?;
77 journals.push(guard.record()?.clone());
78 }
79 let references: Vec<_> = journals.iter().collect();
80 validate(&plan, &references, record)?;
81 layout.check_root()?;
82 Ok(())
83}
84fn check_size(record: &ExecutionSettlementRecord) -> Result<(), PersistenceError> {
85 super::json::check_json_size(record, MAX_EXECUTION_SETTLEMENT_BYTES)
86}
87#[derive(Debug, Error)]
89pub enum ExecutionSettlementPersistenceError {
90 #[error("execution settlement digest mismatch")]
92 DigestMismatch,
93 #[error(transparent)]
95 Plan(#[from] OperationPlanPersistenceError),
96 #[error(transparent)]
98 Authority(#[from] crate::model::operation_plan::OperationPlanError),
99 #[error(transparent)]
101 Journal(#[from] AttemptJournalError),
102 #[error(transparent)]
104 Policy(#[from] ExecutionSettlementPolicyError),
105 #[error(transparent)]
107 Lock(#[from] JournalLockError),
108 #[error(transparent)]
110 Persistence(#[from] PersistenceError),
111}
112#[cfg(all(test, unix))]
113mod tests;