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 = "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}