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 read_operation_plan,
8};
9use crate::{
10 model::{
11 artifacts::ArtifactChecksumRecord,
12 attempt_journal::AttemptJournalRecord,
13 execution_settlement::{
14 ExecutionSettlementError, ExecutionSettlementJournalRecord, ExecutionSettlementRecord,
15 MAX_EXECUTION_SETTLEMENT_BYTES,
16 },
17 operation_plan::OperationPlanRecord,
18 },
19 policy::execution_settlement::{ExecutionSettlementPolicyError, validate},
20};
21use std::{io, path::Path};
22use thiserror::Error;
23
24pub fn checkpoint_execution_settlement(
34 layout: &BackupLayoutGuard,
35 expected_plan: &ArtifactChecksumRecord,
36) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
37 let record = derive_execution_settlement(layout, expected_plan)?;
38 create_execution_settlement(layout, &record)?;
39 Ok(record)
40}
41
42pub fn replay_execution_settlement(
51 layout: &BackupLayoutGuard,
52 expected_plan: &ArtifactChecksumRecord,
53) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
54 let derived = derive_execution_settlement(layout, expected_plan)?;
55 Ok(read_execution_settlement(
56 layout,
57 expected_plan,
58 &derived.digest(),
59 )?)
60}
61
62pub fn reconcile_execution_settlement(
74 layout: &BackupLayoutGuard,
75 expected_plan: &ArtifactChecksumRecord,
76) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
77 let derived = derive_execution_settlement(layout, expected_plan)?;
78 match read_execution_settlement(layout, expected_plan, &derived.digest()) {
79 Ok(existing) => Ok(existing),
80 Err(ExecutionSettlementPersistenceError::Persistence(PersistenceError::Io(error)))
81 if error.kind() == io::ErrorKind::NotFound =>
82 {
83 create_execution_settlement(layout, &derived)?;
84 Ok(read_execution_settlement(
85 layout,
86 expected_plan,
87 &derived.digest(),
88 )?)
89 }
90 Err(error) => Err(error.into()),
91 }
92}
93
94fn derive_execution_settlement(
95 layout: &BackupLayoutGuard,
96 expected_plan: &ArtifactChecksumRecord,
97) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
98 let (plan, journals) = read_retained_journals(layout, expected_plan)?;
99 let rows = journals
100 .iter()
101 .map(ExecutionSettlementJournalRecord::from_journal)
102 .collect();
103 Ok(ExecutionSettlementRecord::new(plan.digest(), rows)?)
104}
105
106pub fn create_execution_settlement(
116 layout: &BackupLayoutGuard,
117 record: &ExecutionSettlementRecord,
118) -> Result<(), ExecutionSettlementPersistenceError> {
119 create_with(layout, record, create_json_durable)
120}
121fn create_with(
122 layout: &BackupLayoutGuard,
123 record: &ExecutionSettlementRecord,
124 writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
125) -> Result<(), ExecutionSettlementPersistenceError> {
126 layout.check_root()?;
127 let path = layout.root().join("execution-settlement.json");
128 let _lock = JournalLock::acquire(&path)?;
129 check_json_size(record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
130 validate_retained(layout, record)?;
131 writer(&path, record)?;
132 Ok(())
133}
134pub fn read_execution_settlement(
142 layout: &BackupLayoutGuard,
143 expected_plan: &ArtifactChecksumRecord,
144 expected: &ArtifactChecksumRecord,
145) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
146 layout.check_root()?;
147 let path = layout.root().join("execution-settlement.json");
148 let _lock = JournalLock::acquire(&path)?;
149 let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
150 check_json_size(&record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
151 if record.plan_intent() != expected_plan || &record.digest() != expected {
152 return Err(ExecutionSettlementPersistenceError::DigestMismatch);
153 }
154 validate_retained(layout, &record)?;
155 Ok(record)
156}
157fn validate_retained(
158 layout: &BackupLayoutGuard,
159 record: &ExecutionSettlementRecord,
160) -> Result<(), ExecutionSettlementPersistenceError> {
161 let (plan, journals) = read_retained_journals(layout, record.plan_intent())?;
162 let references: Vec<_> = journals.iter().collect();
163 validate(&plan, &references, record)?;
164 layout.check_root()?;
165 Ok(())
166}
167fn read_retained_journals(
168 layout: &BackupLayoutGuard,
169 expected_plan: &ArtifactChecksumRecord,
170) -> Result<(OperationPlanRecord, Vec<AttemptJournalRecord>), ExecutionSettlementPersistenceError> {
171 let plan = read_operation_plan(layout, expected_plan)?;
172 let authorities = plan.attempt_authorities()?;
173 let journals = super::attempt_journal::read_original_journals(layout, &authorities, None)?;
174 Ok((plan, journals))
175}
176#[derive(Debug, Error)]
178pub enum ExecutionSettlementCheckpointError {
179 #[error(transparent)]
181 Persistence(#[from] ExecutionSettlementPersistenceError),
182 #[error(transparent)]
184 Record(#[from] ExecutionSettlementError),
185}
186#[derive(Debug, Error)]
188pub enum ExecutionSettlementPersistenceError {
189 #[error("execution settlement digest mismatch")]
191 DigestMismatch,
192 #[error(transparent)]
194 Plan(#[from] OperationPlanPersistenceError),
195 #[error(transparent)]
197 Authority(#[from] crate::model::operation_plan::OperationPlanError),
198 #[error(transparent)]
200 Journal(#[from] AttemptJournalError),
201 #[error(transparent)]
203 Policy(#[from] ExecutionSettlementPolicyError),
204 #[error(transparent)]
206 Lock(#[from] JournalLockError),
207 #[error(transparent)]
209 Persistence(#[from] PersistenceError),
210}
211#[cfg(all(test, unix))]
212mod tests;