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