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 pub(crate) fn matches_metadata(&self, metadata: &IcSnapshotMetadataReply<'_>) -> bool {
87 self.metadata.digest() == metadata.digest()
88 }
89 #[must_use]
92 pub const fn plan(&self) -> Option<&OperationPlanRecord> {
93 self.plan.as_ref()
94 }
95
96 pub(crate) fn validate_binding(
99 &self,
100 binding: &ExecutionStageBindingRecord,
101 ) -> Result<(), IcSnapshotDownloadPlanningError> {
102 let plan = self
103 .plan
104 .as_ref()
105 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
106 binding.validate(self.workflow, plan)?;
107 if !binding.has_learned_evidence(self.sequence, &self.metadata.digest()) {
108 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
109 }
110 Ok(())
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 .as_ref()
130 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
131 metadata_binding.validate(self.workflow, metadata_plan)?;
132 if !metadata_binding.matches_learned_predecessor(
133 metadata_plan,
134 self.metadata.request().digest().hash(),
135 &self.metadata.digest(),
136 &predecessors,
137 ) {
138 return Err(IcSnapshotDownloadPlanningError::MetadataMismatch);
139 }
140 Ok(ExecutionStageBindingRecord::new(
141 self.workflow,
142 self.sequence,
143 plan,
144 predecessors,
145 )?)
146 }
147}
148
149#[derive(Debug, Error)]
151pub enum IcSnapshotDownloadPlanningError {
152 #[error("invalid snapshot download chunk size")]
154 InvalidChunkSize,
155 #[error("snapshot download request count overflow")]
157 CountOverflow,
158 #[error("snapshot download needs {required} updates; original allocation is {original}")]
160 InsufficientAllowance {
161 required: u64,
163 original: u32,
165 },
166 #[error("snapshot download metadata differs from original stage")]
168 MetadataMismatch,
169 #[error("snapshot download has no data reads to bind")]
171 NoDataReads,
172 #[error(transparent)]
174 Plan(#[from] OperationPlanError),
175 #[error(transparent)]
177 Graph(#[from] EffectGraphError),
178 #[error(transparent)]
180 Workflow(#[from] ExecutionWorkflowError),
181 #[error(transparent)]
183 Data(#[from] IcSnapshotDataError),
184 #[error(transparent)]
186 Budget(#[from] AttemptJournalRecordError),
187}
188
189#[cfg(test)]
190pub(crate) mod tests;
191
192impl From<ExtentPlanningError> for IcSnapshotDownloadPlanningError {
193 fn from(error: ExtentPlanningError) -> Self {
194 match error {
195 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
196 ExtentPlanningError::CountOverflow => Self::CountOverflow,
197 ExtentPlanningError::InsufficientAllowance { required, original } => {
198 Self::InsufficientAllowance { required, original }
199 }
200 }
201 }
202}
203
204impl From<DataStagePlanError> for IcSnapshotDownloadPlanningError {
205 fn from(error: DataStagePlanError) -> Self {
206 match error {
207 DataStagePlanError::CountOverflow => Self::CountOverflow,
208 DataStagePlanError::Graph(error) => Self::Graph(error),
209 DataStagePlanError::Plan(error) => Self::Plan(error),
210 DataStagePlanError::Budget(error) => Self::Budget(error),
211 }
212 }
213}