ic_backup/workflow/execution_workflow/
mod.rs1use crate::{
4 model::{
5 artifacts::ArtifactChecksumRecord,
6 execution_workflow::{
7 ExecutionStageBindingRecord, ExecutionStagePredecessorRecord, ExecutionWorkflowError,
8 ExecutionWorkflowRecord,
9 },
10 operation_plan::OperationPlanRecord,
11 },
12 ops::persistence::{
13 BackupLayoutGuard, ExecutionStageCheckpointError, ExecutionStageGuard,
14 ExecutionStagePreparationError, ExecutionWorkflowPersistenceError,
15 ExecutionWorkflowSettlementView, create_execution_workflow,
16 read_complete_execution_workflow, read_execution_stage_occupancy, read_execution_workflow,
17 },
18};
19use std::{collections::BTreeMap, future::Future};
20use thiserror::Error;
21
22pub trait ExecutionWorkflowStageDriver {
30 type Error: std::error::Error + 'static;
32
33 fn bind_stage(
37 &mut self,
38 workflow: &ExecutionWorkflowRecord,
39 stage_sequence: u64,
40 predecessors: &[ExecutionStagePredecessorRecord],
41 ) -> Result<OperationPlanRecord, Self::Error>;
42
43 fn execute_stage(
45 &mut self,
46 stage: &ExecutionStageGuard<'_>,
47 ) -> impl Future<Output = Result<ArtifactChecksumRecord, Self::Error>>;
48}
49
50#[derive(Clone, Debug, Eq, PartialEq)]
56pub struct RetainedStageEvidence {
57 binding: ArtifactChecksumRecord,
58 learned_evidence: ArtifactChecksumRecord,
59}
60impl RetainedStageEvidence {
61 #[must_use]
63 pub const fn new(
64 binding: ArtifactChecksumRecord,
65 learned_evidence: ArtifactChecksumRecord,
66 ) -> Self {
67 Self {
68 binding,
69 learned_evidence,
70 }
71 }
72}
73
74pub async fn run_new_execution_workflow<D: ExecutionWorkflowStageDriver>(
93 layout: &BackupLayoutGuard,
94 workflow: &ExecutionWorkflowRecord,
95 driver: &mut D,
96) -> Result<ExecutionWorkflowSettlementView, ExecutionWorkflowRunError<D::Error>> {
97 create_execution_workflow(layout, workflow)?;
98 let mut completed: BTreeMap<u64, ExecutionStagePredecessorRecord> = BTreeMap::new();
99 for node in workflow.allocation().graph().ordered_nodes() {
100 let sequence = node.operation_sequence();
101 let predecessors = node
102 .depends_on()
103 .iter()
104 .map(|dependency| {
105 completed
106 .get(dependency)
107 .cloned()
108 .ok_or(ExecutionWorkflowRunError::MissingPredecessor(*dependency))
109 })
110 .collect::<Result<Vec<_>, _>>()?;
111 let plan = driver
112 .bind_stage(workflow, sequence, &predecessors)
113 .map_err(ExecutionWorkflowRunError::Driver)?;
114 let binding = ExecutionStageBindingRecord::new(workflow, sequence, &plan, predecessors)?;
115 let stage = ExecutionStageGuard::prepare(layout, binding, plan)?;
116 let learned = driver
117 .execute_stage(&stage)
118 .await
119 .map_err(ExecutionWorkflowRunError::Driver)?;
120 let checkpoint = stage.reconcile_checkpoint(learned)?;
121 completed.insert(sequence, checkpoint);
122 }
123 Ok(read_complete_execution_workflow(
124 layout,
125 &workflow.digest(),
126 )?)
127}
128
129pub async fn run_retained_execution_workflow<D: ExecutionWorkflowStageDriver>(
145 layout: &BackupLayoutGuard,
146 expected_workflow: &ArtifactChecksumRecord,
147 retained: &BTreeMap<u64, RetainedStageEvidence>,
148 driver: &mut D,
149) -> Result<ExecutionWorkflowSettlementView, ExecutionWorkflowRunError<D::Error>> {
150 let workflow = read_execution_workflow(layout, expected_workflow)?;
151 let occupied = read_execution_stage_occupancy(layout, expected_workflow)?;
152 if !retained.keys().copied().eq(occupied.iter().copied()) {
153 return Err(ExecutionWorkflowRunError::RetainedStageSetMismatch);
154 }
155 let mut completed: BTreeMap<u64, ExecutionStagePredecessorRecord> = BTreeMap::new();
156 for (&sequence, evidence) in retained {
157 let stage =
158 ExecutionStageGuard::open(layout, expected_workflow, sequence, &evidence.binding)?;
159 for predecessor in stage.binding().predecessors() {
160 let Some(original) = retained.get(&predecessor.stage_sequence()) else {
161 return Err(ExecutionWorkflowRunError::RetainedStageSetMismatch);
162 };
163 if &original.learned_evidence != predecessor.learned_evidence() {
164 return Err(ExecutionWorkflowRunError::RetainedLearnedMismatch(sequence));
165 }
166 }
167 let checkpoint = stage.replay_checkpoint(evidence.learned_evidence.clone())?;
168 completed.insert(sequence, checkpoint);
169 }
170 if read_execution_stage_occupancy(layout, expected_workflow)? != occupied {
173 return Err(ExecutionWorkflowRunError::RetainedStageSetMismatch);
174 }
175 for node in workflow.allocation().graph().ordered_nodes() {
176 let sequence = node.operation_sequence();
177 if completed.contains_key(&sequence) {
178 continue;
179 }
180 let predecessors = node
181 .depends_on()
182 .iter()
183 .map(|dependency| {
184 completed
185 .get(dependency)
186 .cloned()
187 .ok_or(ExecutionWorkflowRunError::MissingPredecessor(*dependency))
188 })
189 .collect::<Result<Vec<_>, _>>()?;
190 let plan = driver
191 .bind_stage(&workflow, sequence, &predecessors)
192 .map_err(ExecutionWorkflowRunError::Driver)?;
193 let binding = ExecutionStageBindingRecord::new(&workflow, sequence, &plan, predecessors)?;
194 let stage = ExecutionStageGuard::prepare(layout, binding, plan)?;
195 let learned = driver
196 .execute_stage(&stage)
197 .await
198 .map_err(ExecutionWorkflowRunError::Driver)?;
199 let checkpoint = stage.reconcile_checkpoint(learned)?;
200 completed.insert(sequence, checkpoint);
201 }
202 Ok(read_complete_execution_workflow(layout, expected_workflow)?)
203}
204
205#[derive(Debug, Error)]
207pub enum ExecutionWorkflowRunError<E: std::error::Error + 'static> {
208 #[error("original workflow predecessor {0} did not complete")]
210 MissingPredecessor(u64),
211 #[error("retained stage identities do not match occupied original stages")]
213 RetainedStageSetMismatch,
214 #[error("retained stage {0} has a different learned predecessor commitment")]
216 RetainedLearnedMismatch(u64),
217 #[error(transparent)]
219 Workflow(#[from] ExecutionWorkflowPersistenceError),
220 #[error(transparent)]
222 Binding(#[from] ExecutionWorkflowError),
223 #[error(transparent)]
225 Preparation(#[from] ExecutionStagePreparationError),
226 #[error("original workflow stage driver failed: {0}")]
228 Driver(#[source] E),
229 #[error(transparent)]
231 Checkpoint(#[from] ExecutionStageCheckpointError),
232}
233
234#[cfg(test)]
235mod tests;