ic_backup/model/ic_snapshot_download/
mod.rs1use crate::model::{
4 attempt_journal::AttemptJournalRecordError,
5 data_stage_plan::{self, DataStagePlanError},
6 effect_graph::EffectGraphError,
7 execution_workflow::{
8 ExecutionStageBindingRecord, ExecutionStagePredecessorRecord, ExecutionWorkflowError,
9 ExecutionWorkflowRecord,
10 },
11 ic_snapshot_data::{
12 ExtentPlanningError, IcSnapshotDataError, IcSnapshotDataRequest, planned_extents,
13 },
14 ic_snapshot_metadata::IcSnapshotMetadataReply,
15 operation_plan::{OperationPlanError, OperationPlanRecord},
16};
17use thiserror::Error;
18
19#[cfg(test)]
20use crate::model::{
21 attempt_journal::AttemptBudgetRecord,
22 operation_plan::{PlanBudgetRecord, PlannedOperationRecord, PlannedOperationRequest},
23};
24
25pub struct IcSnapshotDownloadPlan<'workflow, 'metadata> {
36 workflow: &'workflow ExecutionWorkflowRecord,
37 sequence: u64,
38 metadata: &'metadata IcSnapshotMetadataReply<'metadata>,
39 requests: Vec<IcSnapshotDataRequest<'metadata>>,
40 plan: Option<OperationPlanRecord>,
41}
42impl<'workflow, 'metadata> IcSnapshotDownloadPlan<'workflow, 'metadata> {
43 pub fn new(
52 workflow: &'workflow ExecutionWorkflowRecord,
53 sequence: u64,
54 metadata: &'metadata IcSnapshotMetadataReply<'metadata>,
55 chunk_bytes: u64,
56 ) -> Result<Self, IcSnapshotDownloadPlanningError> {
57 let stage = workflow.stage(sequence)?;
58 if stage.target() != metadata.request().target() {
59 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
60 }
61 let kinds = planned_extents(metadata, chunk_bytes, stage.budget().mutations())
62 .map_err(IcSnapshotDownloadPlanningError::from)?;
63 let requests = kinds
64 .into_iter()
65 .map(|kind| IcSnapshotDataRequest::new(metadata, kind))
66 .collect::<Result<Vec<_>, _>>()?;
67 let plan = data_stage_plan::build(
68 workflow.allocation(),
69 stage,
70 requests.iter().map(IcSnapshotDataRequest::digest),
71 )?;
72 Ok(Self {
73 workflow,
74 sequence,
75 metadata,
76 requests,
77 plan,
78 })
79 }
80 #[must_use]
82 pub fn requests(&self) -> &[IcSnapshotDataRequest<'metadata>] {
83 &self.requests
84 }
85 #[must_use]
88 pub const fn plan(&self) -> Option<&OperationPlanRecord> {
89 self.plan.as_ref()
90 }
91
92 pub(crate) fn validate_binding(
95 &self,
96 binding: &ExecutionStageBindingRecord,
97 ) -> Result<(), IcSnapshotDownloadPlanningError> {
98 let plan = self
99 .plan
100 .as_ref()
101 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
102 binding.validate(self.workflow, plan)?;
103 if binding.stage_sequence() != self.sequence
104 || !binding
105 .predecessors()
106 .iter()
107 .any(|row| row.learned_evidence() == &self.metadata.digest())
108 {
109 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
110 }
111 Ok(())
112 }
113 pub fn bind(
123 &self,
124 metadata_binding: &ExecutionStageBindingRecord,
125 metadata_plan: &OperationPlanRecord,
126 predecessors: Vec<ExecutionStagePredecessorRecord>,
127 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDownloadPlanningError> {
128 let plan = self
129 .plan
130 .as_ref()
131 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
132 metadata_binding.validate(self.workflow, metadata_plan)?;
133 if metadata_plan.operations().len() != 1
134 || metadata_plan.operations()[0].request() != self.metadata.request().digest().hash()
135 || !predecessors.iter().any(|row| {
136 row.stage_sequence() == metadata_binding.stage_sequence()
137 && row.binding() == &metadata_binding.digest()
138 && row.learned_evidence() == &self.metadata.digest()
139 })
140 {
141 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
142 }
143 Ok(ExecutionStageBindingRecord::new(
144 self.workflow,
145 self.sequence,
146 plan,
147 predecessors,
148 )?)
149 }
150}
151
152#[derive(Debug, Error)]
154pub enum IcSnapshotDownloadPlanningError {
155 #[error("invalid snapshot download chunk size")]
157 InvalidChunkSize,
158 #[error("snapshot download request count overflow")]
160 CountOverflow,
161 #[error("snapshot download needs {required} updates; original allocation is {original}")]
163 InsufficientAllowance {
164 required: u64,
166 original: u32,
168 },
169 #[error("snapshot download metadata differs from original stage")]
171 MetadataMismatch,
172 #[error("snapshot download has no data reads to bind")]
174 NoDataReads,
175 #[error(transparent)]
177 Plan(#[from] OperationPlanError),
178 #[error(transparent)]
180 Graph(#[from] EffectGraphError),
181 #[error(transparent)]
183 Workflow(#[from] ExecutionWorkflowError),
184 #[error(transparent)]
186 Data(#[from] IcSnapshotDataError),
187 #[error(transparent)]
189 Budget(#[from] AttemptJournalRecordError),
190}
191
192#[cfg(test)]
193pub(crate) mod tests;
194
195impl From<ExtentPlanningError> for IcSnapshotDownloadPlanningError {
196 fn from(error: ExtentPlanningError) -> Self {
197 match error {
198 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
199 ExtentPlanningError::CountOverflow => Self::CountOverflow,
200 ExtentPlanningError::InsufficientAllowance { required, original } => {
201 Self::InsufficientAllowance { required, original }
202 }
203 }
204 }
205}
206
207impl From<DataStagePlanError> for IcSnapshotDownloadPlanningError {
208 fn from(error: DataStagePlanError) -> Self {
209 match error {
210 DataStagePlanError::CountOverflow => Self::CountOverflow,
211 DataStagePlanError::Graph(error) => Self::Graph(error),
212 DataStagePlanError::Plan(error) => Self::Plan(error),
213 DataStagePlanError::Budget(error) => Self::Budget(error),
214 }
215 }
216}