ic_backup/workflow/ic_snapshot_restore/
mod.rs1use crate::{
4 model::{
5 attempt_journal::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 async fn restore_snapshot<E: std::error::Error + 'static>(
59 stage: &ExecutionStageGuard<'_>,
60 source_layout: &BackupLayoutGuard,
61 source_plan: &OperationPlanRecord,
62 safety: &RestoreSafetyRequest<'_>,
63 provider: &mut impl IcMutationProvider,
64 admit: impl AsyncFnOnce(
65 &IcMutationRequest<'_>,
66 &RestoreSafetyRequest<'_>,
67 ) -> Result<RestoreSafetyObservation, E>,
68 qualify: impl AsyncFnOnce(
69 &IcMutationRequest<'_>,
70 &IcMutationAcknowledgement,
71 ) -> Result<MutationReceiptRequest, E>,
72) -> Result<(IcMutationAcknowledgement, ExecutionStagePredecessorRecord), IcRestoreExecutionError<E>>
73{
74 let plan = stage.plan();
75 let sequence = safety.binding().operation_sequence();
76 if plan.operations().len() != 1 || plan.operations()[0].operation_sequence() != sequence {
77 return Err(IcRestoreExecutionError::OriginalMismatch);
78 }
79 let retain = || {
80 read_restore_safety_requirement(
81 stage.layout()?,
82 source_layout,
83 plan,
84 source_plan,
85 &safety.requirement().digest(),
86 )?;
87 Ok::<_, IcRestoreReplyError<E>>(())
88 };
89 retain()?;
90 let authority = plan
91 .attempt_authority(sequence)
92 .map_err(IcMutationRequestError::from)?;
93 safety
94 .wire()
95 .validate_mutation_binding(authority.binding())
96 .map_err(IcMutationRequestError::from)?;
97 let mut journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
98 journal.reserve_planned_mutation(&plan.digest())?;
99 let request = IcMutationRequest::new(plan, sequence, journal.record()?, safety.wire())?;
100 let observation = admit(&request, safety)
101 .await
102 .map_err(IcRestoreExecutionError::Admission)?;
103 validate(safety, &observation)?;
104 retain()?;
105 let acknowledgement = provider
106 .submit_mutation(&request, journal.record()?)
107 .await?;
108 let settle = async || {
109 let admitted = validate_acknowledgement(&request, journal.record()?, &acknowledgement)?;
110 retain()?;
111 super::qualified_receipt::AppliedReceiptRecorder {
112 journal: &mut journal,
113 expected_attempt: request.mutation_attempt(),
114 expected_request: safety.wire().digest().hash(),
115 }
116 .record_applied(
117 &request,
118 &acknowledgement,
119 qualify,
120 IcRestoreReplyError::Qualification,
121 || IcRestoreReplyError::ReceiptRequired,
122 retain,
123 )
124 .await?;
125 retain()?;
126 let IcMutationReplyView::Lifecycle(reply) = admitted.reply() else {
127 return Err(IcRestoreReplyError::ReceiptRequired);
128 };
129 Ok(reply.digest())
130 };
131 let evidence = match settle().await {
132 Ok(evidence) => evidence,
133 Err(source) => {
134 return Err(IcRestoreExecutionError::AfterReply {
135 source,
136 acknowledgement: Box::new(acknowledgement),
137 });
138 }
139 };
140 drop(journal);
141 match stage.checkpoint(evidence) {
142 Ok(predecessor) => Ok((acknowledgement, predecessor)),
143 Err(source) => Err(IcRestoreExecutionError::AfterReply {
144 source: IcRestoreReplyError::Checkpoint(source),
145 acknowledgement: Box::new(acknowledgement),
146 }),
147 }
148}
149
150#[derive(Debug, Error)]
152pub enum IcRestoreExecutionError<E: std::error::Error + 'static> {
153 #[error("restore requires an exact singleton original load/start stage")]
155 OriginalMismatch,
156 #[error(transparent)]
158 Stage(#[from] ExecutionWorkflowPersistenceError),
159 #[error(transparent)]
161 Journal(#[from] AttemptJournalError),
162 #[error(transparent)]
164 Progress(#[from] ExecutionProgressPersistenceError),
165 #[error(transparent)]
167 Request(#[from] IcMutationRequestError),
168 #[error(transparent)]
170 Retention(#[from] IcRestoreReplyError<E>),
171 #[error("fresh restore admission failed: {0}")]
173 Admission(#[source] E),
174 #[error(transparent)]
176 Safety(#[from] RestoreSafetyError),
177 #[error(transparent)]
179 Provider(#[from] IcMutationProviderError),
180 #[error("restore reply settlement failed: {source}")]
182 AfterReply {
183 source: IcRestoreReplyError<E>,
185 acknowledgement: Box<IcMutationAcknowledgement>,
187 },
188}
189
190#[derive(Debug, Error)]
192pub enum IcRestoreReplyError<E: std::error::Error + 'static> {
193 #[error(transparent)]
195 Stage(#[from] ExecutionWorkflowPersistenceError),
196 #[error(transparent)]
198 Retention(#[from] RestoreSafetyPersistenceError),
199 #[error(transparent)]
201 Journal(#[from] AttemptJournalError),
202 #[error(transparent)]
204 Association(#[from] IcMutationAssociationError),
205 #[error("restore reply qualification failed: {0}")]
207 Qualification(#[source] E),
208 #[error("restore requires an explicit original Applied receipt")]
210 ReceiptRequired,
211 #[error(transparent)]
213 Checkpoint(#[from] ExecutionStageCheckpointError),
214}
215
216#[cfg(test)]
217mod tests;