use super::{IcSnapshotUploadExecutionError, upload_snapshot_with};
use crate::{
model::{
attempt_journal::MutationReceiptRequest,
evidence_digest::EvidenceDigest,
execution_workflow::ExecutionStagePredecessorRecord,
ic_mutation::IcMutationAcknowledgement,
ic_snapshot_upload::{
IcSnapshotDataUploadPlan, IcSnapshotDataUploadPlanningError, IcSnapshotUploadAttempt,
},
},
ops::persistence::{
AttemptJournalError, DownloadJournalGuard, ExecutionProgressPersistenceError,
ExecutionStageCheckpointError, ExecutionStageGuard, ExecutionWorkflowPersistenceError,
IcSnapshotUploadArtifactError, read_execution_progress,
},
ports::ic_snapshot_upload::IcSnapshotUploadProvider,
};
use thiserror::Error;
pub async fn upload_snapshot_data<E: std::error::Error + 'static>(
stage: &ExecutionStageGuard<'_>,
upload: &IcSnapshotDataUploadPlan<'_, '_, '_>,
source: &DownloadJournalGuard<'_>,
snapshot: &str,
provider: &mut impl IcSnapshotUploadProvider,
mut admit: impl AsyncFnMut(&IcSnapshotUploadAttempt<'_, '_>) -> Result<(), E>,
mut qualify: impl AsyncFnMut(
&IcSnapshotUploadAttempt<'_, '_>,
&IcMutationAcknowledgement,
) -> Result<MutationReceiptRequest, E>,
) -> Result<ExecutionStagePredecessorRecord, IcSnapshotDataUploadExecutionError<E>> {
upload.validate_binding(stage.binding())?;
let plan = upload
.plan()
.ok_or(IcSnapshotDataUploadPlanningError::NoDataWrites)?;
if stage.plan() != plan {
return Err(IcSnapshotDataUploadPlanningError::OriginalMismatch.into());
}
let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
if progress.attempts.mutations_used != 0 || progress.attempts.observations_used != 0 {
return Err(IcSnapshotDataUploadExecutionError::AlreadyAttempted);
}
let mut evidence = EvidenceDigest::new(b"ic-backup/ic-snapshot-data-upload/v1\0");
evidence.append(plan.digest().hash().as_bytes());
for (index, kind) in upload.kinds().iter().enumerate() {
let sequence =
u64::try_from(index).map_err(|_| IcSnapshotDataUploadPlanningError::CountOverflow)?;
let metadata = upload.metadata();
let payload = source.prepare_ic_snapshot_upload_data(
metadata.source_plan(),
snapshot,
metadata,
upload.destination(),
kind.clone(),
)?;
let (acknowledgement, settlement) = upload_snapshot_with(
stage,
sequence,
&payload,
provider,
&mut admit,
async |journal, request, admitted| {
super::super::qualified_receipt::AppliedReceiptRecorder {
journal,
expected_attempt: request.mutation_attempt(),
expected_request: payload.binding_digest().hash(),
}
.record_applied(
request,
admitted.acknowledgement(),
async |request, reply| qualify(request, reply).await,
IcSnapshotDataUploadReplyError::Qualification,
|| IcSnapshotDataUploadReplyError::ReceiptRequired,
|| {
stage
.layout()
.map(|_| ())
.map_err(IcSnapshotDataUploadReplyError::Stage)
},
)
.await?;
stage.layout()?;
Ok::<_, IcSnapshotDataUploadReplyError<E>>(admitted.reply().digest())
},
)
.await?;
let digest =
settlement.map_err(|source| IcSnapshotDataUploadExecutionError::AfterReply {
operation_sequence: sequence,
source,
acknowledgement: Box::new(acknowledgement),
})?;
evidence.append(digest.hash().as_bytes());
}
let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
if progress.applied_operations != upload.kinds().len() {
return Err(IcSnapshotDataUploadExecutionError::AlreadyAttempted);
}
Ok(stage.reconcile_checkpoint(evidence.finish())?)
}
#[derive(Debug, Error)]
pub enum IcSnapshotDataUploadExecutionError<E: std::error::Error + 'static> {
#[error("snapshot data upload original stage was already attempted")]
AlreadyAttempted,
#[error(transparent)]
Planning(#[from] IcSnapshotDataUploadPlanningError),
#[error(transparent)]
Stage(#[from] ExecutionWorkflowPersistenceError),
#[error(transparent)]
Progress(#[from] ExecutionProgressPersistenceError),
#[error(transparent)]
Source(#[from] IcSnapshotUploadArtifactError),
#[error(transparent)]
Upload(#[from] IcSnapshotUploadExecutionError<E>),
#[error("snapshot data upload reply rejected for operation {operation_sequence}: {source}")]
AfterReply {
operation_sequence: u64,
source: IcSnapshotDataUploadReplyError<E>,
acknowledgement: Box<IcMutationAcknowledgement>,
},
#[error(transparent)]
Checkpoint(#[from] ExecutionStageCheckpointError),
}
#[derive(Debug, Error)]
pub enum IcSnapshotDataUploadReplyError<E: std::error::Error + 'static> {
#[error("snapshot data upload qualification failed: {0}")]
Qualification(#[source] E),
#[error("snapshot data upload requires an explicit original Applied receipt")]
ReceiptRequired,
#[error(transparent)]
Stage(#[from] ExecutionWorkflowPersistenceError),
#[error(transparent)]
Journal(#[from] AttemptJournalError),
}