ic_backup/workflow/ic_snapshot_upload/data/
mod.rs1use super::{IcSnapshotUploadExecutionError, upload_snapshot_with};
4use crate::model::data_stage_plan::SnapshotDataPlanningError;
5use crate::{
6 model::{
7 artifacts::ArtifactChecksumRecord,
8 attempt_journal::MutationReceiptRequest,
9 evidence_digest::EvidenceDigest,
10 ic_mutation::IcMutationAcknowledgement,
11 ic_snapshot_upload::{IcSnapshotDataUploadPlan, IcSnapshotUploadAttempt},
12 },
13 ops::persistence::{
14 AttemptJournalError, DownloadJournalGuard, ExecutionProgressPersistenceError,
15 ExecutionStageGuard, ExecutionWorkflowPersistenceError, IcSnapshotUploadArtifactError,
16 read_execution_progress,
17 },
18 ports::ic_snapshot_upload::IcSnapshotUploadProvider,
19};
20use thiserror::Error;
21
22pub async fn upload_snapshot_data<E: std::error::Error + 'static>(
52 stage: &ExecutionStageGuard<'_>,
53 upload: &IcSnapshotDataUploadPlan<'_, '_, '_>,
54 source: &DownloadJournalGuard<'_>,
55 snapshot: &str,
56 provider: &mut impl IcSnapshotUploadProvider,
57 mut admit: impl AsyncFnMut(&IcSnapshotUploadAttempt<'_, '_>) -> Result<(), E>,
58 mut qualify: impl AsyncFnMut(
59 &IcSnapshotUploadAttempt<'_, '_>,
60 &IcMutationAcknowledgement,
61 ) -> Result<MutationReceiptRequest, E>,
62) -> Result<ArtifactChecksumRecord, IcSnapshotDataUploadExecutionError<E>> {
63 upload.validate_binding(stage.binding())?;
64 let plan = upload
65 .plan()
66 .ok_or(SnapshotDataPlanningError::NoDataOperations)?;
67 if stage.plan() != plan {
68 return Err(SnapshotDataPlanningError::OriginalMismatch.into());
69 }
70 let intent = plan.digest();
71 let progress = read_execution_progress(stage.layout()?, &intent)?;
72 if !progress.is_unattempted() {
73 return Err(IcSnapshotDataUploadExecutionError::AlreadyAttempted);
74 }
75 let mut evidence = EvidenceDigest::new(b"ic-backup/ic-snapshot-data-upload/v1\0");
76 evidence.checksum(&intent);
77 let session = source.upload_session(snapshot, upload)?;
78 for (operation, kind) in plan.operations().iter().zip(upload.kinds()) {
80 let sequence = operation.operation_sequence();
81 let payload = session.prepare(sequence, kind.clone())?;
82 let (acknowledgement, settlement) = upload_snapshot_with(
84 stage,
85 sequence,
86 &payload,
87 provider,
88 &mut admit,
89 async |journal, request, admitted| {
90 let receipt = qualify(request, admitted.acknowledgement())
91 .await
92 .map_err(IcSnapshotDataUploadReplyError::Qualification)?;
93 super::super::qualified_receipt::record_applied(
94 journal,
95 receipt,
96 request.mutation_attempt(),
97 payload.binding_digest().hash(),
98 IcSnapshotDataUploadReplyError::ReceiptRequired,
99 || {
100 stage.layout()?;
101 Ok(())
102 },
103 )?;
104 stage.layout()?;
105 Ok::<_, IcSnapshotDataUploadReplyError<E>>(admitted.reply().digest())
106 },
107 )
108 .await?;
109 let digest =
110 settlement.map_err(|source| IcSnapshotDataUploadExecutionError::AfterReply {
111 operation_sequence: sequence,
112 source,
113 acknowledgement: Box::new(acknowledgement),
114 })?;
115 evidence.append(digest.hash().as_bytes());
116 }
117 session.finish()?;
118 let progress = read_execution_progress(stage.layout()?, &intent)?;
119 if !progress.all_applied() {
120 return Err(IcSnapshotDataUploadExecutionError::AlreadyAttempted);
121 }
122 Ok(evidence.finish())
123}
124
125#[derive(Debug, Error)]
127pub enum IcSnapshotDataUploadExecutionError<E: std::error::Error + 'static> {
128 #[error("snapshot data upload original stage was already attempted")]
130 AlreadyAttempted,
131 #[error(transparent)]
133 Planning(#[from] SnapshotDataPlanningError),
134 #[error(transparent)]
136 Stage(#[from] ExecutionWorkflowPersistenceError),
137 #[error(transparent)]
139 Progress(#[from] ExecutionProgressPersistenceError),
140 #[error(transparent)]
142 Source(#[from] IcSnapshotUploadArtifactError),
143 #[error(transparent)]
145 Upload(#[from] IcSnapshotUploadExecutionError<E>),
146 #[error("snapshot data upload reply rejected for operation {operation_sequence}: {source}")]
148 AfterReply {
149 operation_sequence: u64,
151 source: IcSnapshotDataUploadReplyError<E>,
153 acknowledgement: Box<IcMutationAcknowledgement>,
155 },
156}
157
158#[derive(Debug, Error)]
160pub enum IcSnapshotDataUploadReplyError<E: std::error::Error + 'static> {
161 #[error("snapshot data upload qualification failed: {0}")]
163 Qualification(#[source] E),
164 #[error("snapshot data upload requires an explicit original Applied receipt")]
166 ReceiptRequired,
167 #[error(transparent)]
169 Stage(#[from] ExecutionWorkflowPersistenceError),
170 #[error(transparent)]
172 Journal(#[from] AttemptJournalError),
173}
174
175impl<E: std::error::Error + 'static> IcSnapshotDataUploadExecutionError<E> {
176 #[must_use]
179 pub fn acknowledgement(&self) -> Option<&IcMutationAcknowledgement> {
180 match self {
181 Self::AfterReply {
182 acknowledgement, ..
183 } => Some(acknowledgement),
184 Self::Upload(source) => source.acknowledgement(),
185 _ => None,
186 }
187 }
188}