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, validate_kind},
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 pub fn prepare_ic_snapshot_upload_data<'source>(
67 &self,
68 plan: &OperationPlanRecord,
69 snapshot: &str,
70 metadata_upload: &IcSnapshotUploadRequest<'source>,
71 snapshot_id: &[u8],
72 source_kind: SnapshotDataKind,
73 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
74 let metadata = metadata_upload.source();
75 metadata_upload.validate_data_destination(snapshot_id)?;
76 validate_kind(metadata, &source_kind).map_err(IcSnapshotUploadError::from)?;
77 self.with_upload_source(plan, snapshot, metadata_upload, |directory| {
78 let chunk = read_chunk(directory, metadata, &source_kind)?;
79 Ok(IcSnapshotUploadRequest::data(
80 metadata_upload,
81 snapshot_id,
82 source_kind,
83 &chunk,
84 )?)
85 })
86 }
87
88 fn with_upload_source<T>(
89 &self,
90 plan: &OperationPlanRecord,
91 snapshot: &str,
92 metadata_upload: &IcSnapshotUploadRequest<'_>,
93 read: impl FnOnce(&File) -> Result<T, IcSnapshotUploadArtifactError>,
94 ) -> Result<T, IcSnapshotUploadArtifactError> {
95 let metadata = metadata_upload.source();
96 self.require_upload_source(plan, snapshot, metadata_upload)?;
97 let entry = self
98 .record
99 .artifact(metadata.request().target(), snapshot)
100 .map_err(super::DownloadJournalError::from)
101 .map_err(IcSnapshotArtifactError::from)?;
102 with_artifact_directory(self.layout.root(), entry.artifact_path(), read, || {
103 self.require_upload_source(plan, snapshot, metadata_upload)
104 })
105 }
106
107 fn require_upload_source(
108 &self,
109 plan: &OperationPlanRecord,
110 snapshot: &str,
111 upload: &IcSnapshotUploadRequest<'_>,
112 ) -> Result<(), IcSnapshotUploadArtifactError> {
113 if plan.digest() != upload.source_plan().digest() {
114 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
115 }
116 let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
117 if actual != *upload.source_checksum() {
118 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
119 }
120 Ok(())
121 }
122}
123
124fn read_chunk(
125 directory: &File,
126 metadata: &IcSnapshotMetadataReply<'_>,
127 kind: &SnapshotDataKind,
128) -> Result<Vec<u8>, IcSnapshotArtifactError> {
129 match member::select(kind)? {
130 member::Member::Region {
131 region,
132 offset,
133 size,
134 } => {
135 let length = metadata.region_sizes()[region.index()];
136 let (mut file, _) =
137 open_regular_child(directory, REGIONS[region.index()], Some(length), length)?;
138 file.seek(SeekFrom::Start(offset))?;
139 read_chunk_bytes(file, size)
140 }
141 member::Member::Chunk { name } => {
142 let (mut file, original) = open_regular_child(
143 directory,
144 &name,
145 None,
146 MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64,
147 )?;
148 file.seek(SeekFrom::Start(0))?;
149 read_chunk_bytes(file, original.len())
150 }
151 }
152}
153
154fn read_chunk_bytes(reader: impl Read, size: u64) -> Result<Vec<u8>, IcSnapshotArtifactError> {
157 #[cfg(test)]
158 let reader = super::read_count::Reader(reader);
159 let limit = usize::try_from(size)
160 .ok()
161 .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
162 .ok_or(IcSnapshotArtifactError::FileShape)?;
163 ic_host_artifacts::artifact::read_exact_reader(reader.take(size), limit).map_err(|error| {
164 use ic_host_artifacts::artifact::ArtifactError;
165 match error {
166 ArtifactError::LimitExceeded { .. } | ArtifactError::LengthMismatch { .. } => {
167 IcSnapshotArtifactError::FileShape
168 }
169 error => IcSnapshotArtifactError::Io(error.into()),
170 }
171 })
172}
173
174#[derive(Debug, Error)]
176pub enum IcSnapshotUploadArtifactError {
177 #[error("snapshot upload source checksum differs")]
179 SourceMismatch,
180 #[error(transparent)]
182 Artifact(#[from] IcSnapshotArtifactError),
183 #[error(transparent)]
185 Upload(#[from] IcSnapshotUploadError),
186}