ic_backup/model/ic_snapshot_download/
mod.rs1use crate::model::{
4 attempt_journal::AttemptJournalRecordError,
5 data_stage_plan::{self, DataStagePlanError, LearnedDataStage, LearnedDataStageError},
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 Ok(self.binding.validate(binding, plan)?)
103 }
104 pub fn bind(
114 &self,
115 metadata_binding: &ExecutionStageBindingRecord,
116 metadata_plan: &OperationPlanRecord,
117 predecessors: Vec<ExecutionStagePredecessorRecord>,
118 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDownloadPlanningError> {
119 let plan = self
120 .plan
121 .as_ref()
122 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
123 self.binding
124 .bind(
125 plan,
126 metadata_binding,
127 metadata_plan,
128 self.metadata.request().digest().hash(),
129 predecessors,
130 )
131 .map_err(Into::into)
132 }
133}
134
135#[derive(Debug, Error)]
137pub enum IcSnapshotDownloadPlanningError {
138 #[error("invalid snapshot download chunk size")]
140 InvalidChunkSize,
141 #[error("snapshot download request count overflow")]
143 CountOverflow,
144 #[error("snapshot download needs {required} updates; original allocation is {original}")]
146 InsufficientAllowance {
147 required: u64,
149 original: u32,
151 },
152 #[error("snapshot download metadata differs from original stage")]
154 MetadataMismatch,
155 #[error("snapshot download has no data reads to bind")]
157 NoDataReads,
158 #[error(transparent)]
160 Plan(#[from] OperationPlanError),
161 #[error(transparent)]
163 Graph(#[from] EffectGraphError),
164 #[error(transparent)]
166 Workflow(#[from] ExecutionWorkflowError),
167 #[error(transparent)]
169 Data(#[from] IcSnapshotDataError),
170 #[error(transparent)]
172 Budget(#[from] AttemptJournalRecordError),
173}
174
175impl From<LearnedDataStageError> for IcSnapshotDownloadPlanningError {
176 fn from(error: LearnedDataStageError) -> Self {
177 match error {
178 LearnedDataStageError::Workflow(error) => Self::Workflow(error),
179 LearnedDataStageError::Mismatch => Self::MetadataMismatch,
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests;
186
187impl From<ExtentPlanningError> for IcSnapshotDownloadPlanningError {
188 fn from(error: ExtentPlanningError) -> Self {
189 match error {
190 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
191 ExtentPlanningError::CountOverflow => Self::CountOverflow,
192 ExtentPlanningError::InsufficientAllowance { required, original } => {
193 Self::InsufficientAllowance { required, original }
194 }
195 }
196 }
197}
198
199impl From<DataStagePlanError> for IcSnapshotDownloadPlanningError {
200 fn from(error: DataStagePlanError) -> Self {
201 match error {
202 DataStagePlanError::CountOverflow => Self::CountOverflow,
203 DataStagePlanError::Graph(error) => Self::Graph(error),
204 DataStagePlanError::Plan(error) => Self::Plan(error),
205 DataStagePlanError::Budget(error) => Self::Budget(error),
206 }
207 }
208}