use super::{DownloadJournalGuard, LocalRestoreSourceError};
use crate::{
model::{
artifacts::ChecksumError,
download_journal::DownloadArtifactRecord,
operation_plan::{OperationPlanError, OperationPlanRecord, PlannedOperationRecord},
restore_safety::RestoreSafetyRequirementRecord,
},
ops::{
artifacts::{ArtifactError, checksum_directory, stage_relative_path},
persistence::{BackupLayoutGuard, JournalLock, JournalLockError},
},
policy::local_restore_source::LocalRestoreSourceView,
};
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug)]
pub struct LocalRestoreArtifactView<'a> {
source: LocalRestoreSourceView<'a>,
operation: &'a PlannedOperationRecord,
artifact: &'a DownloadArtifactRecord,
path: PathBuf,
}
impl<'a> LocalRestoreArtifactView<'a> {
#[must_use]
pub const fn source(&self) -> &LocalRestoreSourceView<'a> {
&self.source
}
#[must_use]
pub const fn operation(&self) -> &'a PlannedOperationRecord {
self.operation
}
#[must_use]
pub const fn artifact(&self) -> &'a DownloadArtifactRecord {
self.artifact
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
}
impl DownloadJournalGuard<'_> {
pub fn stage_local_restore_artifact<'a>(
&'a self,
restore_layout: &'a BackupLayoutGuard,
restore: &'a OperationPlanRecord,
source: &'a OperationPlanRecord,
requirement: &'a RestoreSafetyRequirementRecord,
operation_sequence: u64,
) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
self.stage_restore_artifact_with(
restore_layout,
restore,
source,
requirement,
operation_sequence,
stage_relative_path,
)
}
fn stage_restore_artifact_with<'a>(
&'a self,
restore_layout: &'a BackupLayoutGuard,
restore: &'a OperationPlanRecord,
source: &'a OperationPlanRecord,
requirement: &'a RestoreSafetyRequirementRecord,
operation_sequence: u64,
copy: impl FnOnce(
&Path,
&Path,
&Path,
)
-> Result<crate::model::artifacts::ArtifactChecksumRecord, ArtifactError>,
) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
let operation = restore.operation(operation_sequence)?;
let view =
self.verify_local_restore_source(restore_layout, restore, source, requirement)?;
let artifact = selected_artifact(&view, operation)?;
let path = staged_path(restore_layout, operation_sequence);
let _lock = JournalLock::acquire(&path)?;
copy(
self.layout.root(),
Path::new(artifact.artifact_path()),
&path,
)?
.verify(
artifact
.checksum()
.ok_or(LocalRestoreArtifactError::ArtifactUnavailable)?
.hash(),
)?;
self.verify_restore_artifact_at(
restore_layout,
restore,
source,
requirement,
operation,
path,
)
}
pub fn verify_staged_local_restore_artifact<'a>(
&'a self,
restore_layout: &'a BackupLayoutGuard,
restore: &'a OperationPlanRecord,
source: &'a OperationPlanRecord,
requirement: &'a RestoreSafetyRequirementRecord,
operation_sequence: u64,
) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
let operation = restore.operation(operation_sequence)?;
let path = staged_path(restore_layout, operation_sequence);
let _lock = JournalLock::acquire(&path)?;
self.verify_restore_artifact_at(
restore_layout,
restore,
source,
requirement,
operation,
path,
)
}
fn verify_restore_artifact_at<'a>(
&'a self,
restore_layout: &'a BackupLayoutGuard,
restore: &'a OperationPlanRecord,
source: &'a OperationPlanRecord,
requirement: &'a RestoreSafetyRequirementRecord,
operation: &'a PlannedOperationRecord,
path: PathBuf,
) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
let view = self.admit_local_restore_source(restore_layout, restore, source, requirement)?;
let artifact = selected_artifact(&view, operation)?;
checksum_directory(&path)?.verify(
artifact
.checksum()
.ok_or(LocalRestoreArtifactError::ArtifactUnavailable)?
.hash(),
)?;
let source =
self.admit_local_restore_source(restore_layout, restore, source, requirement)?;
Ok(LocalRestoreArtifactView {
source,
operation,
artifact,
path,
})
}
}
fn staged_path(layout: &BackupLayoutGuard, sequence: u64) -> PathBuf {
layout
.root()
.join(format!("restore-artifact-{sequence}.tmp"))
}
fn selected_artifact<'a>(
view: &LocalRestoreSourceView<'a>,
operation: &PlannedOperationRecord,
) -> Result<&'a DownloadArtifactRecord, LocalRestoreArtifactError> {
view.selected_artifacts()
.find(|artifact| artifact.artifact().canister_id() == operation.target())
.map(crate::policy::download_integrity::DurableDownloadArtifactView::artifact)
.ok_or(LocalRestoreArtifactError::ArtifactUnavailable)
}
#[derive(Debug, Error)]
pub enum LocalRestoreArtifactError {
#[error(transparent)]
Source(#[from] LocalRestoreSourceError),
#[error(transparent)]
Operation(#[from] OperationPlanError),
#[error("original selected restore artifact unavailable")]
ArtifactUnavailable,
#[error(transparent)]
Artifact(#[from] ArtifactError),
#[error(transparent)]
Checksum(#[from] ChecksumError),
#[error(transparent)]
Lock(#[from] JournalLockError),
}
#[cfg(all(test, unix))]
mod tests;