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 if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_EXECUTION_SETTLEMENT_BYTES {
86 return Err(PersistenceError::RecordTooLarge {
87 limit: MAX_EXECUTION_SETTLEMENT_BYTES,
88 });
89 }
90 Ok(())
91}
92#[derive(Debug, Error)]
94pub enum ExecutionSettlementPersistenceError {
95 #[error("execution settlement digest mismatch")]
97 DigestMismatch,
98 #[error(transparent)]
100 Plan(#[from] OperationPlanPersistenceError),
101 #[error(transparent)]
103 Authority(#[from] crate::model::operation_plan::OperationPlanError),
104 #[error(transparent)]
106 Journal(#[from] AttemptJournalError),
107 #[error(transparent)]
109 Policy(#[from] ExecutionSettlementPolicyError),
110 #[error(transparent)]
112 Lock(#[from] JournalLockError),
113 #[error(transparent)]
115 Persistence(#[from] PersistenceError),
116}
117#[cfg(all(test, unix))]
118mod tests;