ic_backup/workflow/ic_snapshot_restore/
mod.rs1mod source;
4pub use source::{IcRestoreSource, IcRestoreSourceError};
5
6use crate::{
7 model::{
8 attempt_journal::MutationReceiptRequest,
9 execution_workflow::ExecutionStagePredecessorRecord,
10 ic_mutation::{IcMutationAcknowledgement, IcMutationRequest, IcMutationRequestError},
11 restore_safety::{RestoreSafetyObservation, RestoreSafetyRequest},
12 },
13 ops::persistence::{
14 AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
15 ExecutionStageCheckpointError, ExecutionStageGuard, ExecutionWorkflowPersistenceError,
16 RestoreSafetyPersistenceError, read_restore_safety_requirement,
17 },
18 policy::{
19 ic_mutation::{IcMutationAssociationError, IcMutationReplyView, validate_acknowledgement},
20 restore_safety::{RestoreSafetyError, validate},
21 },
22 ports::ic_mutation::{IcMutationProvider, IcMutationProviderError},
23};
24use thiserror::Error;
25
26pub async fn restore_snapshot<E: std::error::Error + 'static>(
62 stage: &ExecutionStageGuard<'_>,
63 source: IcRestoreSource<'_>,
64 safety: &RestoreSafetyRequest<'_>,
65 provider: &mut impl IcMutationProvider,
66 admit: impl AsyncFnOnce(
67 &IcMutationRequest<'_>,
68 &RestoreSafetyRequest<'_>,
69 ) -> Result<RestoreSafetyObservation, E>,
70 qualify: impl AsyncFnOnce(
71 &IcMutationRequest<'_>,
72 &IcMutationAcknowledgement,
73 ) -> Result<MutationReceiptRequest, E>,
74) -> Result<(IcMutationAcknowledgement, ExecutionStagePredecessorRecord), IcRestoreExecutionError<E>>
75{
76 let plan = stage.plan();
77 let sequence = safety.binding().operation_sequence();
78 if plan.operations().len() != 1 || plan.operations()[0].operation_sequence() != sequence {
79 return Err(IcRestoreExecutionError::OriginalMismatch);
80 }
81 let retain = || {
82 source.validate(stage, safety.wire())?;
83 read_restore_safety_requirement(
84 stage.layout()?,
85 source.layout,
86 plan,
87 source.plan,
88 &safety.requirement().digest(),
89 )?;
90 Ok::<_, IcRestoreReplyError<E>>(())
91 };
92 retain()?;
93 let authority = plan
94 .attempt_authority(sequence)
95 .map_err(IcMutationRequestError::from)?;
96 safety
97 .wire()
98 .validate_mutation_binding(authority.binding())
99 .map_err(IcMutationRequestError::from)?;
100 let mut journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
101 journal.reserve_planned_mutation(&plan.digest())?;
102 let request = IcMutationRequest::new(plan, sequence, journal.record()?, safety.wire())?;
103 let observation = admit(&request, safety)
104 .await
105 .map_err(IcRestoreExecutionError::Admission)?;
106 validate(safety, &observation)?;
107 retain()?;
108 let acknowledgement = provider
109 .submit_mutation(&request, journal.record()?)
110 .await?;
111 let settle = async || {
112 let admitted = validate_acknowledgement(&request, journal.record()?, &acknowledgement)?;
113 retain()?;
114 let receipt = qualify(&request, &acknowledgement)
115 .await
116 .map_err(IcRestoreReplyError::Qualification)?;
117 let receipt = super::qualified_receipt::admit_applied(
118 receipt,
119 request.mutation_attempt(),
120 safety.wire().digest().hash(),
121 )
122 .ok_or(IcRestoreReplyError::ReceiptRequired)?;
123 retain()?;
124 journal.record_mutation(receipt)?;
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.reconcile_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 Source(#[from] IcRestoreSourceError),
196 #[error(transparent)]
198 Stage(#[from] ExecutionWorkflowPersistenceError),
199 #[error(transparent)]
201 Retention(#[from] RestoreSafetyPersistenceError),
202 #[error(transparent)]
204 Journal(#[from] AttemptJournalError),
205 #[error(transparent)]
207 Association(#[from] IcMutationAssociationError),
208 #[error("restore reply qualification failed: {0}")]
210 Qualification(#[source] E),
211 #[error("restore requires an explicit original Applied receipt")]
213 ReceiptRequired,
214 #[error(transparent)]
216 Checkpoint(#[from] ExecutionStageCheckpointError),
217}
218
219#[cfg(test)]
220mod tests;