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