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