ic_backup/workflow/ic_snapshot_upload/data/
mod.rs1use super::{IcSnapshotUploadExecutionError, upload_snapshot};
4use crate::{
5 model::{
6 artifacts::ArtifactChecksumRecord,
7 attempt_journal::MutationReceiptRequest,
8 evidence_digest::EvidenceDigest,
9 execution_workflow::ExecutionStagePredecessorRecord,
10 ic_mutation::IcMutationAcknowledgement,
11 ic_snapshot_upload::{
12 IcSnapshotDataUploadPlan, IcSnapshotDataUploadPlanningError, IcSnapshotUploadAttempt,
13 IcSnapshotUploadAttemptError, IcSnapshotUploadRequest,
14 },
15 },
16 ops::persistence::{
17 AttemptJournalError, AttemptJournalGuard, DownloadJournalGuard,
18 ExecutionProgressPersistenceError, ExecutionStageCheckpointError, ExecutionStageGuard,
19 ExecutionWorkflowPersistenceError, IcSnapshotUploadArtifactError, read_execution_progress,
20 },
21 policy::ic_snapshot_upload::{IcSnapshotUploadAssociationError, validate_acknowledgement},
22 ports::ic_snapshot_upload::IcSnapshotUploadProvider,
23};
24use thiserror::Error;
25
26pub async fn upload_snapshot_data<E: std::error::Error + 'static>(
55 stage: &ExecutionStageGuard<'_>,
56 upload: &IcSnapshotDataUploadPlan<'_, '_, '_>,
57 source: &DownloadJournalGuard<'_>,
58 snapshot: &str,
59 provider: &mut impl IcSnapshotUploadProvider,
60 mut admit: impl AsyncFnMut(&IcSnapshotUploadAttempt<'_, '_>) -> Result<(), E>,
61 mut qualify: impl AsyncFnMut(
62 &IcSnapshotUploadAttempt<'_, '_>,
63 &IcMutationAcknowledgement,
64 ) -> Result<MutationReceiptRequest, E>,
65) -> Result<ExecutionStagePredecessorRecord, IcSnapshotDataUploadExecutionError<E>> {
66 upload.validate_binding(stage.binding())?;
67 let plan = upload
68 .plan()
69 .ok_or(IcSnapshotDataUploadPlanningError::NoDataWrites)?;
70 if stage.plan() != plan {
71 return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch.into());
72 }
73 let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
74 if progress.attempts.mutations_used != 0 || progress.attempts.observations_used != 0 {
75 return Err(IcSnapshotDataUploadExecutionError::AlreadyAttempted);
76 }
77 let mut evidence = EvidenceDigest::new(b"ic-backup/ic-snapshot-data-upload/v1\0");
78 evidence.append(plan.digest().hash().as_bytes());
79 for (index, kind) in upload.kinds().iter().enumerate() {
80 let sequence =
81 u64::try_from(index).map_err(|_| IcSnapshotDataUploadPlanningError::CountOverflow)?;
82 let metadata = upload.metadata();
83 let payload = source.prepare_ic_snapshot_upload_data(
84 metadata.source_plan(),
85 snapshot,
86 metadata,
87 upload.destination(),
88 kind.clone(),
89 )?;
90 let acknowledgement =
92 upload_snapshot(stage, sequence, &payload, provider, &mut admit).await?;
93 let digest = record_qualified(stage, sequence, &payload, &acknowledgement, &mut qualify)
94 .await
95 .map_err(|source| IcSnapshotDataUploadExecutionError::AfterReply {
96 operation_sequence: sequence,
97 source,
98 acknowledgement: Box::new(acknowledgement),
99 })?;
100 evidence.append(digest.hash().as_bytes());
101 }
102 let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
103 if progress.applied_operations != upload.kinds().len() {
104 return Err(IcSnapshotDataUploadExecutionError::AlreadyAttempted);
105 }
106 Ok(stage.reconcile_checkpoint(evidence.finish())?)
107}
108
109async fn record_qualified<E: std::error::Error + 'static>(
110 stage: &ExecutionStageGuard<'_>,
111 sequence: u64,
112 payload: &IcSnapshotUploadRequest<'_>,
113 acknowledgement: &IcMutationAcknowledgement,
114 qualify: &mut impl AsyncFnMut(
115 &IcSnapshotUploadAttempt<'_, '_>,
116 &IcMutationAcknowledgement,
117 ) -> Result<MutationReceiptRequest, E>,
118) -> Result<ArtifactChecksumRecord, IcSnapshotDataUploadReplyError<E>> {
119 let plan = stage.plan();
120 let authority = plan
121 .attempt_authority(sequence)
122 .map_err(IcSnapshotUploadAttemptError::from)?;
123 let mut journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
124 let request = IcSnapshotUploadAttempt::new(plan, sequence, journal.record()?, payload)?;
125 let admitted = validate_acknowledgement(&request, journal.record()?, acknowledgement)?;
126 super::super::qualified_receipt::AppliedReceiptRecorder {
127 journal: &mut journal,
128 expected_attempt: request.mutation_attempt(),
129 expected_request: payload.binding_digest().hash(),
130 }
131 .record_applied(
132 &request,
133 acknowledgement,
134 async |request, reply| qualify(request, reply).await,
135 IcSnapshotDataUploadReplyError::Qualification,
136 || IcSnapshotDataUploadReplyError::ReceiptRequired,
137 || {
138 stage
139 .layout()
140 .map(|_| ())
141 .map_err(IcSnapshotDataUploadReplyError::Stage)
142 },
143 )
144 .await?;
145 stage.layout()?;
146 Ok(admitted.reply().digest())
147}
148
149#[derive(Debug, Error)]
151pub enum IcSnapshotDataUploadExecutionError<E: std::error::Error + 'static> {
152 #[error("snapshot data upload original stage was already attempted")]
154 AlreadyAttempted,
155 #[error(transparent)]
157 Planning(#[from] IcSnapshotDataUploadPlanningError),
158 #[error(transparent)]
160 Stage(#[from] ExecutionWorkflowPersistenceError),
161 #[error(transparent)]
163 Progress(#[from] ExecutionProgressPersistenceError),
164 #[error(transparent)]
166 Source(#[from] IcSnapshotUploadArtifactError),
167 #[error(transparent)]
169 Upload(#[from] IcSnapshotUploadExecutionError<E>),
170 #[error("snapshot data upload reply rejected for operation {operation_sequence}: {source}")]
172 AfterReply {
173 operation_sequence: u64,
175 source: IcSnapshotDataUploadReplyError<E>,
177 acknowledgement: Box<IcMutationAcknowledgement>,
179 },
180 #[error(transparent)]
182 Checkpoint(#[from] ExecutionStageCheckpointError),
183}
184
185#[derive(Debug, Error)]
187pub enum IcSnapshotDataUploadReplyError<E: std::error::Error + 'static> {
188 #[error("snapshot data upload qualification failed: {0}")]
190 Qualification(#[source] E),
191 #[error("snapshot data upload requires an explicit original Applied receipt")]
193 ReceiptRequired,
194 #[error(transparent)]
196 Stage(#[from] ExecutionWorkflowPersistenceError),
197 #[error(transparent)]
199 Journal(#[from] AttemptJournalError),
200 #[error(transparent)]
202 Request(#[from] IcSnapshotUploadAttemptError),
203 #[error(transparent)]
205 Association(#[from] IcSnapshotUploadAssociationError),
206}