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