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.has_learned_evidence(self.sequence, &self.metadata.digest()) {
104 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
105 }
106 Ok(())
107 }
108 pub fn bind(
118 &self,
119 metadata_binding: &ExecutionStageBindingRecord,
120 metadata_plan: &OperationPlanRecord,
121 predecessors: Vec<ExecutionStagePredecessorRecord>,
122 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDownloadPlanningError> {
123 let plan = self
124 .plan
125 .as_ref()
126 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
127 metadata_binding.validate(self.workflow, metadata_plan)?;
128 if !metadata_binding.matches_learned_predecessor(
129 metadata_plan,
130 self.metadata.request().digest().hash(),
131 &self.metadata.digest(),
132 &predecessors,
133 ) {
134 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
135 }
136 Ok(ExecutionStageBindingRecord::new(
137 self.workflow,
138 self.sequence,
139 plan,
140 predecessors,
141 )?)
142 }
143}
144
145#[derive(Debug, Error)]
147pub enum IcSnapshotDownloadPlanningError {
148 #[error("invalid snapshot download chunk size")]
150 InvalidChunkSize,
151 #[error("snapshot download request count overflow")]
153 CountOverflow,
154 #[error("snapshot download needs {required} updates; original allocation is {original}")]
156 InsufficientAllowance {
157 required: u64,
159 original: u32,
161 },
162 #[error("snapshot download metadata differs from original stage")]
164 MetadataMismatch,
165 #[error("snapshot download has no data reads to bind")]
167 NoDataReads,
168 #[error(transparent)]
170 Plan(#[from] OperationPlanError),
171 #[error(transparent)]
173 Graph(#[from] EffectGraphError),
174 #[error(transparent)]
176 Workflow(#[from] ExecutionWorkflowError),
177 #[error(transparent)]
179 Data(#[from] IcSnapshotDataError),
180 #[error(transparent)]
182 Budget(#[from] AttemptJournalRecordError),
183}
184
185#[cfg(test)]
186pub(crate) mod tests;
187
188impl From<ExtentPlanningError> for IcSnapshotDownloadPlanningError {
189 fn from(error: ExtentPlanningError) -> Self {
190 match error {
191 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
192 ExtentPlanningError::CountOverflow => Self::CountOverflow,
193 ExtentPlanningError::InsufficientAllowance { required, original } => {
194 Self::InsufficientAllowance { required, original }
195 }
196 }
197 }
198}
199
200impl From<DataStagePlanError> for IcSnapshotDownloadPlanningError {
201 fn from(error: DataStagePlanError) -> Self {
202 match error {
203 DataStagePlanError::CountOverflow => Self::CountOverflow,
204 DataStagePlanError::Graph(error) => Self::Graph(error),
205 DataStagePlanError::Plan(error) => Self::Plan(error),
206 DataStagePlanError::Budget(error) => Self::Budget(error),
207 }
208 }
209}