flowey_lib_hvlite/
download_vmm_perf_runtime.rs1use crate::common::CommonArch;
7use anyhow::Context as _;
8use flowey::node::prelude::*;
9use sha2::Digest as _;
10use sha2::Sha256;
11use std::collections::BTreeMap;
12use std::io::Read as _;
13use std::path::Path;
14
15const VMM_PERF_RUNTIME_VERSION: &str = "20261008.1";
18const VMM_PERF_RUNTIME_LINUX_X64_SHA256: &str =
19 "7a1b7d45f23c595a157d74ad152e25769241737a370c0dd16e03813da9eebf8b";
20const VMM_PERF_RUNTIME_LINUX_ARM64_SHA256: &str =
21 "f3471f563d437a257a9a6f7b58be396ad4f280d6d5c4ebf23bfcdc8e7e9d0697";
22const VMM_PERF_RUNTIME_WINDOWS_X64_SHA256: &str =
23 "7a54f2210c51d2a471c72fbc2e98c9d4438a6e74b405a2f7ec76dbb6242da555";
24const VMM_PERF_RUNTIME_WINDOWS_ARM64_SHA256: &str =
25 "2def83be1b1c5da5f6cd7dffb0a454859136187241c670261e6bccc4a104a517";
26
27flowey_request! {
28 pub enum Request {
29 Get {
30 arch: CommonArch,
31 runtime_archive: WriteVar<PathBuf>,
32 }
33 }
34}
35
36new_flow_node!(struct Node);
37
38impl FlowNode for Node {
39 type Request = Request;
40
41 fn imports(ctx: &mut ImportCtx<'_>) {
42 ctx.import::<flowey_lib_common::download_azcopy::Node>();
43 }
44
45 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
46 let mut requests_by_arch = BTreeMap::<_, Vec<_>>::new();
47 for Request::Get {
48 arch,
49 runtime_archive,
50 } in requests
51 {
52 requests_by_arch
53 .entry(arch)
54 .or_default()
55 .push(runtime_archive);
56 }
57
58 if requests_by_arch.is_empty() {
59 return Ok(());
60 }
61
62 let azcopy = ctx.reqv(flowey_lib_common::download_azcopy::Request::GetAzCopy);
63 let persistent_dir = ctx.persistent_dir();
64 let platform = ctx.platform();
65
66 for (arch, outputs) in requests_by_arch {
67 let (filename, expected_sha256) = runtime_archive_info(platform, arch)?;
68 let url = format!(
69 "https://vmmperfartifactpublic.blob.core.windows.net/perfpackage/{VMM_PERF_RUNTIME_VERSION}/{filename}"
70 );
71
72 ctx.emit_rust_step(format!("download VMM.Perf runtime ({filename})"), |ctx| {
73 let azcopy = azcopy.clone().claim(ctx);
74 let persistent_dir = persistent_dir.clone().claim(ctx);
75 let outputs = outputs.claim(ctx);
76 move |rt| {
77 let cache_dir = if let Some(dir) = persistent_dir {
78 rt.read(dir)
79 } else {
80 rt.sh.current_dir()
81 }
82 .join("vmm-perf")
83 .join(VMM_PERF_RUNTIME_VERSION);
84 fs_err::create_dir_all(&cache_dir)?;
85 let archive = cache_dir.join(filename);
86 let azcopy = rt.read(azcopy);
87
88 if archive.exists()
89 && let Err(err) = verify_sha256(&archive, expected_sha256)
90 {
91 log::warn!(
92 "discarding invalid cached VMM.Perf runtime {}: {err:#}",
93 archive.display()
94 );
95 fs_err::remove_file(&archive).with_context(|| {
96 format!(
97 "failed to remove invalid cached VMM.Perf runtime {}",
98 archive.display()
99 )
100 })?;
101 }
102
103 if !archive.exists() {
104 flowey::shell_cmd!(
105 rt,
106 "{azcopy} copy
107 {url}
108 {archive}
109 --overwrite ifSourceNewer
110 --skip-version-check"
111 )
112 .run()?;
113 }
114
115 verify_sha256(&archive, expected_sha256).or_else(|err| {
116 fs_err::remove_file(&archive).with_context(|| {
117 format!(
118 "failed to remove VMM.Perf runtime with an invalid checksum: {}",
119 archive.display()
120 )
121 })?;
122 Err(err)
123 })?;
124
125 for output in outputs {
126 rt.write(output, &archive.absolute()?);
127 }
128 Ok(())
129 }
130 });
131 }
132
133 Ok(())
134 }
135}
136
137fn runtime_archive_info(
138 platform: FlowPlatform,
139 arch: CommonArch,
140) -> anyhow::Result<(&'static str, &'static str)> {
141 match (platform, arch) {
142 (FlowPlatform::Linux(_), CommonArch::X86_64) => Ok((
143 "vmm-perf-linux-x64.tar.gz",
144 VMM_PERF_RUNTIME_LINUX_X64_SHA256,
145 )),
146 (FlowPlatform::Linux(_), CommonArch::Aarch64) => Ok((
147 "vmm-perf-linux-arm64.tar.gz",
148 VMM_PERF_RUNTIME_LINUX_ARM64_SHA256,
149 )),
150 (FlowPlatform::Windows, CommonArch::X86_64) => {
151 Ok(("vmm-perf-win-x64.zip", VMM_PERF_RUNTIME_WINDOWS_X64_SHA256))
152 }
153 (FlowPlatform::Windows, CommonArch::Aarch64) => Ok((
154 "vmm-perf-win-arm64.zip",
155 VMM_PERF_RUNTIME_WINDOWS_ARM64_SHA256,
156 )),
157 _ => anyhow::bail!("no VMM.Perf runtime archive for {arch:?} on {platform:?}"),
158 }
159}
160
161fn verify_sha256(path: &Path, expected: &str) -> anyhow::Result<()> {
162 let mut file = fs_err::File::open(path)
163 .with_context(|| format!("failed to open VMM.Perf runtime {}", path.display()))?;
164 let mut hasher = Sha256::new();
165 let mut buffer = [0; 64 * 1024];
166 loop {
167 let bytes_read = file
168 .read(&mut buffer)
169 .with_context(|| format!("failed to read VMM.Perf runtime {}", path.display()))?;
170 if bytes_read == 0 {
171 break;
172 }
173 hasher.update(&buffer[..bytes_read]);
174 }
175 let actual = hasher
176 .finalize()
177 .iter()
178 .map(|byte| format!("{byte:02x}"))
179 .collect::<String>();
180 anyhow::ensure!(
181 actual == expected,
182 "VMM.Perf runtime SHA-256 mismatch for {}: expected {expected}, found {actual}",
183 path.display()
184 );
185 Ok(())
186}
187
188#[cfg(test)]
189mod tests {
190 use super::verify_sha256;
191
192 #[test]
193 fn verifies_runtime_sha256() -> anyhow::Result<()> {
194 let scratch = tempfile::tempdir()?;
195 let archive = scratch.path().join("runtime.tar.gz");
196 std::fs::write(&archive, [])?;
197
198 verify_sha256(
199 &archive,
200 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
201 )?;
202 let error = verify_sha256(
203 &archive,
204 "0000000000000000000000000000000000000000000000000000000000000000",
205 )
206 .unwrap_err();
207 assert!(format!("{error:#}").contains("SHA-256 mismatch"));
208 Ok(())
209 }
210}