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.matches_metadata(writer.coverage().metadata())
73 || writer.coverage().covered_region_bytes() != [0; 3]
74 || writer.coverage().covered_chunks() != 0
75 {
76 return Err(IcSnapshotDownloadExecutionError::OriginalMismatch);
77 }
78 writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
79 let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
80 if !progress.is_unattempted() {
81 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
82 }
83 for (operation, payload) in plan.operations().iter().zip(download.requests()) {
85 let sequence = operation.operation_sequence();
86 let (response, result) = read_snapshot_with(
87 stage,
88 sequence,
89 IcSnapshotTransferReadPayload::Data(payload),
90 provider,
91 &mut admit,
92 async |journal, request, admitted| {
93 append_qualified(stage, journal, request, admitted, writer, &mut qualify).await
94 },
95 )
96 .await?;
97 writer = result.map_err(|source| IcSnapshotDownloadExecutionError::AfterReply {
98 operation_sequence: sequence,
99 source,
100 response: Box::new(response),
101 })?;
102 }
103 let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
104 if !progress.all_applied() {
105 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
106 }
107 writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
108 let checksum = writer.finish()?;
109 if let Err(source) = stage.layout() {
110 return Err(IcSnapshotDownloadExecutionError::AfterPublication { source, checksum });
111 }
112 Ok(checksum)
113}
114
115async fn append_qualified<'journal, 'layout, 'metadata, E: std::error::Error + 'static>(
116 stage: &ExecutionStageGuard<'_>,
117 journal: &mut AttemptJournalGuard<'_>,
118 request: &IcSnapshotTransferReadRequest<'_, '_>,
119 admitted: &IcSnapshotTransferReadView<'_, '_>,
120 writer: IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>,
121 qualify: &mut impl AsyncFnMut(
122 &IcSnapshotTransferReadRequest<'_, '_>,
123 &IcSnapshotTransferReadResponse,
124 ) -> Result<MutationReceiptRequest, E>,
125) -> Result<IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>, IcSnapshotDownloadReplyError<E>>
126{
127 let plan = stage.plan();
128 let receipt = qualify(request, admitted.response())
129 .await
130 .map_err(IcSnapshotDownloadReplyError::Qualification)?;
131 let writer = super::qualified_receipt::record_applied(
132 journal,
133 receipt,
134 request.mutation_attempt(),
135 request.payload().digest().hash(),
136 IcSnapshotDownloadReplyError::ReceiptRequired,
137 || {
138 writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
139 let IcSnapshotTransferReadReply::Data(reply) = admitted.reply() else {
140 return Err(IcSnapshotDownloadReplyError::ReceiptRequired);
141 };
142 Ok(writer.append(reply)?)
143 },
144 )?;
145 stage.layout()?;
146 Ok(writer)
147}
148
149#[derive(Debug, Error)]
151pub enum IcSnapshotDownloadExecutionError<E: std::error::Error + 'static> {
152 #[error("snapshot download originals differ")]
154 OriginalMismatch,
155 #[error("snapshot download original data stage was already attempted")]
157 AlreadyAttempted,
158 #[error(transparent)]
160 Planning(#[from] IcSnapshotDownloadPlanningError),
161 #[error(transparent)]
163 Stage(#[from] ExecutionWorkflowPersistenceError),
164 #[error(transparent)]
166 Progress(#[from] ExecutionProgressPersistenceError),
167 #[error(transparent)]
169 Read(#[from] IcSnapshotTransferReadExecutionError<E>),
170 #[error(transparent)]
172 Artifact(#[from] IcSnapshotArtifactError),
173 #[error("snapshot download reply rejected for operation {operation_sequence}: {source}")]
175 AfterReply {
176 operation_sequence: u64,
178 source: IcSnapshotDownloadReplyError<E>,
180 response: Box<IcSnapshotTransferReadResponse>,
182 },
183 #[error("snapshot download stage changed after publication: {source}")]
185 AfterPublication {
186 source: ExecutionWorkflowPersistenceError,
188 checksum: ArtifactChecksumRecord,
190 },
191}
192
193#[derive(Debug, Error)]
195pub enum IcSnapshotDownloadReplyError<E: std::error::Error + 'static> {
196 #[error("snapshot download reply qualification failed: {0}")]
198 Qualification(#[source] E),
199 #[error("snapshot download requires an explicit original Applied receipt")]
201 ReceiptRequired,
202 #[error(transparent)]
204 Stage(#[from] ExecutionWorkflowPersistenceError),
205 #[error(transparent)]
207 Journal(#[from] AttemptJournalError),
208 #[error(transparent)]
210 Artifact(#[from] IcSnapshotArtifactError),
211}
212
213#[cfg(test)]
214mod tests;