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