ic_backup/ops/persistence/execution_settlement/
mod.rs1use super::json::check_json_size;
4use super::{
5 AttemptJournalError, BackupLayoutGuard, JournalLock, JournalLockError,
6 OperationPlanPersistenceError, PersistenceError, create_json_durable, read_json,
7};
8use crate::{
9 model::{
10 artifacts::ArtifactChecksumRecord,
11 execution_settlement::{
12 ExecutionSettlementError, ExecutionSettlementJournalRecord, ExecutionSettlementRecord,
13 MAX_EXECUTION_SETTLEMENT_BYTES,
14 },
15 },
16 policy::execution_settlement::{ExecutionSettlementPolicyError, validate},
17};
18use std::{io, path::Path};
19use thiserror::Error;
20
21pub fn checkpoint_execution_settlement(
31 layout: &BackupLayoutGuard,
32 expected_plan: &ArtifactChecksumRecord,
33) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
34 let record = derive_execution_settlement(layout, expected_plan)?;
35 create_execution_settlement(layout, &record)?;
36 Ok(record)
37}
38
39pub fn replay_execution_settlement(
48 layout: &BackupLayoutGuard,
49 expected_plan: &ArtifactChecksumRecord,
50) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
51 let derived = derive_execution_settlement(layout, expected_plan)?;
52 Ok(read_execution_settlement(
53 layout,
54 expected_plan,
55 &derived.digest(),
56 )?)
57}
58
59pub fn reconcile_execution_settlement(
71 layout: &BackupLayoutGuard,
72 expected_plan: &ArtifactChecksumRecord,
73) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
74 let derived = derive_execution_settlement(layout, expected_plan)?;
75 match read_execution_settlement(layout, expected_plan, &derived.digest()) {
76 Ok(existing) => Ok(existing),
77 Err(ExecutionSettlementPersistenceError::Persistence(PersistenceError::Io(error)))
78 if error.kind() == io::ErrorKind::NotFound =>
79 {
80 create_execution_settlement(layout, &derived)?;
81 Ok(read_execution_settlement(
82 layout,
83 expected_plan,
84 &derived.digest(),
85 )?)
86 }
87 Err(error) => Err(error.into()),
88 }
89}
90
91fn derive_execution_settlement(
92 layout: &BackupLayoutGuard,
93 expected_plan: &ArtifactChecksumRecord,
94) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
95 let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
96 ExecutionSettlementPersistenceError,
97 >(layout, expected_plan, None)?;
98 let rows = journals
99 .iter()
100 .map(ExecutionSettlementJournalRecord::from_journal)
101 .collect();
102 Ok(ExecutionSettlementRecord::new(plan.digest(), rows)?)
103}
104
105pub fn create_execution_settlement(
115 layout: &BackupLayoutGuard,
116 record: &ExecutionSettlementRecord,
117) -> Result<(), ExecutionSettlementPersistenceError> {
118 create_with(layout, record, create_json_durable)
119}
120fn create_with(
121 layout: &BackupLayoutGuard,
122 record: &ExecutionSettlementRecord,
123 writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
124) -> Result<(), ExecutionSettlementPersistenceError> {
125 layout.check_root()?;
126 let path = layout.root().join("execution-settlement.json");
127 let _lock = JournalLock::acquire(&path)?;
128 check_json_size(record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
129 validate_retained(layout, record)?;
130 writer(&path, record)?;
131 Ok(())
132}
133pub fn read_execution_settlement(
141 layout: &BackupLayoutGuard,
142 expected_plan: &ArtifactChecksumRecord,
143 expected: &ArtifactChecksumRecord,
144) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
145 layout.check_root()?;
146 let path = layout.root().join("execution-settlement.json");
147 let _lock = JournalLock::acquire(&path)?;
148 let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
149 check_json_size(&record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
150 if record.plan_intent() != expected_plan || &record.digest() != expected {
151 return Err(ExecutionSettlementPersistenceError::DigestMismatch);
152 }
153 validate_retained(layout, &record)?;
154 Ok(record)
155}
156fn validate_retained(
157 layout: &BackupLayoutGuard,
158 record: &ExecutionSettlementRecord,
159) -> Result<(), ExecutionSettlementPersistenceError> {
160 let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
161 ExecutionSettlementPersistenceError,
162 >(layout, record.plan_intent(), None)?;
163 let references: Vec<_> = journals.iter().collect();
164 validate(&plan, &references, record)?;
165 layout.check_root()?;
166 Ok(())
167}
168#[derive(Debug, Error)]
170pub enum ExecutionSettlementCheckpointError {
171 #[error(transparent)]
173 Persistence(#[from] ExecutionSettlementPersistenceError),
174 #[error(transparent)]
176 Record(#[from] ExecutionSettlementError),
177}
178#[derive(Debug, Error)]
180pub enum ExecutionSettlementPersistenceError {
181 #[error("execution settlement digest mismatch")]
183 DigestMismatch,
184 #[error(transparent)]
186 Plan(#[from] OperationPlanPersistenceError),
187 #[error(transparent)]
189 Authority(#[from] crate::model::operation_plan::OperationPlanError),
190 #[error(transparent)]
192 Journal(#[from] AttemptJournalError),
193 #[error(transparent)]
195 Policy(#[from] ExecutionSettlementPolicyError),
196 #[error(transparent)]
198 Lock(#[from] JournalLockError),
199 #[error(transparent)]
201 Persistence(#[from] PersistenceError),
202}
203#[cfg(all(test, unix))]
204mod tests;