ic_backup/workflow/ic_snapshot_restore/source/
mod.rs1use crate::model::data_stage_plan::SnapshotDataPlanningError;
4use crate::{
5 model::{
6 attempt_journal::AppliedReceiptEvidence,
7 execution_workflow::ExecutionStagePredecessorRecord,
8 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
9 ic_snapshot_metadata::IcSnapshotMetadataRequest,
10 ic_snapshot_upload::{IcSnapshotDataUploadPlan, UploadCompletion},
11 operation_plan::OperationPlanRecord,
12 },
13 ops::persistence::{
14 AttemptJournalError, AttemptJournalGuard, BackupLayoutGuard, ExecutionStageCheckpointError,
15 ExecutionStageGuard, ExecutionWorkflowPersistenceError,
16 },
17};
18use thiserror::Error;
19
20#[derive(Clone)]
27pub struct IcRestoreSource<'a> {
28 pub(super) layout: &'a BackupLayoutGuard,
29 pub(super) plan: &'a OperationPlanRecord,
30 target: String,
31 snapshot_id: Vec<u8>,
32 upload: Option<UploadOrigin<'a>>,
33}
34
35#[derive(Clone)]
36struct UploadOrigin<'a> {
37 layout: &'a BackupLayoutGuard,
38 workflow: crate::model::artifacts::ArtifactChecksumRecord,
39 completed: ExecutionStagePredecessorRecord,
40 completion: UploadCompletion,
41}
42
43impl<'a> IcRestoreSource<'a> {
44 #[must_use]
49 pub fn existing(
50 layout: &'a BackupLayoutGuard,
51 plan: &'a OperationPlanRecord,
52 metadata: &IcSnapshotMetadataRequest,
53 ) -> Self {
54 Self {
55 layout,
56 plan,
57 target: metadata.target().into(),
58 snapshot_id: metadata.snapshot_id().into(),
59 upload: None,
60 }
61 }
62
63 pub fn uploaded(
71 layout: &'a BackupLayoutGuard,
72 upload: &IcSnapshotDataUploadPlan<'_, '_, 'a>,
73 workflow_layout: &'a BackupLayoutGuard,
74 completed: &ExecutionStagePredecessorRecord,
75 ) -> Result<Self, IcRestoreSourceError> {
76 let stage = ExecutionStageGuard::open(
77 workflow_layout,
78 &upload.workflow_digest(),
79 completed.stage_sequence(),
80 completed.binding(),
81 )?;
82 let completion = upload.validate_completion(
83 stage.binding(),
84 stage.plan(),
85 completed.learned_evidence(),
86 )?;
87 let origin = UploadOrigin {
88 layout: workflow_layout,
89 workflow: upload.workflow_digest(),
90 completed: completed.clone(),
91 completion,
92 };
93 origin.replay(&stage)?;
94 Ok(Self {
95 layout,
96 plan: upload.metadata().source_plan(),
97 target: upload.metadata().target().into(),
98 snapshot_id: upload.destination().into(),
99 upload: Some(origin),
100 })
101 }
102
103 pub(crate) fn validate(
104 &self,
105 stage: &ExecutionStageGuard<'_>,
106 wire: &IcManagementRequestRecord,
107 ) -> Result<(), IcRestoreSourceError> {
108 if wire.target() != self.target
109 || (wire.method() == IcManagementMethodRecord::LoadCanisterSnapshot
110 && wire.snapshot_id() != Some(self.snapshot_id.as_slice()))
111 {
112 return Err(IcRestoreSourceError::Mismatch);
113 }
114 if let Some(origin) = &self.upload {
115 if stage.binding().workflow() == &origin.workflow
116 && (wire.method() == IcManagementMethodRecord::LoadCanisterSnapshot
117 && !stage.binding().predecessors().contains(&origin.completed))
118 {
119 return Err(IcRestoreSourceError::Mismatch);
120 }
121 let upload = origin.open()?;
122 origin.replay(&upload)?;
123 }
124 Ok(())
125 }
126}
127
128impl UploadOrigin<'_> {
129 fn open(&self) -> Result<ExecutionStageGuard<'_>, ExecutionWorkflowPersistenceError> {
130 ExecutionStageGuard::open(
131 self.layout,
132 &self.workflow,
133 self.completed.stage_sequence(),
134 self.completed.binding(),
135 )
136 }
137
138 fn replay(&self, stage: &ExecutionStageGuard<'_>) -> Result<(), IcRestoreSourceError> {
139 if stage.replay_checkpoint(self.completed.learned_evidence().clone())? != self.completed {
140 return Err(IcRestoreSourceError::Mismatch);
141 }
142 if let UploadCompletion::Allocation {
143 sequence,
144 evidence: expected_evidence,
145 } = &self.completion
146 {
147 let authority = stage
148 .plan()
149 .attempt_authority(*sequence)
150 .map_err(|_| IcRestoreSourceError::Mismatch)?;
151 let journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
152 if !matches!(journal.record()?.applied_receipt(), Some(AppliedReceiptEvidence::Mutation { evidence, .. }) if evidence == expected_evidence.hash())
153 {
154 return Err(IcRestoreSourceError::Mismatch);
155 }
156 }
157 Ok(())
158 }
159}
160
161#[derive(Debug, Error)]
163pub enum IcRestoreSourceError {
164 #[error(transparent)]
166 Journal(#[from] AttemptJournalError),
167 #[error("restore snapshot source differs from its original upload or metadata")]
169 Mismatch,
170 #[error(transparent)]
172 Planning(#[from] SnapshotDataPlanningError),
173 #[error(transparent)]
175 Stage(#[from] ExecutionWorkflowPersistenceError),
176 #[error(transparent)]
178 Checkpoint(#[from] ExecutionStageCheckpointError),
179}