ic_backup/model/ic_snapshot_download/
mod.rs1use crate::model::{
4 attempt_journal::AttemptJournalRecordError,
5 data_stage_plan::{self, DataStagePlanError, LearnedDataStage},
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
19pub struct IcSnapshotDownloadPlan<'workflow, 'metadata> {
30 binding: LearnedDataStage<'workflow>,
31 metadata: &'metadata IcSnapshotMetadataReply<'metadata>,
32 requests: Vec<IcSnapshotDataRequest<'metadata>>,
33 plan: Option<OperationPlanRecord>,
34}
35impl<'workflow, 'metadata> IcSnapshotDownloadPlan<'workflow, 'metadata> {
36 pub fn new(
45 workflow: &'workflow ExecutionWorkflowRecord,
46 sequence: u64,
47 metadata: &'metadata IcSnapshotMetadataReply<'metadata>,
48 chunk_bytes: u64,
49 ) -> Result<Self, IcSnapshotDownloadPlanningError> {
50 let stage = workflow.stage(sequence)?;
51 if stage.target() != metadata.request().target() {
52 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
53 }
54 let kinds = planned_extents(metadata, chunk_bytes, stage.budget().mutations())
55 .map_err(IcSnapshotDownloadPlanningError::from)?;
56 let requests = kinds
57 .into_iter()
58 .map(|kind| IcSnapshotDataRequest::new(metadata, kind))
59 .collect::<Result<Vec<_>, _>>()?;
60 let plan = data_stage_plan::build(
61 workflow.allocation(),
62 stage,
63 requests.iter().map(IcSnapshotDataRequest::digest),
64 )?;
65 Ok(Self {
66 binding: LearnedDataStage {
67 workflow,
68 sequence,
69 evidence: metadata.digest(),
70 },
71 metadata,
72 requests,
73 plan,
74 })
75 }
76 #[must_use]
78 pub fn requests(&self) -> &[IcSnapshotDataRequest<'metadata>] {
79 &self.requests
80 }
81 pub(crate) fn matches_metadata(&self, metadata: &IcSnapshotMetadataReply<'_>) -> bool {
83 self.metadata.digest() == metadata.digest()
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 if !self.binding.validate(binding, plan)? {
103 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
104 }
105 Ok(())
106 }
107 pub fn bind(
117 &self,
118 metadata_binding: &ExecutionStageBindingRecord,
119 metadata_plan: &OperationPlanRecord,
120 predecessors: Vec<ExecutionStagePredecessorRecord>,
121 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDownloadPlanningError> {
122 let plan = self
123 .plan
124 .as_ref()
125 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
126 self.binding
127 .bind(
128 plan,
129 metadata_binding,
130 metadata_plan,
131 self.metadata.request().digest().hash(),
132 predecessors,
133 )?
134 .ok_or(IcSnapshotDownloadPlanningError::MetadataMismatch)
135 }
136}
137
138#[derive(Debug, Error)]
140pub enum IcSnapshotDownloadPlanningError {
141 #[error("invalid snapshot download chunk size")]
143 InvalidChunkSize,
144 #[error("snapshot download request count overflow")]
146 CountOverflow,
147 #[error("snapshot download needs {required} updates; original allocation is {original}")]
149 InsufficientAllowance {
150 required: u64,
152 original: u32,
154 },
155 #[error("snapshot download metadata differs from original stage")]
157 MetadataMismatch,
158 #[error("snapshot download has no data reads to bind")]
160 NoDataReads,
161 #[error(transparent)]
163 Plan(#[from] OperationPlanError),
164 #[error(transparent)]
166 Graph(#[from] EffectGraphError),
167 #[error(transparent)]
169 Workflow(#[from] ExecutionWorkflowError),
170 #[error(transparent)]
172 Data(#[from] IcSnapshotDataError),
173 #[error(transparent)]
175 Budget(#[from] AttemptJournalRecordError),
176}
177
178#[cfg(test)]
179mod tests;
180
181impl From<ExtentPlanningError> for IcSnapshotDownloadPlanningError {
182 fn from(error: ExtentPlanningError) -> Self {
183 match error {
184 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
185 ExtentPlanningError::CountOverflow => Self::CountOverflow,
186 ExtentPlanningError::InsufficientAllowance { required, original } => {
187 Self::InsufficientAllowance { required, original }
188 }
189 }
190 }
191}
192
193impl From<DataStagePlanError> for IcSnapshotDownloadPlanningError {
194 fn from(error: DataStagePlanError) -> Self {
195 match error {
196 DataStagePlanError::CountOverflow => Self::CountOverflow,
197 DataStagePlanError::Graph(error) => Self::Graph(error),
198 DataStagePlanError::Plan(error) => Self::Plan(error),
199 DataStagePlanError::Budget(error) => Self::Budget(error),
200 }
201 }
202}