ic_backup/ops/persistence/execution_settlement/
mod.rs1use super::json::{check_json_size, create_encoded, encode_json};
4use super::{
5 AttemptJournalError, BackupLayoutGuard, JournalLock, JournalLockError,
6 OperationPlanPersistenceError, PersistenceError, 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 replay_execution_settlement(
30 layout: &BackupLayoutGuard,
31 expected_plan: &ArtifactChecksumRecord,
32) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
33 let derived = derive_execution_settlement(layout, expected_plan)?;
34 Ok(read_execution_settlement(
35 layout,
36 expected_plan,
37 &derived.digest(),
38 )?)
39}
40
41pub fn reconcile_execution_settlement(
53 layout: &BackupLayoutGuard,
54 expected_plan: &ArtifactChecksumRecord,
55) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
56 let derived = derive_execution_settlement(layout, expected_plan)?;
57 match read_execution_settlement(layout, expected_plan, &derived.digest()) {
58 Ok(existing) => Ok(existing),
59 Err(ExecutionSettlementPersistenceError::Persistence(PersistenceError::Io(error)))
60 if error.kind() == io::ErrorKind::NotFound =>
61 {
62 create_execution_settlement(layout, &derived)?;
63 Ok(read_execution_settlement(
64 layout,
65 expected_plan,
66 &derived.digest(),
67 )?)
68 }
69 Err(error) => Err(error.into()),
70 }
71}
72
73fn derive_execution_settlement(
74 layout: &BackupLayoutGuard,
75 expected_plan: &ArtifactChecksumRecord,
76) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
77 let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
78 ExecutionSettlementPersistenceError,
79 >(layout, expected_plan, None)?;
80 let rows = journals
81 .iter()
82 .map(ExecutionSettlementJournalRecord::from_journal)
83 .collect();
84 Ok(ExecutionSettlementRecord::new(plan.digest(), rows)?)
85}
86
87fn create_execution_settlement(
97 layout: &BackupLayoutGuard,
98 record: &ExecutionSettlementRecord,
99) -> Result<(), ExecutionSettlementPersistenceError> {
100 create_with(layout, record, create_encoded)
101}
102fn create_with(
103 layout: &BackupLayoutGuard,
104 record: &ExecutionSettlementRecord,
105 writer: impl FnOnce(&Path, &[u8]) -> Result<(), PersistenceError>,
106) -> Result<(), ExecutionSettlementPersistenceError> {
107 layout.check_root()?;
108 let path = layout.root().join("execution-settlement.json");
109 let _lock = JournalLock::acquire(&path)?;
110 let bytes = encode_json(record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
111 validate_retained(layout, record)?;
112 writer(&path, &bytes)?;
113 Ok(())
114}
115pub fn read_execution_settlement(
123 layout: &BackupLayoutGuard,
124 expected_plan: &ArtifactChecksumRecord,
125 expected: &ArtifactChecksumRecord,
126) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
127 layout.check_root()?;
128 let path = layout.root().join("execution-settlement.json");
129 let _lock = JournalLock::acquire(&path)?;
130 let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
131 check_json_size(&record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
132 if record.plan_intent() != expected_plan || &record.digest() != expected {
133 return Err(ExecutionSettlementPersistenceError::DigestMismatch);
134 }
135 validate_retained(layout, &record)?;
136 Ok(record)
137}
138fn validate_retained(
139 layout: &BackupLayoutGuard,
140 record: &ExecutionSettlementRecord,
141) -> Result<(), ExecutionSettlementPersistenceError> {
142 let (plan, journals) = super::attempt_journal::read_original_plan_journals::<
143 ExecutionSettlementPersistenceError,
144 >(layout, record.plan_intent(), None)?;
145 let references: Vec<_> = journals.iter().collect();
146 validate(&plan, &references, record)?;
147 layout.check_root()?;
148 Ok(())
149}
150#[derive(Debug, Error)]
152pub enum ExecutionSettlementCheckpointError {
153 #[error(transparent)]
155 Persistence(#[from] ExecutionSettlementPersistenceError),
156 #[error(transparent)]
158 Record(#[from] ExecutionSettlementError),
159}
160#[derive(Debug, Error)]
162pub enum ExecutionSettlementPersistenceError {
163 #[error("execution settlement digest mismatch")]
165 DigestMismatch,
166 #[error(transparent)]
168 Plan(#[from] OperationPlanPersistenceError),
169 #[error(transparent)]
171 Authority(#[from] crate::model::operation_plan::OperationPlanError),
172 #[error(transparent)]
174 Journal(#[from] AttemptJournalError),
175 #[error(transparent)]
177 Policy(#[from] ExecutionSettlementPolicyError),
178 #[error(transparent)]
180 Lock(#[from] JournalLockError),
181 #[error(transparent)]
183 Persistence(#[from] PersistenceError),
184}
185#[cfg(all(test, unix))]
186mod tests;