1use crate::common::CommonArch;
18use flowey::node::prelude::*;
19use std::collections::BTreeMap;
20use std::collections::BTreeSet;
21
22#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
25pub enum LinuxTestKernelVersion {
26 Linux6_1,
27 Linux6_18,
28 CcaV15,
36 SnpGuest,
38 MshvTest,
40}
41
42impl LinuxTestKernelVersion {
43 pub fn artifact_tag(self) -> &'static str {
46 match self {
47 Self::Linux6_1 => "6.1",
48 Self::Linux6_18 => "6.18",
49 Self::CcaV15 => "cca-v15",
50 Self::SnpGuest => "snp-guest",
51 Self::MshvTest => "mshv-test",
52 }
53 }
54
55 pub fn is_available_for(self, arch: CommonArch) -> bool {
60 match self {
61 Self::Linux6_1 | Self::Linux6_18 => true,
62 Self::CcaV15 => matches!(arch, CommonArch::Aarch64),
63 Self::SnpGuest | Self::MshvTest => matches!(arch, CommonArch::X86_64),
64 }
65 }
66}
67
68#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
70pub enum OpenvmmTestKernelFile {
71 Kernel,
73 BzImage,
75}
76
77impl OpenvmmTestKernelFile {
78 pub fn is_available_for(self, arch: CommonArch) -> bool {
80 match self {
81 Self::Kernel => true,
82 Self::BzImage => matches!(arch, CommonArch::X86_64),
83 }
84 }
85
86 pub fn filename(self, arch: CommonArch) -> &'static str {
88 match self {
89 Self::Kernel => match arch {
90 CommonArch::X86_64 => "vmlinux",
91 CommonArch::Aarch64 => "Image",
92 },
93 Self::BzImage => "bzImage",
94 }
95 }
96}
97
98pub const DEFAULT_LINUX_TEST_KERNEL_VERSION: LinuxTestKernelVersion =
101 LinuxTestKernelVersion::Linux6_18;
102
103pub const CCA_LINUX_TEST_KERNEL_VERSION: LinuxTestKernelVersion = LinuxTestKernelVersion::CcaV15;
110
111pub const SNP_GUEST_LINUX_TEST_KERNEL_VERSION: LinuxTestKernelVersion =
113 LinuxTestKernelVersion::SnpGuest;
114
115flowey_config! {
116 pub struct Config {
118 pub version: Option<String>,
120 pub mshv_version: Option<String>,
122 pub local_paths: BTreeMap<(CommonArch, LinuxTestKernelVersion), ConfigVar<PathBuf>>,
125 }
126}
127
128flowey_request! {
129 pub enum Request {
130 Get(
132 OpenvmmTestKernelFile,
133 CommonArch,
134 LinuxTestKernelVersion,
135 WriteVar<PathBuf>,
136 ),
137 }
138}
139
140new_flow_node_with_config!(struct Node);
141
142impl FlowNodeWithConfig for Node {
143 type Request = Request;
144 type Config = Config;
145
146 fn imports(ctx: &mut ImportCtx<'_>) {
147 ctx.import::<flowey_lib_common::install_dist_pkg::Node>();
148 ctx.import::<flowey_lib_common::download_gh_release::Node>();
149 }
150
151 fn emit(
152 config: Config,
153 requests: Vec<Self::Request>,
154 ctx: &mut NodeCtx<'_>,
155 ) -> anyhow::Result<()> {
156 let Config {
157 version,
158 mshv_version,
159 local_paths,
160 } = config;
161 let mut deps: BTreeMap<
162 (OpenvmmTestKernelFile, CommonArch, LinuxTestKernelVersion),
163 Vec<WriteVar<PathBuf>>,
164 > = BTreeMap::new();
165
166 for req in requests {
167 match req {
168 Request::Get(file, arch, kver, var) => {
169 if !kver.is_available_for(arch) {
170 anyhow::bail!(
171 "test kernel {:?} is not published for {arch:?}",
172 kver.artifact_tag()
173 );
174 }
175 if !file.is_available_for(arch) {
176 anyhow::bail!(
177 "{file:?} is not available in the openvmm-test-linux archive for {arch:?}"
178 );
179 }
180 deps.entry((file, arch, kver)).or_default().push(var);
181 }
182 }
183 }
184
185 if version.is_some() && !local_paths.is_empty() {
186 anyhow::bail!("Cannot specify both Version and LocalPath requests");
187 }
188
189 if version.is_none() && mshv_version.is_none() && local_paths.is_empty() {
190 anyhow::bail!("Must specify a Version or LocalPath request");
191 }
192
193 if deps.is_empty() {
196 return Ok(());
197 }
198
199 if !local_paths.is_empty() {
200 ctx.emit_rust_step("use local openvmm-test-linux", |ctx| {
201 let deps = deps.claim(ctx);
202 let local_paths: BTreeMap<_, _> = local_paths
203 .into_iter()
204 .map(|(key, var)| (key, var.claim(ctx)))
205 .collect();
206 move |rt| {
207 let resolved_paths: BTreeMap<(CommonArch, LinuxTestKernelVersion), PathBuf> =
208 local_paths
209 .into_iter()
210 .map(|(key, var)| (key, rt.read(var)))
211 .collect();
212
213 for ((file, arch, kver), vars) in deps {
214 let base_dir = resolved_paths.get(&(arch, kver)).ok_or_else(|| {
215 anyhow::anyhow!("No local path specified for ({:?}, {:?})", arch, kver)
216 })?;
217 let path = base_dir.join(file.filename(arch));
218 rt.write_all(vars, &path)
219 }
220
221 Ok(())
222 }
223 });
224
225 return Ok(());
226 }
227
228 let needed_archives: BTreeSet<(CommonArch, LinuxTestKernelVersion)> =
232 deps.keys().map(|(_, arch, kver)| (*arch, *kver)).collect();
233 let mut archives = BTreeMap::new();
234 for (arch, kver) in needed_archives {
235 let version = if kver == LinuxTestKernelVersion::MshvTest {
236 mshv_version.as_ref().or(version.as_ref())
237 } else {
238 version.as_ref()
239 }
240 .with_context(|| format!("missing openvmm-test-linux version for {kver:?}"))?
241 .clone();
242 let arch_str = match arch {
243 CommonArch::X86_64 => "x86_64",
244 CommonArch::Aarch64 => "aarch64",
245 };
246 let kver_str = kver.artifact_tag();
247 let archive = ctx.reqv(|v| flowey_lib_common::download_gh_release::Request {
248 repo_owner: crate::common::OPENVMM_GITHUB_OWNER.into(),
249 repo_name: crate::common::OPENVMM_DEPS_GITHUB_REPO.into(),
250 needs_auth: false,
251 tag: version.clone(),
252 file_name: format!("openvmm-test-linux-{kver_str}.{arch_str}.{version}.tar.gz"),
253 path: v,
254 });
255 archives.insert((arch, kver), (archive, version));
256 }
257
258 let persistent_dir = ctx.persistent_dir();
259
260 ctx.emit_rust_step("unpack openvmm-test-linux archives", |ctx| {
261 let persistent_dir = persistent_dir.claim(ctx);
262 let archives: BTreeMap<_, _> = archives
263 .into_iter()
264 .map(|(key, (archive, version))| (key, (archive.claim(ctx), version)))
265 .collect();
266 let deps = deps.claim(ctx);
267 move |rt| {
268 let persistent_dir = persistent_dir.map(|d| rt.read(d));
269
270 let mut extract_dirs = BTreeMap::new();
271 for (key, (archive, version)) in archives {
272 let file = rt.read(archive);
273 let dir = flowey_lib_common::_util::extract::extract_tar_gz_if_new(
274 rt,
275 persistent_dir.as_deref(),
276 &file,
277 &version,
278 )?;
279 extract_dirs.insert(key, dir);
280 }
281
282 for ((file, arch, kver), vars) in deps {
283 let path = extract_dirs
284 .get(&(arch, kver))
285 .with_context(|| {
286 format!(
287 "missing extracted openvmm-test-linux archive for {arch:?} {kver:?}"
288 )
289 })?
290 .join(file.filename(arch));
291 rt.write_all(vars, &path)
292 }
293
294 Ok(())
295 }
296 });
297
298 Ok(())
299 }
300}