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