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>(
142 layout: &BackupLayoutGuard,
143 expected_workflow: &ArtifactChecksumRecord,
144 retained: &BTreeMap<u64, RetainedStageEvidence>,
145 driver: &mut D,
146) -> Result<ExecutionWorkflowSettlementView, ExecutionWorkflowRunError<D::Error>> {
147 let workflow = read_execution_workflow(layout, expected_workflow)?;
148 let occupied = read_execution_stage_occupancy(layout, expected_workflow)?;
149 if !retained.keys().copied().eq(occupied.iter().copied()) {
150 return Err(ExecutionWorkflowRunError::RetainedStageSetMismatch);
151 }
152 let mut completed: BTreeMap<u64, ExecutionStagePredecessorRecord> = BTreeMap::new();
153 for (&sequence, evidence) in retained {
154 let stage =
155 ExecutionStageGuard::open(layout, expected_workflow, sequence, &evidence.binding)?;
156 let checkpoint = stage.replay_checkpoint(evidence.learned_evidence.clone())?;
157 completed.insert(sequence, checkpoint);
158 }
159 if read_execution_stage_occupancy(layout, expected_workflow)? != occupied {
162 return Err(ExecutionWorkflowRunError::RetainedStageSetMismatch);
163 }
164 for node in workflow.allocation().graph().ordered_nodes() {
165 let sequence = node.operation_sequence();
166 if completed.contains_key(&sequence) {
167 continue;
168 }
169 let predecessors = node
170 .depends_on()
171 .iter()
172 .map(|dependency| {
173 completed
174 .get(dependency)
175 .cloned()
176 .ok_or(ExecutionWorkflowRunError::MissingPredecessor(*dependency))
177 })
178 .collect::<Result<Vec<_>, _>>()?;
179 let plan = driver
180 .bind_stage(&workflow, sequence, &predecessors)
181 .map_err(ExecutionWorkflowRunError::Driver)?;
182 let binding = ExecutionStageBindingRecord::new(&workflow, sequence, &plan, predecessors)?;
183 let stage = ExecutionStageGuard::prepare(layout, binding, plan)?;
184 let learned = driver
185 .execute_stage(&stage)
186 .await
187 .map_err(ExecutionWorkflowRunError::Driver)?;
188 let checkpoint = stage.reconcile_checkpoint(learned)?;
189 completed.insert(sequence, checkpoint);
190 }
191 Ok(read_complete_execution_workflow(layout, expected_workflow)?)
192}
193
194#[derive(Debug, Error)]
196pub enum ExecutionWorkflowRunError<E: std::error::Error + 'static> {
197 #[error("original workflow predecessor {0} did not complete")]
199 MissingPredecessor(u64),
200 #[error("retained stage identities do not match occupied original stages")]
202 RetainedStageSetMismatch,
203 #[error(transparent)]
205 Workflow(#[from] ExecutionWorkflowPersistenceError),
206 #[error(transparent)]
208 Binding(#[from] ExecutionWorkflowError),
209 #[error(transparent)]
211 Preparation(#[from] ExecutionStagePreparationError),
212 #[error("original workflow stage driver failed: {0}")]
214 Driver(#[source] E),
215 #[error(transparent)]
217 Checkpoint(#[from] ExecutionStageCheckpointError),
218}
219
220#[cfg(test)]
221mod tests;