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 (response, result) = read_snapshot_with(
89 stage,
90 sequence,
91 IcSnapshotTransferReadPayload::Data(payload),
92 provider,
93 &mut admit,
94 async |journal, request, admitted| {
95 append_qualified(stage, journal, request, admitted, writer, &mut qualify).await
96 },
97 )
98 .await?;
99 writer = result.map_err(|source| IcSnapshotDownloadExecutionError::AfterReply {
100 operation_sequence: sequence,
101 source,
102 response: Box::new(response),
103 })?;
104 }
105 let progress = read_execution_progress(stage.layout()?, &intent)?;
106 if !progress.all_applied() {
107 return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
108 }
109 writer.validate_transfer_origin(stage.layout()?.root(), intent.hash())?;
110 let checksum = writer.finish()?;
111 if let Err(source) = stage.layout() {
112 return Err(IcSnapshotDownloadExecutionError::AfterPublication { source, checksum });
113 }
114 Ok(checksum)
115}
116
117async fn append_qualified<'journal, 'layout, 'metadata, E: std::error::Error + 'static>(
118 stage: &ExecutionStageGuard<'_>,
119 journal: &mut AttemptJournalGuard<'_>,
120 request: &IcSnapshotTransferReadRequest<'_, '_>,
121 admitted: &IcSnapshotTransferReadView<'_, '_>,
122 writer: IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>,
123 qualify: &mut impl AsyncFnMut(
124 &IcSnapshotTransferReadRequest<'_, '_>,
125 &IcSnapshotTransferReadResponse,
126 ) -> Result<MutationReceiptRequest, E>,
127) -> Result<IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>, IcSnapshotDownloadReplyError<E>>
128{
129 let receipt = qualify(request, admitted.response())
130 .await
131 .map_err(IcSnapshotDownloadReplyError::Qualification)?;
132 let writer = super::qualified_receipt::record_applied(
133 journal,
134 receipt,
135 request.mutation_attempt(),
136 request.payload().digest().hash(),
137 IcSnapshotDownloadReplyError::ReceiptRequired,
138 || {
139 writer.validate_transfer_origin(
141 stage.layout()?.root(),
142 request.authority().binding().intent(),
143 )?;
144 let IcSnapshotTransferReadReply::Data(reply) = admitted.reply() else {
145 return Err(IcSnapshotDownloadReplyError::ReceiptRequired);
146 };
147 Ok(writer.append(reply)?)
148 },
149 )?;
150 stage.layout()?;
151 Ok(writer)
152}
153
154#[derive(Debug, Error)]
156pub enum IcSnapshotDownloadExecutionError<E: std::error::Error + 'static> {
157 #[error("snapshot download originals differ")]
159 OriginalMismatch,
160 #[error("snapshot download original data stage was already attempted")]
162 AlreadyAttempted,
163 #[error(transparent)]
165 Planning(#[from] SnapshotDataPlanningError),
166 #[error(transparent)]
168 Stage(#[from] ExecutionWorkflowPersistenceError),
169 #[error(transparent)]
171 Progress(#[from] ExecutionProgressPersistenceError),
172 #[error(transparent)]
174 Read(#[from] IcSnapshotTransferReadExecutionError<E>),
175 #[error(transparent)]
177 Artifact(#[from] IcSnapshotArtifactError),
178 #[error("snapshot download reply rejected for operation {operation_sequence}: {source}")]
180 AfterReply {
181 operation_sequence: u64,
183 source: IcSnapshotDownloadReplyError<E>,
185 response: Box<IcSnapshotTransferReadResponse>,
187 },
188 #[error("snapshot download stage changed after publication: {source}")]
190 AfterPublication {
191 source: ExecutionWorkflowPersistenceError,
193 checksum: ArtifactChecksumRecord,
195 },
196}
197
198#[derive(Debug, Error)]
200pub enum IcSnapshotDownloadReplyError<E: std::error::Error + 'static> {
201 #[error("snapshot download reply qualification failed: {0}")]
203 Qualification(#[source] E),
204 #[error("snapshot download requires an explicit original Applied receipt")]
206 ReceiptRequired,
207 #[error(transparent)]
209 Stage(#[from] ExecutionWorkflowPersistenceError),
210 #[error(transparent)]
212 Journal(#[from] AttemptJournalError),
213 #[error(transparent)]
215 Artifact(#[from] IcSnapshotArtifactError),
216}
217
218#[cfg(test)]
219mod tests;