ic_backup/ops/persistence/download_journal/local_restore_artifact/
mod.rs1mod publication;
4pub use publication::LocalRestoreArtifactPublicationError;
5
6use super::{DownloadJournalGuard, LocalRestoreSourceError};
7use crate::{
8 model::{
9 artifacts::ChecksumError,
10 download_journal::DownloadArtifactRecord,
11 operation_plan::{OperationPlanError, OperationPlanRecord, PlannedOperationRecord},
12 restore_safety::RestoreSafetyRequirementRecord,
13 },
14 ops::{
15 artifacts::{ArtifactError, checksum_directory, stage_relative_path},
16 persistence::{BackupLayoutGuard, JournalLock, JournalLockError},
17 },
18 policy::local_restore_source::LocalRestoreSourceView,
19};
20use std::path::{Path, PathBuf};
21use thiserror::Error;
22
23#[derive(Debug)]
29pub struct LocalRestoreArtifactView<'a> {
30 source: LocalRestoreSourceView<'a>,
31 operation: &'a PlannedOperationRecord,
32 artifact: &'a DownloadArtifactRecord,
33 path: PathBuf,
34}
35
36impl<'a> LocalRestoreArtifactView<'a> {
37 #[must_use]
39 pub const fn source(&self) -> &LocalRestoreSourceView<'a> {
40 &self.source
41 }
42 #[must_use]
44 pub const fn operation(&self) -> &'a PlannedOperationRecord {
45 self.operation
46 }
47 #[must_use]
49 pub const fn artifact(&self) -> &'a DownloadArtifactRecord {
50 self.artifact
51 }
52 #[must_use]
54 pub fn path(&self) -> &Path {
55 &self.path
56 }
57}
58
59impl DownloadJournalGuard<'_> {
60 pub fn stage_local_restore_artifact<'a>(
77 &'a self,
78 restore_layout: &'a BackupLayoutGuard,
79 restore: &'a OperationPlanRecord,
80 source: &'a OperationPlanRecord,
81 requirement: &'a RestoreSafetyRequirementRecord,
82 operation_sequence: u64,
83 ) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
84 self.stage_restore_artifact_with(
85 restore_layout,
86 restore,
87 source,
88 requirement,
89 operation_sequence,
90 stage_relative_path,
91 )
92 }
93
94 fn stage_restore_artifact_with<'a>(
95 &'a self,
96 restore_layout: &'a BackupLayoutGuard,
97 restore: &'a OperationPlanRecord,
98 source: &'a OperationPlanRecord,
99 requirement: &'a RestoreSafetyRequirementRecord,
100 operation_sequence: u64,
101 copy: impl FnOnce(
102 &Path,
103 &Path,
104 &Path,
105 )
106 -> Result<crate::model::artifacts::ArtifactChecksumRecord, ArtifactError>,
107 ) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
108 let operation = restore.operation(operation_sequence)?;
109 let view =
110 self.verify_local_restore_source(restore_layout, restore, source, requirement)?;
111 let artifact = selected_artifact(&view, operation)?;
112 let path = staged_path(restore_layout, operation_sequence);
113 let _lock = JournalLock::acquire(&path)?;
114 copy(
115 self.layout.root(),
116 Path::new(artifact.artifact_path()),
117 &path,
118 )?
119 .verify(
120 artifact
121 .checksum()
122 .ok_or(LocalRestoreArtifactError::ArtifactUnavailable)?
123 .hash(),
124 )?;
125 self.verify_restore_artifact_at(
126 restore_layout,
127 restore,
128 source,
129 requirement,
130 operation,
131 path,
132 )
133 }
134
135 pub fn verify_staged_local_restore_artifact<'a>(
146 &'a self,
147 restore_layout: &'a BackupLayoutGuard,
148 restore: &'a OperationPlanRecord,
149 source: &'a OperationPlanRecord,
150 requirement: &'a RestoreSafetyRequirementRecord,
151 operation_sequence: u64,
152 ) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
153 let operation = restore.operation(operation_sequence)?;
154 let path = staged_path(restore_layout, operation_sequence);
155 let _lock = JournalLock::acquire(&path)?;
156 self.verify_restore_artifact_at(
157 restore_layout,
158 restore,
159 source,
160 requirement,
161 operation,
162 path,
163 )
164 }
165
166 fn verify_restore_artifact_at<'a>(
167 &'a self,
168 restore_layout: &'a BackupLayoutGuard,
169 restore: &'a OperationPlanRecord,
170 source: &'a OperationPlanRecord,
171 requirement: &'a RestoreSafetyRequirementRecord,
172 operation: &'a PlannedOperationRecord,
173 path: PathBuf,
174 ) -> Result<LocalRestoreArtifactView<'a>, LocalRestoreArtifactError> {
175 let view = self.admit_local_restore_source(restore_layout, restore, source, requirement)?;
176 let artifact = selected_artifact(&view, operation)?;
177 checksum_directory(&path)?.verify(
178 artifact
179 .checksum()
180 .ok_or(LocalRestoreArtifactError::ArtifactUnavailable)?
181 .hash(),
182 )?;
183 let source =
184 self.admit_local_restore_source(restore_layout, restore, source, requirement)?;
185 Ok(LocalRestoreArtifactView {
186 source,
187 operation,
188 artifact,
189 path,
190 })
191 }
192}
193
194fn staged_path(layout: &BackupLayoutGuard, sequence: u64) -> PathBuf {
195 layout
196 .root()
197 .join(format!("restore-artifact-{sequence}.tmp"))
198}
199fn selected_artifact<'a>(
200 view: &LocalRestoreSourceView<'a>,
201 operation: &PlannedOperationRecord,
202) -> Result<&'a DownloadArtifactRecord, LocalRestoreArtifactError> {
203 view.selected_artifacts()
204 .find(|artifact| artifact.artifact().canister_id() == operation.target())
205 .map(crate::policy::download_integrity::DurableDownloadArtifactView::artifact)
206 .ok_or(LocalRestoreArtifactError::ArtifactUnavailable)
207}
208
209#[derive(Debug, Error)]
211pub enum LocalRestoreArtifactError {
212 #[error(transparent)]
214 Source(#[from] LocalRestoreSourceError),
215 #[error(transparent)]
217 Operation(#[from] OperationPlanError),
218 #[error("original selected restore artifact unavailable")]
220 ArtifactUnavailable,
221 #[error(transparent)]
223 Artifact(#[from] ArtifactError),
224 #[error(transparent)]
226 Checksum(#[from] ChecksumError),
227 #[error(transparent)]
229 Lock(#[from] JournalLockError),
230}
231
232#[cfg(all(test, unix))]
233mod tests;