ic_backup/ops/persistence/download_journal/ic_snapshot_artifact/upload/
mod.rs1mod plan;
4pub use plan::IcSnapshotDataUploadPreparationError;
5
6use super::super::metrics::LocalOperation;
7use super::{
8 DownloadJournalGuard, File, IcSnapshotArtifactError, Mode, OFlags, REGIONS,
9 check_directory_identity, hex_bytes, open_directory, unix_fs, verification::open_regular_child,
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::{IcSnapshotUploadError, IcSnapshotUploadRequest},
15 operation_plan::OperationPlanRecord,
16};
17use ic_management_canister_types::SnapshotDataKind;
18use std::io::{self, Read, Seek, SeekFrom};
19use std::time::Instant;
20use thiserror::Error;
21
22impl DownloadJournalGuard<'_> {
23 pub fn prepare_ic_snapshot_upload_metadata<'source>(
33 &self,
34 plan: &'source OperationPlanRecord,
35 snapshot: &str,
36 metadata: &'source IcSnapshotMetadataReply<'source>,
37 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
38 let started = Instant::now();
39 let result = (|| {
40 let checksum = self.verify_ic_snapshot_artifact(plan, snapshot, metadata)?;
41 Ok(IcSnapshotUploadRequest::metadata(
42 plan, metadata, &checksum,
43 )?)
44 })();
45 self.record_ic_snapshot_metrics(
46 LocalOperation::UploadMetadata,
47 started,
48 result.is_ok(),
49 None,
50 );
51 result
52 }
53
54 pub fn prepare_ic_snapshot_upload_data<'source>(
66 &self,
67 plan: &OperationPlanRecord,
68 snapshot: &str,
69 metadata_upload: &IcSnapshotUploadRequest<'source>,
70 snapshot_id: &[u8],
71 source_kind: SnapshotDataKind,
72 ) -> Result<IcSnapshotUploadRequest<'source>, IcSnapshotUploadArtifactError> {
73 let started = Instant::now();
74 let result = (|| {
75 let metadata = metadata_upload.source();
76 metadata_upload.validate_data_destination(snapshot_id)?;
77 validate_kind(metadata, &source_kind).map_err(IcSnapshotUploadError::from)?;
78 self.require_upload_source(plan, snapshot, metadata_upload)?;
79 let entry = self
80 .record
81 .artifact(metadata.request().target(), snapshot)
82 .map_err(super::DownloadJournalError::from)
83 .map_err(IcSnapshotArtifactError::from)?;
84 let parent_path = self.layout.root().join("artifacts");
85 let parent = open_directory(&parent_path).map_err(IcSnapshotArtifactError::from)?;
86 let path = self.layout.root().join(entry.artifact_path());
87 let directory = File::from(
88 unix_fs::openat(
89 &parent,
90 path.file_name()
91 .ok_or(IcSnapshotArtifactError::CustodyChanged)?,
92 OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
93 Mode::empty(),
94 )
95 .map_err(io::Error::from)
96 .map_err(IcSnapshotArtifactError::from)?,
97 );
98 let chunk = read_chunk(&directory, metadata, &source_kind)?;
99 let payload =
100 IcSnapshotUploadRequest::data(metadata_upload, snapshot_id, source_kind, &chunk)?;
101 check_directory_identity(&parent_path, &parent)?;
102 check_directory_identity(&path, &directory)?;
103 self.require_upload_source(plan, snapshot, metadata_upload)?;
104 Ok((payload, chunk.len()))
105 })();
106 self.record_ic_snapshot_metrics(
107 LocalOperation::UploadData,
108 started,
109 result.is_ok(),
110 result.as_ref().ok().map(|(_, bytes)| *bytes),
111 );
112 result.map(|(payload, _)| payload)
113 }
114
115 fn require_upload_source(
116 &self,
117 plan: &OperationPlanRecord,
118 snapshot: &str,
119 upload: &IcSnapshotUploadRequest<'_>,
120 ) -> Result<(), IcSnapshotUploadArtifactError> {
121 if plan.digest() != upload.source_plan().digest() {
122 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
123 }
124 let actual = self.verify_ic_snapshot_artifact(plan, snapshot, upload.source())?;
125 if actual != *upload.source_checksum() {
126 return Err(IcSnapshotUploadArtifactError::SourceMismatch);
127 }
128 Ok(())
129 }
130}
131
132fn read_chunk(
133 directory: &File,
134 metadata: &IcSnapshotMetadataReply<'_>,
135 kind: &SnapshotDataKind,
136) -> Result<Vec<u8>, IcSnapshotArtifactError> {
137 let values = metadata.metadata();
138 let (name, length, offset, size) = match kind {
139 SnapshotDataKind::WasmModule { offset, size } => (
140 REGIONS[0].to_owned(),
141 Some(values.wasm_module_size),
142 *offset,
143 Some(*size),
144 ),
145 SnapshotDataKind::WasmMemory { offset, size } => (
146 REGIONS[1].to_owned(),
147 Some(values.wasm_memory_size),
148 *offset,
149 Some(*size),
150 ),
151 SnapshotDataKind::StableMemory { offset, size } => (
152 REGIONS[2].to_owned(),
153 Some(values.stable_memory_size),
154 *offset,
155 Some(*size),
156 ),
157 SnapshotDataKind::WasmChunk { hash } => {
158 (format!("chunk-{}.bin", hex_bytes(hash)), None, 0, None)
159 }
160 };
161 let maximum = length.unwrap_or(MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES as u64);
162 let (mut file, original) = open_regular_child(directory, &name, length, maximum)?;
163 file.seek(SeekFrom::Start(offset))?;
164 let size = size.unwrap_or(original.len());
165 let limit = usize::try_from(size)
166 .ok()
167 .filter(|size| *size <= MAX_IC_SNAPSHOT_DATA_CHUNK_BYTES)
168 .ok_or(IcSnapshotArtifactError::FileShape)?;
169 let bytes =
170 ic_host_artifacts::artifact::read_reader(file.take(size), limit).map_err(|error| {
171 use ic_host_artifacts::artifact::ArtifactError;
172 match error {
173 ArtifactError::LimitExceeded { .. } => IcSnapshotArtifactError::FileShape,
174 error => IcSnapshotArtifactError::Io(error.into()),
175 }
176 })?;
177 if u64::try_from(bytes.len()).ok() != Some(size) {
178 return Err(IcSnapshotArtifactError::FileShape);
179 }
180 Ok(bytes)
181}
182
183#[derive(Debug, Error)]
185pub enum IcSnapshotUploadArtifactError {
186 #[error("snapshot upload source checksum differs")]
188 SourceMismatch,
189 #[error(transparent)]
191 Artifact(#[from] IcSnapshotArtifactError),
192 #[error(transparent)]
194 Upload(#[from] IcSnapshotUploadError),
195}