ic_backup/workflow/ic_snapshot_restore/
mod.rs1use crate::{
4 model::{
5 attempt_journal::{MutationOutcomeRecord, MutationReceiptRequest},
6 execution_workflow::ExecutionStagePredecessorRecord,
7 ic_mutation::{IcMutationAcknowledgement, IcMutationRequest, IcMutationRequestError},
8 operation_plan::OperationPlanRecord,
9 restore_safety::{RestoreSafetyObservation, RestoreSafetyRequest},
10 },
11 ops::persistence::{
12 AttemptJournalError, AttemptJournalGuard, BackupLayoutGuard,
13 ExecutionProgressPersistenceError, ExecutionStageCheckpointError, ExecutionStageGuard,
14 ExecutionWorkflowPersistenceError, RestoreSafetyPersistenceError,
15 read_restore_safety_requirement,
16 },
17 policy::{
18 ic_mutation::{IcMutationAssociationError, IcMutationReplyView, validate_acknowledgement},
19 restore_safety::{RestoreSafetyError, validate},
20 },
21 ports::ic_mutation::{IcMutationProvider, IcMutationProviderError},
22};
23use thiserror::Error;
24
25pub fn restore_snapshot<E: std::error::Error + 'static>(
54 stage: &ExecutionStageGuard<'_>,
55 source_layout: &BackupLayoutGuard,
56 source_plan: &OperationPlanRecord,
57 safety: &RestoreSafetyRequest<'_>,
58 provider: &mut impl IcMutationProvider,
59 admit: impl FnOnce(
60 &IcMutationRequest<'_>,
61 &RestoreSafetyRequest<'_>,
62 ) -> Result<RestoreSafetyObservation, E>,
63 qualify: impl FnOnce(
64 &IcMutationRequest<'_>,
65 &IcMutationAcknowledgement,
66 ) -> Result<MutationReceiptRequest, E>,
67) -> Result<(IcMutationAcknowledgement, ExecutionStagePredecessorRecord), IcRestoreExecutionError<E>>
68{
69 let plan = stage.plan();
70 let sequence = safety.binding().operation_sequence();
71 if plan.operations().len() != 1 || plan.operations()[0].operation_sequence() != sequence {
72 return Err(IcRestoreExecutionError::OriginalMismatch);
73 }
74 let retain = || {
75 read_restore_safety_requirement(
76 stage.layout()?,
77 source_layout,
78 plan,
79 source_plan,
80 &safety.requirement().digest(),
81 )?;
82 Ok::<_, IcRestoreReplyError<E>>(())
83 };
84 retain()?;
85 let authority = plan
86 .attempt_authority(sequence)
87 .map_err(IcMutationRequestError::from)?;
88 safety
89 .wire()
90 .validate_mutation_binding(authority.binding())
91 .map_err(IcMutationRequestError::from)?;
92 let mut journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
93 journal.reserve_planned_mutation(&plan.digest())?;
94 let request = IcMutationRequest::new(plan, sequence, journal.record()?, safety.wire())?;
95 let observation = admit(&request, safety).map_err(IcRestoreExecutionError::Admission)?;
96 validate(safety, &observation)?;
97 retain()?;
98 let acknowledgement = provider.submit_mutation(&request)?;
99 let settle = || {
100 let admitted = validate_acknowledgement(&request, journal.record()?, &acknowledgement)?;
101 retain()?;
102 let receipt =
103 qualify(&request, &acknowledgement).map_err(IcRestoreReplyError::Qualification)?;
104 if receipt.outcome != MutationOutcomeRecord::Applied
105 || receipt.attempt != request.mutation_attempt()
106 || receipt.request != safety.wire().digest().hash()
107 {
108 return Err(IcRestoreReplyError::ReceiptRequired);
109 }
110 retain()?;
111 journal.record_mutation(receipt)?;
112 retain()?;
113 let IcMutationReplyView::Lifecycle(reply) = admitted.reply() else {
114 return Err(IcRestoreReplyError::ReceiptRequired);
115 };
116 Ok(reply.digest())
117 };
118 let evidence = match settle() {
119 Ok(evidence) => evidence,
120 Err(source) => {
121 return Err(IcRestoreExecutionError::AfterReply {
122 source,
123 acknowledgement: Box::new(acknowledgement),
124 });
125 }
126 };
127 drop(journal);
128 match stage.checkpoint(evidence) {
129 Ok(predecessor) => Ok((acknowledgement, predecessor)),
130 Err(source) => Err(IcRestoreExecutionError::AfterReply {
131 source: IcRestoreReplyError::Checkpoint(source),
132 acknowledgement: Box::new(acknowledgement),
133 }),
134 }
135}
136
137#[derive(Debug, Error)]
139pub enum IcRestoreExecutionError<E: std::error::Error + 'static> {
140 #[error("restore requires an exact singleton original load/start stage")]
142 OriginalMismatch,
143 #[error(transparent)]
145 Stage(#[from] ExecutionWorkflowPersistenceError),
146 #[error(transparent)]
148 Journal(#[from] AttemptJournalError),
149 #[error(transparent)]
151 Progress(#[from] ExecutionProgressPersistenceError),
152 #[error(transparent)]
154 Request(#[from] IcMutationRequestError),
155 #[error(transparent)]
157 Retention(#[from] IcRestoreReplyError<E>),
158 #[error("fresh restore admission failed: {0}")]
160 Admission(#[source] E),
161 #[error(transparent)]
163 Safety(#[from] RestoreSafetyError),
164 #[error(transparent)]
166 Provider(#[from] IcMutationProviderError),
167 #[error("restore reply settlement failed: {source}")]
169 AfterReply {
170 source: IcRestoreReplyError<E>,
172 acknowledgement: Box<IcMutationAcknowledgement>,
174 },
175}
176
177#[derive(Debug, Error)]
179pub enum IcRestoreReplyError<E: std::error::Error + 'static> {
180 #[error(transparent)]
182 Stage(#[from] ExecutionWorkflowPersistenceError),
183 #[error(transparent)]
185 Retention(#[from] RestoreSafetyPersistenceError),
186 #[error(transparent)]
188 Journal(#[from] AttemptJournalError),
189 #[error(transparent)]
191 Association(#[from] IcMutationAssociationError),
192 #[error("restore reply qualification failed: {0}")]
194 Qualification(#[source] E),
195 #[error("restore requires an explicit original Applied receipt")]
197 ReceiptRequired,
198 #[error(transparent)]
200 Checkpoint(#[from] ExecutionStageCheckpointError),
201}
202
203#[cfg(test)]
204mod tests;