ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs1mod plan;
4mod session;
5pub use plan::IcSnapshotDataUploadPreparationError;
6
7use super::{
8 DownloadJournalGuard, File, IcSnapshotArtifactError, REGIONS, member,
9 verification::open_regular_child, with_artifact_directory,
10};
11use crate::model::{
12 ic_snapshot_data::MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES,
13 ic_snapshot_metadata::IcSnapshotMetadataReply,
14 ic_snapshot_upload::{
15 IcSnapshotDataUploadPlan, IcSnapshotUploadError, IcSnapshotUploadRequest,
16 },
17 operation_plan::OperationPlanRecord,
18};
19use ic_platform::management_canister_types::SnapshotDataKind;
20use std::io::{Read, Seek, SeekFrom};
21use thiserror::Error;
22
23impl DownloadJournalGuard<'_> {
24 pub(crate) fn upload_session<'a, 'workflow, 'request, 'source>(
25 &'a self,
26 snapshot: &'a str,
27 upload: &'a IcSnapshotDataUploadPlan<'workflow, 'request, 'source>,
28 ) -> Result<
29 session::UploadSession<'a, 'workflow, 'request, 'source>,
30 IcSnapshotUploadArtifactError,
31 > {
32 session::UploadSession::new(self, snapshot, upload)
33 }
34 pub fn prepare_ic_snapshot_upload_metadata<'source>(
44 &self,
45 plan: &'source OperationPlanRecord,
46 snapshot: &str,
47 metadata: &'source IcSnapshotMetadataReply<'source>,
48 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
49 let checksum = self.verify_ic_snapshot_artifact(plan, snapshot, metadata)?;
50 Ok(IcSnapshotUploadRequest::metadata(
51 plan, metadata, &checksum,
52 )?)
53 }
54
55 fn with_upload_source<T>(
56 &self,
57 plan: &OperationPlanRecord,
58 snapshot: &str,
59 metadata_upload: &IcSnapshotUploadRequest<'_>,
60 read: impl FnOnce(&File) -> Result<T, IcSnapshotUploadArtifactError>,
61 ) -> Result<T, IcSnapshotUploadArtifactError> {
62 let metadata = metadata_upload.source();
63 self.require_upload_source(plan, snapshot, metadata_upload)?;
64 let entry = self
65 .record
66 .artifact(metadata.request().target(), snapshot)
67 .map_err(super::DownloadJournalError::from)
68 .map_err(IcSnapshotArtifactError::from)?;
69 with_artifact_directory(self.layout.root(), entry.artifact_path(), read, || {
70 self.require_upload_source(plan, snapshot, metadata_upload)
71 })
72 }
73
74 fn require_upload_source(
75 &self,
76 plan: &OperationPlanRecord,
77 snapshot: &str,
78 upload: &IcSnapshotUploadRequest<'_>,
79 ) -> Result<(), IcSnapshotUploadArtifactError> {
80 if plan.digest() != upload.source_plan().digest() {
81 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
82 }
83 let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
84 if actual != *upload.source_checksum() {
85 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
86 }
87 Ok(())
88 }
89}
90
91fn read_chunk(
92 directory: &File,
93 metadata: &IcSnapshotMetadataReply<'_>,
94 kind: &SnapshotDataKind,
95) -> Result<Vec<u8>, IcSnapshotArtifactError> {
96 match member::select(kind)? {
97 member::Member::Region {
98 region,
99 offset,
100 size,
101 } => {
102 let length = metadata.region_sizes()[region.index()];
103 let (mut file, _) =
104 open_regular_child(directory, REGIONS[region.index()], Some(length), length)?;
105 file.seek(SeekFrom::Start(offset))?;
106 read_chunk_bytes(file, size)
107 }
108 member::Member::Chunk { name } => {
109 let (mut file, original) = open_regular_child(
110 directory,
111 &name,
112 None,
113 MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64,
114 )?;
115 file.seek(SeekFrom::Start(0))?;
116 read_chunk_bytes(file, original.len())
117 }
118 }
119}
120
121fn read_chunk_bytes(reader: impl Read, size: u64) -> Result<Vec<u8>, IcSnapshotArtifactError> {
124 #[cfg(test)]
125 let reader = super::tests::read_count::Reader(reader);
126 let limit = usize::try_from(size)
127 .ok()
128 .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
129 .ok_or(IcSnapshotArtifactError::FileShape)?;
130 ic_host_artifacts::artifact::read_exact_reader(reader.take(size), limit).map_err(|error| {
131 use ic_host_artifacts::artifact::ArtifactError;
132 match error {
133 ArtifactError::LimitExceeded { .. } | ArtifactError::LengthMismatch { .. } => {
134 IcSnapshotArtifactError::FileShape
135 }
136 error => IcSnapshotArtifactError::Io(error.into()),
137 }
138 })
139}
140
141#[derive(Debug, Error)]
143pub enum IcSnapshotUploadArtifactError {
144 #[error("snapshot upload source checksum differs")]
146 SourceMismatch,
147 #[error(transparent)]
149 Artifact(#[from] IcSnapshotArtifactError),
150 #[error(transparent)]
152 Upload(#[from] IcSnapshotUploadError),
153}
154
155#[cfg(test)]
156mod tests;