ic_backup/workflow/ic_snapshot_download/
mod.rs1use crate::model::data_stage_plan::SnapshotDataPlanningError;
4use crate::{
5 model::{
6 artifacts::ArtifactChecksumRecord,
7 attempt_journal::MutationReceiptRequest,
8 ic_snapshot_download::IcSnapshotDownloadPlan,
9 ic_snapshot_transfer_read::{
10 IcSnapshotTransferReadPayload, IcSnapshotTransferReadRequest,
11 IcSnapshotTransferReadResponse,
12 },
13 },
14 ops::persistence::{
15 AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
16 ExecutionStageGuard, ExecutionWorkflowPersistenceError, IcSnapshotArtifactError,
17 IcSnapshotArtifactWriter, read_execution_progress,
18 },
19 policy::ic_snapshot_transfer_read::{IcSnapshotTransferReadReply, IcSnapshotTransferReadView},
20 ports::ic_snapshot_transfer_read::IcSnapshotTransferReadProvider,
21 workflow::ic_snapshot_transfer_read::{
22 IcSnapshotTransferReadExecutionError, read_snapshot_with,
23 },
24};
25use thiserror::Error;
26
27pub async fn download_snapshot<E: std::error::Error + 'static>(
58 stage: &ExecutionStageGuard<'_>,
59 download: &IcSnapshotDownloadPlan<'_, '_>,
60 mut writer: IcSnapshotArtifactWriter<'_, '_, '_>,
61 provider: &mut impl IcSnapshotTransferReadProvider,
62 mut admit: impl AsyncFnMut(&IcSnapshotTransferReadRequest<'_, '_>) -> Result<(), E>,
63 mut qualify: impl AsyncFnMut(
64 &IcSnapshotTransferReadRequest<'_, '_>,
65 &IcSnapshotTransferReadResponse,
66 ) -> Result<MutationReceiptRequest, E>,
67) -> Result<ArtifactChecksumRecord, IcSnapshotDownloadExecutionError<E>> {
68 download.validate_binding(stage.binding())?;
69 let plan = download
70 .plan()
71 .ok_or(SnapshotDataPlanningError::NoDataOperations)?;
72 if stage.plan() != plan
73 || !download.matches_metadata(writer.coverage().metadata())
74 || writer.coverage().covered_region_bytes() != [0; 3]
75 || writer.coverage().covered_chunks() != 0
76 {
77 return Err(IcSnapshotDownloadExecutionError::OriginalMismatch);
78 }
79 let intent = plan.digest();
80 writer.validate_transfer_origin(stage.layout()?.root(), intent.hash())?;
81 let progress = read_execution_progress(stage.layout()?, &intent)?;
82 if !progress.is_unattempted() {
83 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
84 }
85 for (operation, payload) in plan.operations().iter().zip(download.requests()) {
87 let sequence = operation.operation_sequence();
88 let payload = payload.map_err(SnapshotDataPlanningError::from)?;
89 let (response, result) = read_snapshot_with(
90 stage,
91 sequence,
92 IcSnapshotTransferReadPayload::Data(&payload),
93 provider,
94 &mut admit,
95 async |journal, request, admitted| {
96 append_qualified(stage, journal, request, admitted, writer, &mut qualify).await
97 },
98 )
99 .await?;
100 writer = result.map_err(|source| IcSnapshotDownloadExecutionError::AfterReply {
101 operation_sequence: sequence,
102 source,
103 response: Box::new(response),
104 })?;
105 }
106 let progress = read_execution_progress(stage.layout()?, &intent)?;
107 if !progress.all_applied() {
108 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
109 }
110 writer.validate_transfer_origin(stage.layout()?.root(), intent.hash())?;
111 let checksum = writer.finish()?;
112 if let Err(source) = stage.layout() {
113 return Err(IcSnapshotDownloadExecutionError::AfterPublication { source, checksum });
114 }
115 Ok(checksum)
116}
117
118async fn append_qualified<'journal, 'layout, 'metadata, E: std::error::Error + 'static>(
119 stage: &ExecutionStageGuard<'_>,
120 journal: &mut AttemptJournalGuard<'_>,
121 request: &IcSnapshotTransferReadRequest<'_, '_>,
122 admitted: &IcSnapshotTransferReadView<'_, '_>,
123 writer: IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>,
124 qualify: &mut impl AsyncFnMut(
125 &IcSnapshotTransferReadRequest<'_, '_>,
126 &IcSnapshotTransferReadResponse,
127 ) -> Result<MutationReceiptRequest, E>,
128) -> Result<IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>, IcSnapshotDownloadReplyError<E>>
129{
130 let receipt = qualify(request, admitted.response())
131 .await
132 .map_err(IcSnapshotDownloadReplyError::Qualification)?;
133 let writer = super::qualified_receipt::record_applied(
134 journal,
135 receipt,
136 request.mutation_attempt(),
137 request.payload().digest().hash(),
138 IcSnapshotDownloadReplyError::ReceiptRequired,
139 || {
140 writer.validate_transfer_origin(
142 stage.layout()?.root(),
143 request.authority().binding().intent(),
144 )?;
145 let IcSnapshotTransferReadReply::Data(reply) = admitted.reply() else {
146 return Err(IcSnapshotDownloadReplyError::ReceiptRequired);
147 };
148 Ok(writer.append(reply)?)
149 },
150 )?;
151 stage.layout()?;
152 Ok(writer)
153}
154
155#[derive(Debug, Error)]
157pub enum IcSnapshotDownloadExecutionError<E: std::error::Error + 'static> {
158 #[error("snapshot download originals differ")]
160 OriginalMismatch,
161 #[error("snapshot download original data stage was already attempted")]
163 AlreadyAttempted,
164 #[error(transparent)]
166 Planning(#[from] SnapshotDataPlanningError),
167 #[error(transparent)]
169 Stage(#[from] ExecutionWorkflowPersistenceError),
170 #[error(transparent)]
172 Progress(#[from] ExecutionProgressPersistenceError),
173 #[error(transparent)]
175 Read(#[from] IcSnapshotTransferReadExecutionError<E>),
176 #[error(transparent)]
178 Artifact(#[from] IcSnapshotArtifactError),
179 #[error("snapshot download reply rejected for operation {operation_sequence}: {source}")]
181 AfterReply {
182 operation_sequence: u64,
184 source: IcSnapshotDownloadReplyError<E>,
186 response: Box<IcSnapshotTransferReadResponse>,
188 },
189 #[error("snapshot download stage changed after publication: {source}")]
191 AfterPublication {
192 source: ExecutionWorkflowPersistenceError,
194 checksum: ArtifactChecksumRecord,
196 },
197}
198
199#[derive(Debug, Error)]
201pub enum IcSnapshotDownloadReplyError<E: std::error::Error + 'static> {
202 #[error("snapshot download reply qualification failed: {0}")]
204 Qualification(#[source] E),
205 #[error("snapshot download requires an explicit original Applied receipt")]
207 ReceiptRequired,
208 #[error(transparent)]
210 Stage(#[from] ExecutionWorkflowPersistenceError),
211 #[error(transparent)]
213 Journal(#[from] AttemptJournalError),
214 #[error(transparent)]
216 Artifact(#[from] IcSnapshotArtifactError),
217}
218
219#[cfg(test)]
220mod tests;
221
222impl<E: std::error::Error + 'static> IcSnapshotDownloadExecutionError<E> {
223 #[must_use]
226 pub fn response(&self) -> Option<&IcSnapshotTransferReadResponse> {
227 match self {
228 Self::AfterReply { response, .. } => Some(response),
229 Self::Read(source) => source.response(),
230 _ => None,
231 }
232 }
233}