Skip to main content

flowey_lib_hvlite/
resolve_openvmm_test_linux_kernel.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Download files from a Linux test kernel `openvmm-deps` GitHub release
5//! artifact, or use a local path if specified.
6//!
7//! Each [`LinuxTestKernelVersion`] variant corresponds to its own
8//! per-kernel-version GitHub release artifact (e.g.
9//! `openvmm-test-linux-6.1.<arch>.<ver>.tar.gz`), so consumers can target
10//! different kernel versions independently. Each archive contains the
11//! primary kernel image (`vmlinux` on x86_64, `Image` on aarch64) and, on
12//! x86_64, an additional `bzImage`-format kernel — see
13//! [`OpenvmmTestKernelFile`] to select between them. The matching guest-
14//! userland initrd is shared across kernel versions and lives in its own
15//! node (see [`crate::resolve_openvmm_test_initrd`]).
16
17use crate::common::CommonArch;
18use flowey::node::prelude::*;
19use std::collections::BTreeMap;
20use std::collections::BTreeSet;
21
22/// Which Linux test kernel version to fetch from the openvmm-deps GitHub
23/// release.
24///
25/// The `openvmm-deps` release currently only ships the 6.1 kernel; additional
26/// kernel lines (e.g. 6.6, 6.12) are intended to be added as purely additive
27/// follow-ups, both upstream and as new variants of this enum.
28#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
29pub enum LinuxTestKernelVersion {
30    Linux6_1,
31}
32
33impl LinuxTestKernelVersion {
34    /// The version string used in the openvmm-deps GitHub release artifact
35    /// filename (e.g. `"6.1"` for `openvmm-test-linux-6.1.<arch>.<ver>.tar.gz`).
36    pub fn artifact_tag(self) -> &'static str {
37        match self {
38            Self::Linux6_1 => "6.1",
39        }
40    }
41}
42
43/// Which file to extract from a per-(arch, kver) `openvmm-test-linux` archive.
44#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
45pub enum OpenvmmTestKernelFile {
46    /// Primary kernel image: `vmlinux` on x86_64, `Image` on aarch64.
47    Kernel,
48    /// `bzImage`-format kernel image. Only available on x86_64.
49    BzImage,
50}
51
52impl OpenvmmTestKernelFile {
53    /// Whether this file is shipped in the archive for the given architecture.
54    pub fn is_available_for(self, arch: CommonArch) -> bool {
55        match self {
56            Self::Kernel => true,
57            Self::BzImage => matches!(arch, CommonArch::X86_64),
58        }
59    }
60
61    /// The filename of this file inside the per-(arch, kver) archive.
62    pub fn filename(self, arch: CommonArch) -> &'static str {
63        match self {
64            Self::Kernel => match arch {
65                CommonArch::X86_64 => "vmlinux",
66                CommonArch::Aarch64 => "Image",
67            },
68            Self::BzImage => "bzImage",
69        }
70    }
71}
72
73/// The default Linux test kernel version. Call sites that don't otherwise care
74/// which kernel they're using should pass this.
75pub const DEFAULT_LINUX_TEST_KERNEL_VERSION: LinuxTestKernelVersion =
76    LinuxTestKernelVersion::Linux6_1;
77
78flowey_config! {
79    /// Config for the resolve_openvmm_test_linux_kernel node.
80    pub struct Config {
81        /// Specify version of the github release to pull from
82        pub version: Option<String>,
83        /// Use locally downloaded openvmm-test-linux contents, keyed by
84        /// (architecture, kernel version)
85        pub local_paths: BTreeMap<(CommonArch, LinuxTestKernelVersion), ConfigVar<PathBuf>>,
86    }
87}
88
89flowey_request! {
90    pub enum Request {
91        /// Get the path to a specific file from the per-(arch, kver) archive.
92        Get(
93            OpenvmmTestKernelFile,
94            CommonArch,
95            LinuxTestKernelVersion,
96            WriteVar<PathBuf>,
97        ),
98    }
99}
100
101new_flow_node_with_config!(struct Node);
102
103impl FlowNodeWithConfig for Node {
104    type Request = Request;
105    type Config = Config;
106
107    fn imports(ctx: &mut ImportCtx<'_>) {
108        ctx.import::<flowey_lib_common::install_dist_pkg::Node>();
109        ctx.import::<flowey_lib_common::download_gh_release::Node>();
110    }
111
112    fn emit(
113        config: Config,
114        requests: Vec<Self::Request>,
115        ctx: &mut NodeCtx<'_>,
116    ) -> anyhow::Result<()> {
117        let Config {
118            version,
119            local_paths,
120        } = config;
121        let mut deps: BTreeMap<
122            (OpenvmmTestKernelFile, CommonArch, LinuxTestKernelVersion),
123            Vec<WriteVar<PathBuf>>,
124        > = BTreeMap::new();
125
126        for req in requests {
127            match req {
128                Request::Get(file, arch, kver, var) => {
129                    if !file.is_available_for(arch) {
130                        anyhow::bail!(
131                            "{file:?} is not available in the openvmm-test-linux archive for {arch:?}"
132                        );
133                    }
134                    deps.entry((file, arch, kver)).or_default().push(var);
135                }
136            }
137        }
138
139        if version.is_some() && !local_paths.is_empty() {
140            anyhow::bail!("Cannot specify both Version and LocalPath requests");
141        }
142
143        if version.is_none() && local_paths.is_empty() {
144            anyhow::bail!("Must specify a Version or LocalPath request");
145        }
146
147        // -- end of req processing -- //
148
149        if deps.is_empty() {
150            return Ok(());
151        }
152
153        if !local_paths.is_empty() {
154            ctx.emit_rust_step("use local openvmm-test-linux", |ctx| {
155                let deps = deps.claim(ctx);
156                let local_paths: BTreeMap<_, _> = local_paths
157                    .into_iter()
158                    .map(|(key, var)| (key, var.claim(ctx)))
159                    .collect();
160                move |rt| {
161                    let resolved_paths: BTreeMap<(CommonArch, LinuxTestKernelVersion), PathBuf> =
162                        local_paths
163                            .into_iter()
164                            .map(|(key, var)| (key, rt.read(var)))
165                            .collect();
166
167                    for ((file, arch, kver), vars) in deps {
168                        let base_dir = resolved_paths.get(&(arch, kver)).ok_or_else(|| {
169                            anyhow::anyhow!("No local path specified for ({:?}, {:?})", arch, kver)
170                        })?;
171                        let path = base_dir.join(file.filename(arch));
172                        rt.write_all(vars, &path)
173                    }
174
175                    Ok(())
176                }
177            });
178
179            return Ok(());
180        }
181
182        // The same per-(arch, kver) archive can satisfy multiple file
183        // requests (e.g. `Kernel` and `BzImage` for the same x86_64 6.1
184        // archive), so dedupe download + extract on `(arch, kver)`.
185        let needed_archives: BTreeSet<(CommonArch, LinuxTestKernelVersion)> =
186            deps.keys().map(|(_, arch, kver)| (*arch, *kver)).collect();
187
188        let mut archives = BTreeMap::new();
189        for (arch, kver) in needed_archives {
190            let version = version.clone().expect("local requests handled above");
191            let arch_str = match arch {
192                CommonArch::X86_64 => "x86_64",
193                CommonArch::Aarch64 => "aarch64",
194            };
195            let kver_str = kver.artifact_tag();
196            let archive = ctx.reqv(|v| flowey_lib_common::download_gh_release::Request {
197                repo_owner: "microsoft".into(),
198                repo_name: "openvmm-deps".into(),
199                needs_auth: false,
200                tag: version.clone(),
201                file_name: format!("openvmm-test-linux-{kver_str}.{arch_str}.{version}.tar.gz"),
202                path: v,
203            });
204            archives.insert((arch, kver), archive);
205        }
206
207        let persistent_dir = ctx.persistent_dir();
208
209        ctx.emit_rust_step("unpack openvmm-test-linux archives", |ctx| {
210            let persistent_dir = persistent_dir.claim(ctx);
211            let archives = archives.claim(ctx);
212            let deps = deps.claim(ctx);
213            let version = version.clone().expect("local requests handled above");
214            move |rt| {
215                let persistent_dir = persistent_dir.map(|d| rt.read(d));
216
217                let mut extract_dirs = BTreeMap::new();
218                for (key, archive) in archives {
219                    let file = rt.read(archive);
220                    let dir = flowey_lib_common::_util::extract::extract_tar_gz_if_new(
221                        rt,
222                        persistent_dir.as_deref(),
223                        &file,
224                        &version,
225                    )?;
226                    extract_dirs.insert(key, dir);
227                }
228
229                for ((file, arch, kver), vars) in deps {
230                    let path = extract_dirs[&(arch, kver)].join(file.filename(arch));
231                    rt.write_all(vars, &path)
232                }
233
234                Ok(())
235            }
236        });
237
238        Ok(())
239    }
240}