ic_backup/model/ic_snapshot_upload/plan/
mod.rs1use super::{
4 IcSnapshotUploadError, IcSnapshotUploadReply, IcSnapshotUploadReplyKind,
5 IcSnapshotUploadRequest,
6};
7use crate::model::{
8 artifacts::ArtifactChecksumRecord,
9 attempt_journal::AttemptJournalRecordError,
10 data_stage_plan::{self, DataStagePlanError},
11 effect_graph::EffectGraphError,
12 execution_workflow::{
13 ExecutionStageBindingRecord, ExecutionStagePredecessorRecord, ExecutionWorkflowError,
14 ExecutionWorkflowRecord,
15 },
16 ic_snapshot_data::{ExtentPlanningError, planned_extents},
17 operation_plan::{OperationPlanError, OperationPlanRecord, PlannedOperationRecord},
18};
19use ic_platform::management_canister_types::SnapshotDataKind;
20use thiserror::Error;
21
22pub struct IcSnapshotDataUploadPlan<'workflow, 'request, 'source> {
30 workflow: &'workflow ExecutionWorkflowRecord,
31 sequence: u64,
32 metadata: &'request IcSnapshotUploadRequest<'source>,
33 destination: Vec<u8>,
34 allocation_evidence: ArtifactChecksumRecord,
35 kinds: Vec<SnapshotDataKind>,
36 plan: Option<OperationPlanRecord>,
37}
38pub(crate) struct DataUploadPreparation<'workflow, 'request, 'source> {
41 workflow: &'workflow ExecutionWorkflowRecord,
42 stage: &'workflow PlannedOperationRecord,
43 metadata: &'request IcSnapshotUploadRequest<'source>,
44 destination: Vec<u8>,
45 allocation_evidence: ArtifactChecksumRecord,
46 kinds: Vec<SnapshotDataKind>,
47}
48
49impl<'workflow, 'request, 'source> DataUploadPreparation<'workflow, 'request, 'source> {
50 pub(crate) fn new(
51 workflow: &'workflow ExecutionWorkflowRecord,
52 sequence: u64,
53 allocation: &IcSnapshotUploadReply<'request, 'source>,
54 chunk_bytes: u64,
55 ) -> Result<Self, IcSnapshotDataUploadPlanningError> {
56 let metadata = allocation.request();
57 let stage = workflow.stage(sequence)?;
58 let IcSnapshotUploadReplyKind::Metadata { snapshot_id } = allocation.kind() else {
59 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
60 };
61 metadata.validate_data_destination(snapshot_id)?;
62 if stage.target() != metadata.target()
63 || !super::attempt::source_context_matches(workflow.allocation(), metadata)
64 {
65 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
66 }
67 let kinds = planned_extents(metadata.source(), chunk_bytes, stage.budget().mutations())?;
68 Ok(Self {
69 workflow,
70 stage,
71 metadata,
72 destination: snapshot_id.clone(),
73 allocation_evidence: allocation.digest(),
74 kinds,
75 })
76 }
77
78 pub(crate) const fn metadata(&self) -> &'request IcSnapshotUploadRequest<'source> {
79 self.metadata
80 }
81 pub(crate) fn destination(&self) -> &[u8] {
82 &self.destination
83 }
84 pub(crate) fn kinds(&self) -> &[SnapshotDataKind] {
85 &self.kinds
86 }
87
88 pub(crate) fn finish(
89 self,
90 bindings: &[ArtifactChecksumRecord],
91 ) -> Result<
92 IcSnapshotDataUploadPlan<'workflow, 'request, 'source>,
93 IcSnapshotDataUploadPlanningError,
94 > {
95 if bindings.len() != self.kinds.len() {
96 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
97 }
98 let plan = data_stage_plan::build(
99 self.workflow.allocation(),
100 self.stage,
101 bindings.iter().cloned(),
102 )?;
103 Ok(IcSnapshotDataUploadPlan {
104 workflow: self.workflow,
105 sequence: self.stage.operation_sequence(),
106 metadata: self.metadata,
107 destination: self.destination,
108 allocation_evidence: self.allocation_evidence,
109 kinds: self.kinds,
110 plan,
111 })
112 }
113}
114
115impl<'request, 'source> IcSnapshotDataUploadPlan<'_, 'request, 'source> {
116 #[must_use]
118 pub fn kinds(&self) -> &[SnapshotDataKind] {
119 &self.kinds
120 }
121 #[must_use]
123 pub const fn metadata(&self) -> &'request IcSnapshotUploadRequest<'source> {
124 self.metadata
125 }
126 #[must_use]
128 pub fn destination(&self) -> &[u8] {
129 &self.destination
130 }
131 #[must_use]
133 pub const fn plan(&self) -> Option<&OperationPlanRecord> {
134 self.plan.as_ref()
135 }
136
137 pub fn bind(
145 &self,
146 allocation_binding: &ExecutionStageBindingRecord,
147 allocation_plan: &OperationPlanRecord,
148 predecessors: Vec<ExecutionStagePredecessorRecord>,
149 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDataUploadPlanningError> {
150 let plan = self
151 .plan
152 .as_ref()
153 .ok_or(IcSnapshotDataUploadPlanningError::NoDataWrites)?;
154 allocation_binding.validate(self.workflow, allocation_plan)?;
155 if !allocation_binding.matches_learned_predecessor(
156 allocation_plan,
157 self.metadata.binding_digest().hash(),
158 &self.allocation_evidence,
159 &predecessors,
160 ) {
161 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
162 }
163 Ok(ExecutionStageBindingRecord::new(
164 self.workflow,
165 self.sequence,
166 plan,
167 predecessors,
168 )?)
169 }
170 pub(crate) fn validate_binding(
171 &self,
172 binding: &ExecutionStageBindingRecord,
173 ) -> Result<(), IcSnapshotDataUploadPlanningError> {
174 let plan = self
175 .plan
176 .as_ref()
177 .ok_or(IcSnapshotDataUploadPlanningError::NoDataWrites)?;
178 binding.validate(self.workflow, plan)?;
179 if !binding.has_learned_evidence(self.sequence, &self.allocation_evidence) {
180 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
181 }
182 Ok(())
183 }
184}
185
186#[derive(Debug, Error)]
188pub enum IcSnapshotDataUploadPlanningError {
189 #[error("invalid snapshot data upload chunk size")]
191 InvalidChunkSize,
192 #[error("snapshot data upload count overflow")]
194 CountOverflow,
195 #[error("snapshot data upload needs {required} updates; original allocation is {original}")]
197 InsufficientAllowance {
198 required: u64,
200 original: u32,
202 },
203 #[error("snapshot data upload originals differ")]
205 OriginalMismatch,
206 #[error("snapshot data upload has no data writes to bind")]
208 NoDataWrites,
209 #[error(transparent)]
211 Plan(#[from] OperationPlanError),
212 #[error(transparent)]
214 Graph(#[from] EffectGraphError),
215 #[error(transparent)]
217 Workflow(#[from] ExecutionWorkflowError),
218 #[error(transparent)]
220 Upload(#[from] IcSnapshotUploadError),
221 #[error(transparent)]
223 Budget(#[from] AttemptJournalRecordError),
224}
225impl From<ExtentPlanningError> for IcSnapshotDataUploadPlanningError {
226 fn from(error: ExtentPlanningError) -> Self {
227 match error {
228 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
229 ExtentPlanningError::CountOverflow => Self::CountOverflow,
230 ExtentPlanningError::InsufficientAllowance { required, original } => {
231 Self::InsufficientAllowance { required, original }
232 }
233 }
234 }
235}
236
237impl From<DataStagePlanError> for IcSnapshotDataUploadPlanningError {
238 fn from(error: DataStagePlanError) -> Self {
239 match error {
240 DataStagePlanError::CountOverflow => Self::CountOverflow,
241 DataStagePlanError::Graph(error) => Self::Graph(error),
242 DataStagePlanError::Plan(error) => Self::Plan(error),
243 DataStagePlanError::Budget(error) => Self::Budget(error),
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests;