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 reads: Option<DataReads<'metadata>>,
33}
34
35struct DataReads<'metadata> {
37 requests: Vec<IcSnapshotDataRequest<'metadata>>,
38 plan: OperationPlanRecord,
39}
40impl<'workflow, 'metadata> IcSnapshotDownloadPlan<'workflow, 'metadata> {
41 pub fn new(
50 workflow: &'workflow ExecutionWorkflowRecord,
51 sequence: u64,
52 metadata: &'metadata IcSnapshotMetadataReply<'metadata>,
53 chunk_bytes: u64,
54 ) -> Result<Self, IcSnapshotDownloadPlanningError> {
55 let stage = workflow.stage(sequence)?;
56 if stage.target() != metadata.request().target() {
57 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
58 }
59 let kinds = planned_extents(metadata, chunk_bytes, stage.budget().mutations())
60 .map_err(IcSnapshotDownloadPlanningError::from)?;
61 let requests = kinds
62 .into_iter()
63 .map(|kind| IcSnapshotDataRequest::new(metadata, kind))
64 .collect::<Result<Vec<_>, _>>()?;
65 let plan = data_stage_plan::build(
66 workflow.allocation(),
67 stage,
68 requests.iter().map(IcSnapshotDataRequest::digest),
69 )?;
70 Ok(Self {
71 binding: LearnedDataStage {
72 workflow,
73 sequence,
74 evidence: metadata.digest(),
75 },
76 metadata,
77 reads: plan.map(|plan| DataReads { requests, plan }),
78 })
79 }
80 #[must_use]
82 pub fn requests(&self) -> &[IcSnapshotDataRequest<'metadata>] {
83 self.reads
84 .as_ref()
85 .map_or(&[], |reads| reads.requests.as_slice())
86 }
87 pub(crate) fn matches_metadata(&self, metadata: &IcSnapshotMetadataReply<'_>) -> bool {
89 self.metadata.digest() == metadata.digest()
90 }
91 #[must_use]
94 pub const fn plan(&self) -> Option<&OperationPlanRecord> {
95 match &self.reads {
96 Some(reads) => Some(&reads.plan),
97 None => None,
98 }
99 }
100
101 pub(crate) fn validate_binding(
104 &self,
105 binding: &ExecutionStageBindingRecord,
106 ) -> Result<(), IcSnapshotDownloadPlanningError> {
107 let plan = self
108 .plan()
109 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
110 Ok(self.binding.validate(binding, plan)?)
111 }
112 pub fn bind(
122 &self,
123 metadata_binding: &ExecutionStageBindingRecord,
124 metadata_plan: &OperationPlanRecord,
125 predecessors: Vec<ExecutionStagePredecessorRecord>,
126 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDownloadPlanningError> {
127 let plan = self
128 .plan()
129 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
130 self.binding
131 .bind(
132 plan,
133 metadata_binding,
134 metadata_plan,
135 self.metadata.request().digest().hash(),
136 predecessors,
137 )
138 .map_err(Into::into)
139 }
140}
141
142#[derive(Debug, Error)]
144pub enum IcSnapshotDownloadPlanningError {
145 #[error("invalid snapshot download chunk size")]
147 InvalidChunkSize,
148 #[error("snapshot download request count overflow")]
150 CountOverflow,
151 #[error("snapshot download needs {required} updates; original allocation is {original}")]
153 InsufficientAllowance {
154 required: u64,
156 original: u32,
158 },
159 #[error("snapshot download metadata differs from original stage")]
161 MetadataMismatch,
162 #[error("snapshot download has no data reads to bind")]
164 NoDataReads,
165 #[error(transparent)]
167 Plan(#[from] OperationPlanError),
168 #[error(transparent)]
170 Graph(#[from] EffectGraphError),
171 #[error(transparent)]
173 Workflow(#[from] ExecutionWorkflowError),
174 #[error(transparent)]
176 Data(#[from] IcSnapshotDataError),
177 #[error(transparent)]
179 Budget(#[from] AttemptJournalRecordError),
180}
181
182impl From<LearnedDataStageError> for IcSnapshotDownloadPlanningError {
183 fn from(error: LearnedDataStageError) -> Self {
184 match error {
185 LearnedDataStageError::Workflow(error) => Self::Workflow(error),
186 LearnedDataStageError::Mismatch => Self::MetadataMismatch,
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests;
193
194impl From<ExtentPlanningError> for IcSnapshotDownloadPlanningError {
195 fn from(error: ExtentPlanningError) -> Self {
196 match error {
197 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
198 ExtentPlanningError::CountOverflow => Self::CountOverflow,
199 ExtentPlanningError::InsufficientAllowance { required, original } => {
200 Self::InsufficientAllowance { required, original }
201 }
202 }
203 }
204}
205
206impl From<DataStagePlanError> for IcSnapshotDownloadPlanningError {
207 fn from(error: DataStagePlanError) -> Self {
208 match error {
209 DataStagePlanError::CountOverflow => Self::CountOverflow,
210 DataStagePlanError::Graph(error) => Self::Graph(error),
211 DataStagePlanError::Plan(error) => Self::Plan(error),
212 DataStagePlanError::Budget(error) => Self::Budget(error),
213 }
214 }
215}