Skip to main content

flowey_lib_hvlite/
download_vmm_perf_runtime.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Download the pinned VMM.Perf runtime package.
5
6use 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
15// Update the version and all hashes together when refreshing the archives
16// published to the public VMM.Perf runtime source below.
17const 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}