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