use crate::model::data_stage_plan::SnapshotDataPlanningError;
use crate::{
model::{
attempt_journal::AppliedReceiptEvidence,
execution_workflow::ExecutionStagePredecessorRecord,
ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
ic_snapshot_metadata::IcSnapshotMetadataRequest,
ic_snapshot_upload::{IcSnapshotDataUploadPlan, UploadCompletion},
operation_plan::OperationPlanRecord,
},
ops::persistence::{
AttemptJournalError, AttemptJournalGuard, BackupLayoutGuard, ExecutionStageCheckpointError,
ExecutionStageGuard, ExecutionWorkflowPersistenceError,
},
};
use thiserror::Error;
#[derive(Clone)]
pub struct IcRestoreSource<'a> {
pub(super) layout: &'a BackupLayoutGuard,
pub(super) plan: &'a OperationPlanRecord,
target: String,
snapshot_id: Vec<u8>,
upload: Option<UploadOrigin<'a>>,
}
#[derive(Clone)]
struct UploadOrigin<'a> {
layout: &'a BackupLayoutGuard,
workflow: crate::model::artifacts::ArtifactChecksumRecord,
completed: ExecutionStagePredecessorRecord,
completion: UploadCompletion,
}
impl<'a> IcRestoreSource<'a> {
#[must_use]
pub fn existing(
layout: &'a BackupLayoutGuard,
plan: &'a OperationPlanRecord,
metadata: &IcSnapshotMetadataRequest,
) -> Self {
Self {
layout,
plan,
target: metadata.target().into(),
snapshot_id: metadata.snapshot_id().into(),
upload: None,
}
}
pub fn uploaded(
layout: &'a BackupLayoutGuard,
upload: &IcSnapshotDataUploadPlan<'_, '_, 'a>,
workflow_layout: &'a BackupLayoutGuard,
completed: &ExecutionStagePredecessorRecord,
) -> Result<Self, IcRestoreSourceError> {
let stage = ExecutionStageGuard::open(
workflow_layout,
&upload.workflow_digest(),
completed.stage_sequence(),
completed.binding(),
)?;
let completion = upload.validate_completion(
stage.binding(),
stage.plan(),
completed.learned_evidence(),
)?;
let origin = UploadOrigin {
layout: workflow_layout,
workflow: upload.workflow_digest(),
completed: completed.clone(),
completion,
};
origin.replay(&stage)?;
Ok(Self {
layout,
plan: upload.metadata().source_plan(),
target: upload.metadata().target().into(),
snapshot_id: upload.destination().into(),
upload: Some(origin),
})
}
pub(crate) fn validate(
&self,
stage: &ExecutionStageGuard<'_>,
wire: &IcManagementRequestRecord,
) -> Result<(), IcRestoreSourceError> {
if wire.target() != self.target
|| (wire.method() == IcManagementMethodRecord::LoadCanisterSnapshot
&& wire.snapshot_id() != Some(self.snapshot_id.as_slice()))
{
return Err(IcRestoreSourceError::Mismatch);
}
if let Some(origin) = &self.upload {
if stage.binding().workflow() == &origin.workflow
&& (wire.method() == IcManagementMethodRecord::LoadCanisterSnapshot
&& !stage.binding().predecessors().contains(&origin.completed))
{
return Err(IcRestoreSourceError::Mismatch);
}
let upload = origin.open()?;
origin.replay(&upload)?;
}
Ok(())
}
}
impl UploadOrigin<'_> {
fn open(&self) -> Result<ExecutionStageGuard<'_>, ExecutionWorkflowPersistenceError> {
ExecutionStageGuard::open(
self.layout,
&self.workflow,
self.completed.stage_sequence(),
self.completed.binding(),
)
}
fn replay(&self, stage: &ExecutionStageGuard<'_>) -> Result<(), IcRestoreSourceError> {
if stage.replay_checkpoint(self.completed.learned_evidence().clone())? != self.completed {
return Err(IcRestoreSourceError::Mismatch);
}
if let UploadCompletion::Allocation {
sequence,
evidence: expected_evidence,
} = &self.completion
{
let authority = stage
.plan()
.attempt_authority(*sequence)
.map_err(|_| IcRestoreSourceError::Mismatch)?;
let journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
if !matches!(journal.record()?.applied_receipt(), Some(AppliedReceiptEvidence::Mutation { evidence, .. }) if evidence == expected_evidence.hash())
{
return Err(IcRestoreSourceError::Mismatch);
}
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum IcRestoreSourceError {
#[error(transparent)]
Journal(#[from] AttemptJournalError),
#[error("restore snapshot source differs from its original upload or metadata")]
Mismatch,
#[error(transparent)]
Planning(#[from] SnapshotDataPlanningError),
#[error(transparent)]
Stage(#[from] ExecutionWorkflowPersistenceError),
#[error(transparent)]
Checkpoint(#[from] ExecutionStageCheckpointError),
}