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 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 let receipt = qualify(&request, &acknowledgement)
112 .await
113 .map_err(IcRestoreReplyError::Qualification)?;
114 if receipt.outcome != MutationOutcomeRecord::Applied
115 || receipt.attempt != request.mutation_attempt()
116 || receipt.request != safety.wire().digest().hash()
117 {
118 return Err(IcRestoreReplyError::ReceiptRequired);
119 }
120 retain()?;
121 journal.record_mutation(receipt)?;
122 retain()?;
123 let IcMutationReplyView::Lifecycle(reply) = admitted.reply() else {
124 return Err(IcRestoreReplyError::ReceiptRequired);
125 };
126 Ok(reply.digest())
127 };
128 let evidence = match settle().await {
129 Ok(evidence) => evidence,
130 Err(source) => {
131 return Err(IcRestoreExecutionError::AfterReply {
132 source,
133 acknowledgement: Box::new(acknowledgement),
134 });
135 }
136 };
137 drop(journal);
138 match stage.checkpoint(evidence) {
139 Ok(predecessor) => Ok((acknowledgement, predecessor)),
140 Err(source) => Err(IcRestoreExecutionError::AfterReply {
141 source: IcRestoreReplyError::Checkpoint(source),
142 acknowledgement: Box::new(acknowledgement),
143 }),
144 }
145}
146
147#[derive(Debug, Error)]
149pub enum IcRestoreExecutionError<E: std::error::Error + 'static> {
150 #[error("restore requires an exact singleton original load/start stage")]
152 OriginalMismatch,
153 #[error(transparent)]
155 Stage(#[from] ExecutionWorkflowPersistenceError),
156 #[error(transparent)]
158 Journal(#[from] AttemptJournalError),
159 #[error(transparent)]
161 Progress(#[from] ExecutionProgressPersistenceError),
162 #[error(transparent)]
164 Request(#[from] IcMutationRequestError),
165 #[error(transparent)]
167 Retention(#[from] IcRestoreReplyError<E>),
168 #[error("fresh restore admission failed: {0}")]
170 Admission(#[source] E),
171 #[error(transparent)]
173 Safety(#[from] RestoreSafetyError),
174 #[error(transparent)]
176 Provider(#[from] IcMutationProviderError),
177 #[error("restore reply settlement failed: {source}")]
179 AfterReply {
180 source: IcRestoreReplyError<E>,
182 acknowledgement: Box<IcMutationAcknowledgement>,
184 },
185}
186
187#[derive(Debug, Error)]
189pub enum IcRestoreReplyError<E: std::error::Error + 'static> {
190 #[error(transparent)]
192 Stage(#[from] ExecutionWorkflowPersistenceError),
193 #[error(transparent)]
195 Retention(#[from] RestoreSafetyPersistenceError),
196 #[error(transparent)]
198 Journal(#[from] AttemptJournalError),
199 #[error(transparent)]
201 Association(#[from] IcMutationAssociationError),
202 #[error("restore reply qualification failed: {0}")]
204 Qualification(#[source] E),
205 #[error("restore requires an explicit original Applied receipt")]
207 ReceiptRequired,
208 #[error(transparent)]
210 Checkpoint(#[from] ExecutionStageCheckpointError),
211}
212
213#[cfg(test)]
214mod tests;