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::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
62fn derive_execution_settlement(
63 layout: &BackupLayoutGuard,
64 expected_plan: &ArtifactChecksumRecord,
65) -> Result<ExecutionSettlementRecord, ExecutionSettlementCheckpointError> {
66 let (plan, journals) = read_retained_journals(layout, expected_plan)?;
67 let rows = journals
68 .iter()
69 .map(ExecutionSettlementJournalRecord::from_journal)
70 .collect();
71 Ok(ExecutionSettlementRecord::new(plan.digest(), rows)?)
72}
73
74pub fn create_execution_settlement(
84 layout: &BackupLayoutGuard,
85 record: &ExecutionSettlementRecord,
86) -> Result<(), ExecutionSettlementPersistenceError> {
87 create_with(layout, record, create_json_durable)
88}
89fn create_with(
90 layout: &BackupLayoutGuard,
91 record: &ExecutionSettlementRecord,
92 writer: impl FnOnce(&Path, &ExecutionSettlementRecord) -> Result<(), PersistenceError>,
93) -> Result<(), ExecutionSettlementPersistenceError> {
94 layout.check_root()?;
95 let path = layout.root().join("execution-settlement.json");
96 let _lock = JournalLock::acquire(&path)?;
97 check_json_size(record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
98 validate_retained(layout, record)?;
99 writer(&path, record)?;
100 Ok(())
101}
102pub fn read_execution_settlement(
110 layout: &BackupLayoutGuard,
111 expected_plan: &ArtifactChecksumRecord,
112 expected: &ArtifactChecksumRecord,
113) -> Result<ExecutionSettlementRecord, ExecutionSettlementPersistenceError> {
114 layout.check_root()?;
115 let path = layout.root().join("execution-settlement.json");
116 let _lock = JournalLock::acquire(&path)?;
117 let record: ExecutionSettlementRecord = read_json(&path, MAX_EXECUTION_SETTLEMENT_BYTES)?;
118 check_json_size(&record, MAX_EXECUTION_SETTLEMENT_BYTES)?;
119 if record.plan_intent() != expected_plan || &record.digest() != expected {
120 return Err(ExecutionSettlementPersistenceError::DigestMismatch);
121 }
122 validate_retained(layout, &record)?;
123 Ok(record)
124}
125fn validate_retained(
126 layout: &BackupLayoutGuard,
127 record: &ExecutionSettlementRecord,
128) -> Result<(), ExecutionSettlementPersistenceError> {
129 let (plan, journals) = read_retained_journals(layout, record.plan_intent())?;
130 let references: Vec<_> = journals.iter().collect();
131 validate(&plan, &references, record)?;
132 layout.check_root()?;
133 Ok(())
134}
135fn read_retained_journals(
136 layout: &BackupLayoutGuard,
137 expected_plan: &ArtifactChecksumRecord,
138) -> Result<(OperationPlanRecord, Vec<AttemptJournalRecord>), ExecutionSettlementPersistenceError> {
139 let plan = read_operation_plan(layout, expected_plan)?;
140 let authorities = plan.attempt_authorities()?;
141 let journals = super::attempt_journal::read_original_journals(layout, &authorities, None)?;
142 Ok((plan, journals))
143}
144#[derive(Debug, Error)]
146pub enum ExecutionSettlementCheckpointError {
147 #[error(transparent)]
149 Persistence(#[from] ExecutionSettlementPersistenceError),
150 #[error(transparent)]
152 Record(#[from] ExecutionSettlementError),
153}
154#[derive(Debug, Error)]
156pub enum ExecutionSettlementPersistenceError {
157 #[error("execution settlement digest mismatch")]
159 DigestMismatch,
160 #[error(transparent)]
162 Plan(#[from] OperationPlanPersistenceError),
163 #[error(transparent)]
165 Authority(#[from] crate::model::operation_plan::OperationPlanError),
166 #[error(transparent)]
168 Journal(#[from] AttemptJournalError),
169 #[error(transparent)]
171 Policy(#[from] ExecutionSettlementPolicyError),
172 #[error(transparent)]
174 Lock(#[from] JournalLockError),
175 #[error(transparent)]
177 Persistence(#[from] PersistenceError),
178}
179#[cfg(all(test, unix))]
180mod tests;