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},
18};
19use ic_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}
38impl<'workflow, 'request, 'source> IcSnapshotDataUploadPlan<'workflow, 'request, 'source> {
39 pub(crate) fn derive_kinds(
40 workflow: &ExecutionWorkflowRecord,
41 sequence: u64,
42 allocation: &IcSnapshotUploadReply<'_, '_>,
43 chunk_bytes: u64,
44 ) -> Result<Vec<SnapshotDataKind>, IcSnapshotDataUploadPlanningError> {
45 let metadata = allocation.request();
46 let stage = workflow.stage(sequence)?;
47 let IcSnapshotUploadReplyKind::Metadata { snapshot_id } = allocation.kind() else {
48 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
49 };
50 metadata.validate_data_destination(snapshot_id)?;
51 if stage.target() != metadata.target()
52 || !super::attempt::source_context_matches(workflow.allocation(), metadata)
53 {
54 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
55 }
56 Ok(planned_extents(
57 metadata.source(),
58 chunk_bytes,
59 stage.budget().mutations(),
60 )?)
61 }
62
63 pub(crate) fn from_bindings(
64 workflow: &'workflow ExecutionWorkflowRecord,
65 sequence: u64,
66 allocation: &IcSnapshotUploadReply<'request, 'source>,
67 kinds: Vec<SnapshotDataKind>,
68 bindings: &[ArtifactChecksumRecord],
69 ) -> Result<Self, IcSnapshotDataUploadPlanningError> {
70 if bindings.len() != kinds.len() {
73 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
74 }
75 let IcSnapshotUploadReplyKind::Metadata { snapshot_id } = allocation.kind() else {
76 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
77 };
78 let stage = workflow.stage(sequence)?;
79 let plan = data_stage_plan::build(workflow.allocation(), stage, bindings.iter().cloned())?;
80 Ok(Self {
81 workflow,
82 sequence,
83 metadata: allocation.request(),
84 destination: snapshot_id.clone(),
85 allocation_evidence: allocation.digest(),
86 kinds,
87 plan,
88 })
89 }
90
91 #[must_use]
93 pub fn kinds(&self) -> &[SnapshotDataKind] {
94 &self.kinds
95 }
96 #[must_use]
98 pub const fn metadata(&self) -> &'request IcSnapshotUploadRequest<'source> {
99 self.metadata
100 }
101 #[must_use]
103 pub fn destination(&self) -> &[u8] {
104 &self.destination
105 }
106 #[must_use]
108 pub const fn plan(&self) -> Option<&OperationPlanRecord> {
109 self.plan.as_ref()
110 }
111
112 pub fn bind(
120 &self,
121 allocation_binding: &ExecutionStageBindingRecord,
122 allocation_plan: &OperationPlanRecord,
123 predecessors: Vec<ExecutionStagePredecessorRecord>,
124 ) -> Result<ExecutionStageBindingRecord, IcSnapshotDataUploadPlanningError> {
125 let plan = self
126 .plan
127 .as_ref()
128 .ok_or(IcSnapshotDataUploadPlanningError::NoDataWrites)?;
129 allocation_binding.validate(self.workflow, allocation_plan)?;
130 if allocation_plan.operations().len() != 1
131 || allocation_plan.operations()[0].request() != self.metadata.binding_digest().hash()
132 || !predecessors.iter().any(|row| {
133 row.stage_sequence() == allocation_binding.stage_sequence()
134 && row.binding() == &allocation_binding.digest()
135 && row.learned_evidence() == &self.allocation_evidence
136 })
137 {
138 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
139 }
140 Ok(ExecutionStageBindingRecord::new(
141 self.workflow,
142 self.sequence,
143 plan,
144 predecessors,
145 )?)
146 }
147 pub(crate) fn validate_binding(
148 &self,
149 binding: &ExecutionStageBindingRecord,
150 ) -> Result<(), IcSnapshotDataUploadPlanningError> {
151 let plan = self
152 .plan
153 .as_ref()
154 .ok_or(IcSnapshotDataUploadPlanningError::NoDataWrites)?;
155 binding.validate(self.workflow, plan)?;
156 if binding.stage_sequence() != self.sequence
157 || !binding
158 .predecessors()
159 .iter()
160 .any(|row| row.learned_evidence() == &self.allocation_evidence)
161 {
162 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch);
163 }
164 Ok(())
165 }
166}
167
168#[derive(Debug, Error)]
170pub enum IcSnapshotDataUploadPlanningError {
171 #[error("invalid snapshot data upload chunk size")]
173 InvalidChunkSize,
174 #[error("snapshot data upload count overflow")]
176 CountOverflow,
177 #[error("snapshot data upload needs {required} updates; original allocation is {original}")]
179 InsufficientAllowance {
180 required: u64,
182 original: u32,
184 },
185 #[error("snapshot data upload originals differ")]
187 OriginalMismatch,
188 #[error("snapshot data upload has no data writes to bind")]
190 NoDataWrites,
191 #[error(transparent)]
193 Plan(#[from] OperationPlanError),
194 #[error(transparent)]
196 Graph(#[from] EffectGraphError),
197 #[error(transparent)]
199 Workflow(#[from] ExecutionWorkflowError),
200 #[error(transparent)]
202 Upload(#[from] IcSnapshotUploadError),
203 #[error(transparent)]
205 Budget(#[from] AttemptJournalRecordError),
206}
207impl From<ExtentPlanningError> for IcSnapshotDataUploadPlanningError {
208 fn from(error: ExtentPlanningError) -> Self {
209 match error {
210 ExtentPlanningError::InvalidChunkSize => Self::InvalidChunkSize,
211 ExtentPlanningError::CountOverflow => Self::CountOverflow,
212 ExtentPlanningError::InsufficientAllowance { required, original } => {
213 Self::InsufficientAllowance { required, original }
214 }
215 }
216 }
217}
218
219impl From<DataStagePlanError> for IcSnapshotDataUploadPlanningError {
220 fn from(error: DataStagePlanError) -> Self {
221 match error {
222 DataStagePlanError::CountOverflow => Self::CountOverflow,
223 DataStagePlanError::Graph(error) => Self::Graph(error),
224 DataStagePlanError::Plan(error) => Self::Plan(error),
225 DataStagePlanError::Budget(error) => Self::Budget(error),
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests;