ic_backup/workflow/ic_snapshot_download/
mod.rs1use crate::{
4 model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::MutationReceiptRequest,
7 ic_snapshot_download::{IcSnapshotDownloadPlan, IcSnapshotDownloadPlanningError},
8 ic_snapshot_transfer_read::{
9 IcSnapshotTransferReadError, IcSnapshotTransferReadPayload,
10 IcSnapshotTransferReadRequest, IcSnapshotTransferReadResponse,
11 },
12 },
13 ops::persistence::{
14 AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
15 ExecutionStageGuard, ExecutionWorkflowPersistenceError, IcSnapshotArtifactError,
16 IcSnapshotArtifactWriter, read_execution_progress,
17 },
18 policy::ic_snapshot_transfer_read::{
19 IcSnapshotTransferReadAssociationError, IcSnapshotTransferReadReply,
20 IcSnapshotTransferReadView,
21 },
22 ports::ic_snapshot_transfer_read::IcSnapshotTransferReadProvider,
23 workflow::ic_snapshot_transfer_read::{
24 IcSnapshotTransferReadExecutionError, read_snapshot_with,
25 },
26};
27use thiserror::Error;
28
29pub async fn download_snapshot<E: std::error::Error + 'static>(
60 stage: &ExecutionStageGuard<'_>,
61 download: &IcSnapshotDownloadPlan<'_, '_>,
62 mut writer: IcSnapshotArtifactWriter<'_, '_, '_>,
63 provider: &mut impl IcSnapshotTransferReadProvider,
64 mut admit: impl AsyncFnMut(&IcSnapshotTransferReadRequest<'_, '_>) -> Result<(), E>,
65 mut qualify: impl AsyncFnMut(
66 &IcSnapshotTransferReadRequest<'_, '_>,
67 &IcSnapshotTransferReadResponse,
68 ) -> Result<MutationReceiptRequest, E>,
69) -> Result<ArtifactChecksumRecord, IcSnapshotDownloadExecutionError<E>> {
70 download.validate_binding(stage.binding())?;
71 let plan = download
72 .plan()
73 .ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
74 if stage.plan() != plan
75 || download
76 .requests()
77 .iter()
78 .any(|request| request.metadata().digest() != writer.coverage().metadata().digest())
79 || writer.coverage().covered_region_bytes() != [0; 3]
80 || writer.coverage().covered_chunks() != 0
81 {
82 return Err(IcSnapshotDownloadExecutionError::OriginalMismatch);
83 }
84 writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
85 let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
86 if progress.attempts.mutations_used != 0 || progress.attempts.observations_used != 0 {
87 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
88 }
89 for (index, payload) in download.requests().iter().enumerate() {
90 let sequence =
92 u64::try_from(index).map_err(|_| IcSnapshotDownloadPlanningError::CountOverflow)?;
93 let (response, result) = read_snapshot_with(
94 stage,
95 sequence,
96 IcSnapshotTransferReadPayload::Data(payload),
97 provider,
98 &mut admit,
99 async |journal, request, admitted| {
100 append_qualified(stage, journal, request, admitted, writer, &mut qualify).await
101 },
102 )
103 .await?;
104 writer = result.map_err(|source| IcSnapshotDownloadExecutionError::AfterReply {
105 operation_sequence: sequence,
106 source,
107 response: Box::new(response),
108 })?;
109 }
110 let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
111 if progress.applied_operations != download.requests().len() {
112 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
113 }
114 writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
115 let checksum = writer.finish()?;
116 if let Err(source) = stage.layout() {
117 return Err(IcSnapshotDownloadExecutionError::AfterPublication { source, checksum });
118 }
119 Ok(checksum)
120}
121
122async fn append_qualified<'journal, 'layout, 'metadata, E: std::error::Error + 'static>(
123 stage: &ExecutionStageGuard<'_>,
124 journal: &mut AttemptJournalGuard<'_>,
125 request: &IcSnapshotTransferReadRequest<'_, '_>,
126 admitted: &IcSnapshotTransferReadView<'_, '_>,
127 writer: IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>,
128 qualify: &mut impl AsyncFnMut(
129 &IcSnapshotTransferReadRequest<'_, '_>,
130 &IcSnapshotTransferReadResponse,
131 ) -> Result<MutationReceiptRequest, E>,
132) -> Result<IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>, IcSnapshotDownloadReplyError<E>>
133{
134 let plan = stage.plan();
135 let writer = super::qualified_receipt::AppliedReceiptRecorder {
136 journal,
137 expected_attempt: request.mutation_attempt(),
138 expected_request: request.payload().digest().hash(),
139 }
140 .record_applied(
141 request,
142 admitted.response(),
143 async |request, reply| qualify(request, reply).await,
144 IcSnapshotDownloadReplyError::Qualification,
145 || IcSnapshotDownloadReplyError::ReceiptRequired,
146 || {
147 writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
148 let IcSnapshotTransferReadReply::Data(reply) = admitted.reply() else {
149 return Err(IcSnapshotDownloadReplyError::ReceiptRequired);
150 };
151 Ok(writer.append(reply)?)
152 },
153 )
154 .await?;
155 stage.layout()?;
156 Ok(writer)
157}
158
159#[derive(Debug, Error)]
161pub enum IcSnapshotDownloadExecutionError<E: std::error::Error + 'static> {
162 #[error("snapshot download originals differ")]
164 OriginalMismatch,
165 #[error("snapshot download original data stage was already attempted")]
167 AlreadyAttempted,
168 #[error(transparent)]
170 Planning(#[from] IcSnapshotDownloadPlanningError),
171 #[error(transparent)]
173 Stage(#[from] ExecutionWorkflowPersistenceError),
174 #[error(transparent)]
176 Progress(#[from] ExecutionProgressPersistenceError),
177 #[error(transparent)]
179 Read(#[from] IcSnapshotTransferReadExecutionError<E>),
180 #[error(transparent)]
182 Artifact(#[from] IcSnapshotArtifactError),
183 #[error("snapshot download reply rejected for operation {operation_sequence}: {source}")]
185 AfterReply {
186 operation_sequence: u64,
188 source: IcSnapshotDownloadReplyError<E>,
190 response: Box<IcSnapshotTransferReadResponse>,
192 },
193 #[error("snapshot download stage changed after publication: {source}")]
195 AfterPublication {
196 source: ExecutionWorkflowPersistenceError,
198 checksum: ArtifactChecksumRecord,
200 },
201}
202
203#[derive(Debug, Error)]
205pub enum IcSnapshotDownloadReplyError<E: std::error::Error + 'static> {
206 #[error("snapshot download reply qualification failed: {0}")]
208 Qualification(#[source] E),
209 #[error("snapshot download requires an explicit original Applied receipt")]
211 ReceiptRequired,
212 #[error(transparent)]
214 Stage(#[from] ExecutionWorkflowPersistenceError),
215 #[error(transparent)]
217 Journal(#[from] AttemptJournalError),
218 #[error(transparent)]
220 Request(#[from] IcSnapshotTransferReadError),
221 #[error(transparent)]
223 Association(#[from] IcSnapshotTransferReadAssociationError),
224 #[error(transparent)]
226 Artifact(#[from] IcSnapshotArtifactError),
227}
228
229#[cfg(test)]
230mod tests;