1use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe;
8use crate::build_tpm_guest_tests::TpmGuestTestsOutput;
9use crate::download_openvmm_deps::OpenvmmDepsArch;
10use crate::download_release_igvm_files_from_gh::OpenhclReleaseVersion;
11use crate::download_uefi_mu_msvm::MuMsvmArch;
12use flowey::node::prelude::*;
13use std::collections::BTreeMap;
14
15flowey_request! {
16 pub struct Request {
17 pub test_content_dir: ReadVar<PathBuf>,
20 pub disk_images_dir: Option<ReadVar<PathBuf>>,
22 pub vmm_tests_target: target_lexicon::Triple,
27
28 pub register_openvmm: Option<ReadVar<crate::build_openvmm::OpenvmmOutput>>,
30 pub register_pipette_windows: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
32 pub register_pipette_linux_musl: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
34 pub register_guest_test_uefi:
36 Option<ReadVar<crate::build_guest_test_uefi::GuestTestUefiOutput>>,
37 pub register_openhcl_igvm_files: Option<
39 ReadVar<
40 Vec<(
41 OpenhclIgvmRecipe,
42 crate::run_igvmfilegen::IgvmOutput,
43 )>,
44 >,
45 >,
46 pub register_tmks: Option<ReadVar<crate::build_tmks::TmksOutput>>,
48 pub register_tmk_vmm: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
50 pub register_tmk_vmm_linux_musl: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
52 pub register_vmgstool: Option<ReadVar<crate::build_vmgstool::VmgstoolOutput>>,
54 pub register_tpm_guest_tests_windows: Option<ReadVar<TpmGuestTestsOutput>>,
56 pub register_tpm_guest_tests_linux: Option<ReadVar<TpmGuestTestsOutput>>,
58
59 pub get_test_log_path: Option<WriteVar<PathBuf>>,
61 pub get_env: WriteVar<BTreeMap<String, String>>,
63 pub release_igvm_files: Option<ReadVar<crate::download_release_igvm_files_from_gh::ReleaseOutput>>,
64 pub use_relative_paths: bool,
66 }
67}
68
69new_simple_flow_node!(struct Node);
70
71impl SimpleFlowNode for Node {
72 type Request = Request;
73
74 fn imports(ctx: &mut ImportCtx<'_>) {
75 ctx.import::<crate::download_openvmm_deps::Node>();
76 ctx.import::<crate::git_checkout_openvmm_repo::Node>();
77 ctx.import::<crate::download_uefi_mu_msvm::Node>();
78 }
79
80 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
81 let Request {
82 test_content_dir,
83 vmm_tests_target,
84 register_openvmm,
85 register_pipette_windows,
86 register_pipette_linux_musl,
87 register_guest_test_uefi,
88 register_tmks,
89 register_tmk_vmm,
90 register_tmk_vmm_linux_musl,
91 register_vmgstool,
92 register_tpm_guest_tests_windows,
93 register_tpm_guest_tests_linux,
94 disk_images_dir,
95 register_openhcl_igvm_files,
96 get_test_log_path,
97 get_env,
98 release_igvm_files,
99 use_relative_paths,
100 } = request;
101
102 let openvmm_deps_arch = match vmm_tests_target.architecture {
103 target_lexicon::Architecture::X86_64 => OpenvmmDepsArch::X86_64,
104 target_lexicon::Architecture::Aarch64(_) => OpenvmmDepsArch::Aarch64,
105 arch => anyhow::bail!("unsupported arch {arch}"),
106 };
107
108 let test_linux_initrd = ctx.reqv(|v| {
109 crate::download_openvmm_deps::Request::GetLinuxTestInitrd(openvmm_deps_arch, v)
110 });
111 let test_linux_kernel = ctx.reqv(|v| {
112 crate::download_openvmm_deps::Request::GetLinuxTestKernel(openvmm_deps_arch, v)
113 });
114
115 let mu_msvm_arch = match vmm_tests_target.architecture {
116 target_lexicon::Architecture::X86_64 => MuMsvmArch::X86_64,
117 target_lexicon::Architecture::Aarch64(_) => MuMsvmArch::Aarch64,
118 arch => anyhow::bail!("unsupported arch {arch}"),
119 };
120 let uefi = ctx.reqv(|v| crate::download_uefi_mu_msvm::Request::GetMsvmFd {
121 arch: mu_msvm_arch,
122 msvm_fd: v,
123 });
124
125 ctx.emit_rust_step("setting up vmm_tests env", |ctx| {
126 let test_content_dir = test_content_dir.claim(ctx);
127 let get_env = get_env.claim(ctx);
128 let get_test_log_path = get_test_log_path.claim(ctx);
129 let openvmm = register_openvmm.claim(ctx);
130 let pipette_win = register_pipette_windows.claim(ctx);
131 let pipette_linux = register_pipette_linux_musl.claim(ctx);
132 let guest_test_uefi = register_guest_test_uefi.claim(ctx);
133 let tmks = register_tmks.claim(ctx);
134 let tmk_vmm = register_tmk_vmm.claim(ctx);
135 let tmk_vmm_linux_musl = register_tmk_vmm_linux_musl.claim(ctx);
136 let vmgstool = register_vmgstool.claim(ctx);
137 let tpm_guest_tests_windows = register_tpm_guest_tests_windows.claim(ctx);
138 let tpm_guest_tests_linux = register_tpm_guest_tests_linux.claim(ctx);
139 let disk_image_dir = disk_images_dir.claim(ctx);
140 let openhcl_igvm_files = register_openhcl_igvm_files.claim(ctx);
141 let test_linux_initrd = test_linux_initrd.claim(ctx);
142 let test_linux_kernel = test_linux_kernel.claim(ctx);
143 let uefi = uefi.claim(ctx);
144 let release_igvm_files_dir = release_igvm_files.claim(ctx);
145 move |rt| {
146 let test_linux_initrd = rt.read(test_linux_initrd);
147 let test_linux_kernel = rt.read(test_linux_kernel);
148 let uefi = rt.read(uefi);
149 let release_igvm_files_dir = rt.read(release_igvm_files_dir);
150 let test_content_dir = rt.read(test_content_dir);
151
152 let mut env = BTreeMap::new();
153
154 let windows_via_wsl2 = flowey_lib_common::_util::running_in_wsl(rt)
155 && matches!(
156 vmm_tests_target.operating_system,
157 target_lexicon::OperatingSystem::Windows
158 );
159
160 let working_dir_ref = test_content_dir.as_path();
161 let working_dir_win = windows_via_wsl2.then(|| {
162 flowey_lib_common::_util::wslpath::linux_to_win(working_dir_ref)
163 .display()
164 .to_string()
165 });
166 let maybe_convert_path = |path: &Path| -> anyhow::Result<String> {
167 let path = if windows_via_wsl2 {
168 flowey_lib_common::_util::wslpath::linux_to_win(path)
169 } else {
170 path.absolute()
171 .with_context(|| format!("invalid path {}", path.display()))?
172 };
173 let path = if use_relative_paths {
174 if windows_via_wsl2 {
175 let working_dir_trimmed =
176 working_dir_win.as_ref().unwrap().trim_end_matches('\\');
177 let path_win = path.display().to_string();
178 let path_trimmed = path_win.trim_end_matches('\\');
179 PathBuf::from(format!(
180 "$PSScriptRoot{}",
181 path_trimmed
182 .strip_prefix(working_dir_trimmed)
183 .with_context(|| format!(
184 "{} not in {}",
185 path_win, working_dir_trimmed
186 ),)?
187 ))
188 } else {
189 path.strip_prefix(working_dir_ref)
190 .with_context(|| {
191 format!(
192 "{} not in {}",
193 path.display(),
194 working_dir_ref.display()
195 )
196 })?
197 .to_path_buf()
198 }
199 } else {
200 path
201 };
202 Ok(path.display().to_string())
203 };
204
205 env.insert(
206 "VMM_TESTS_CONTENT_DIR".into(),
207 maybe_convert_path(&test_content_dir)?,
208 );
209
210 let test_log_dir = test_content_dir.join("test_results");
212 if !test_log_dir.exists() {
213 fs_err::create_dir(&test_log_dir)?
214 };
215 env.insert(
216 "TEST_OUTPUT_PATH".into(),
217 maybe_convert_path(&test_log_dir)?,
218 );
219
220 if let Some(disk_image_dir) = disk_image_dir {
221 env.insert(
222 "VMM_TEST_IMAGES".into(),
223 maybe_convert_path(&rt.read(disk_image_dir))?,
224 );
225 }
226
227 if let Some(openvmm) = openvmm {
228 match rt.read(openvmm) {
230 crate::build_openvmm::OpenvmmOutput::WindowsBin { exe, pdb: _ } => {
231 fs_err::copy(exe, test_content_dir.join("openvmm.exe"))?;
232 }
233 crate::build_openvmm::OpenvmmOutput::LinuxBin { bin, dbg: _ } => {
234 let dst = test_content_dir.join("openvmm");
235 fs_err::copy(bin, dst.clone())?;
236 dst.make_executable()?;
237 }
238 }
239 }
240
241 if let Some(pipette_win) = pipette_win {
242 match rt.read(pipette_win) {
243 crate::build_pipette::PipetteOutput::WindowsBin { exe, pdb: _ } => {
244 fs_err::copy(exe, test_content_dir.join("pipette.exe"))?;
245 }
246 _ => anyhow::bail!("did not find `pipette.exe` in RegisterPipetteWindows"),
247 }
248 }
249
250 if let Some(pipette_linux) = pipette_linux {
251 match rt.read(pipette_linux) {
252 crate::build_pipette::PipetteOutput::LinuxBin { bin, dbg: _ } => {
253 fs_err::copy(bin, test_content_dir.join("pipette"))?;
254 }
255 _ => {
256 anyhow::bail!("did not find `pipette.exe` in RegisterPipetteLinuxMusl")
257 }
258 }
259 }
260
261 if let Some(guest_test_uefi) = guest_test_uefi {
262 let crate::build_guest_test_uefi::GuestTestUefiOutput {
263 efi: _,
264 pdb: _,
265 img,
266 } = rt.read(guest_test_uefi);
267 fs_err::copy(img, test_content_dir.join("guest_test_uefi.img"))?;
268 }
269
270 if let Some(tmks) = tmks {
271 let crate::build_tmks::TmksOutput { bin, dbg: _ } = rt.read(tmks);
272 fs_err::copy(bin, test_content_dir.join("simple_tmk"))?;
273 }
274
275 if let Some(tmk_vmm) = tmk_vmm {
276 match rt.read(tmk_vmm) {
277 crate::build_tmk_vmm::TmkVmmOutput::WindowsBin { exe, .. } => {
278 fs_err::copy(exe, test_content_dir.join("tmk_vmm.exe"))?;
279 }
280 crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, .. } => {
281 let dst = test_content_dir.join("tmk_vmm");
282 fs_err::copy(bin, &dst)?;
283 dst.make_executable()?;
284 }
285 }
286 }
287
288 if let Some(tmk_vmm_linux_musl) = tmk_vmm_linux_musl {
289 let crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, dbg: _ } =
290 rt.read(tmk_vmm_linux_musl)
291 else {
292 anyhow::bail!("invalid tmk_vmm output")
293 };
294 fs_err::copy(bin, test_content_dir.join("tmk_vmm"))?;
298 }
299
300 if let Some(vmgstool) = vmgstool {
301 match rt.read(vmgstool) {
302 crate::build_vmgstool::VmgstoolOutput::WindowsBin { exe, .. } => {
303 fs_err::copy(exe, test_content_dir.join("vmgstool.exe"))?;
304 }
305 crate::build_vmgstool::VmgstoolOutput::LinuxBin { bin, .. } => {
306 let dst = test_content_dir.join("vmgstool");
307 fs_err::copy(bin, &dst)?;
308 dst.make_executable()?;
309 }
310 }
311 }
312
313 if let Some(tpm_guest_tests_windows) = tpm_guest_tests_windows {
314 let TpmGuestTestsOutput::WindowsBin { exe, .. } =
315 rt.read(tpm_guest_tests_windows)
316 else {
317 anyhow::bail!("expected Windows tpm_guest_tests artifact")
318 };
319 fs_err::copy(exe, test_content_dir.join("tpm_guest_tests.exe"))?;
320 }
321
322 if let Some(tpm_guest_tests_linux) = tpm_guest_tests_linux {
323 let TpmGuestTestsOutput::LinuxBin { bin, .. } = rt.read(tpm_guest_tests_linux)
324 else {
325 anyhow::bail!("expected Linux tpm_guest_tests artifact")
326 };
327 let dst = test_content_dir.join("tpm_guest_tests");
328 fs_err::copy(bin, &dst)?;
329 dst.make_executable()?;
330 }
331
332 if let Some(openhcl_igvm_files) = openhcl_igvm_files {
333 for (recipe, openhcl_igvm) in rt.read(openhcl_igvm_files) {
334 let crate::run_igvmfilegen::IgvmOutput { igvm_bin, .. } = openhcl_igvm;
335
336 let filename = match recipe {
337 OpenhclIgvmRecipe::X64 => "openhcl-x64.bin",
338 OpenhclIgvmRecipe::X64Devkern => "openhcl-x64-devkern.bin",
339 OpenhclIgvmRecipe::X64Cvm => "openhcl-x64-cvm.bin",
340 OpenhclIgvmRecipe::X64TestLinuxDirect => {
341 "openhcl-x64-test-linux-direct.bin"
342 }
343 OpenhclIgvmRecipe::Aarch64 => "openhcl-aarch64.bin",
344 OpenhclIgvmRecipe::Aarch64Devkern => "openhcl-aarch64-devkern.bin",
345 _ => {
346 log::info!("petri doesn't support this OpenHCL recipe: {recipe:?}");
347 continue;
348 }
349 };
350
351 fs_err::copy(igvm_bin, test_content_dir.join(filename))?;
352 }
353 }
354
355 if let Some(release_igvm_files) = release_igvm_files_dir {
356 let latest_release_version = OpenhclReleaseVersion::latest();
357
358 if let Some(src) = &release_igvm_files.openhcl {
359 let new_name = format!("{latest_release_version}-x64-openhcl.bin");
360 fs_err::copy(src, test_content_dir.join(new_name))?;
361 }
362
363 if let Some(src) = &release_igvm_files.openhcl_aarch64 {
364 let new_name = format!("{latest_release_version}-aarch64-openhcl.bin");
365 fs_err::copy(src, test_content_dir.join(new_name))?;
366 }
367
368 if let Some(src) = &release_igvm_files.openhcl_direct {
369 let new_name = format!("{latest_release_version}-x64-direct-openhcl.bin");
370 fs_err::copy(src, test_content_dir.join(new_name))?;
371 }
372 }
373
374 let (arch_dir, kernel_file_name) = match openvmm_deps_arch {
375 OpenvmmDepsArch::X86_64 => ("x64", "vmlinux"),
376 OpenvmmDepsArch::Aarch64 => ("aarch64", "Image"),
377 };
378 fs_err::create_dir_all(test_content_dir.join(arch_dir))?;
379 fs_err::copy(
380 test_linux_initrd,
381 test_content_dir.join(arch_dir).join("initrd"),
382 )?;
383 fs_err::copy(
384 test_linux_kernel,
385 test_content_dir.join(arch_dir).join(kernel_file_name),
386 )?;
387
388 let uefi_dir = test_content_dir
389 .join(format!(
390 "hyperv.uefi.mscoreuefi.{}.RELEASE",
391 match mu_msvm_arch {
392 MuMsvmArch::Aarch64 => "AARCH64",
393 MuMsvmArch::X86_64 => "x64",
394 }
395 ))
396 .join(format!(
397 "Msvm{}",
398 match mu_msvm_arch {
399 MuMsvmArch::Aarch64 => "AARCH64",
400 MuMsvmArch::X86_64 => "X64",
401 }
402 ))
403 .join("RELEASE_VS2022")
404 .join("FV");
405 fs_err::create_dir_all(&uefi_dir)?;
406 fs_err::copy(uefi, uefi_dir.join("MSVM.fd"))?;
407
408 log::debug!("final folder content: {}", test_content_dir.display());
410 for entry in test_content_dir.read_dir()? {
411 let entry = entry?;
412 log::debug!("contains: {:?}", entry.file_name());
413 }
414
415 rt.write(get_env, &env);
416
417 if let Some(var) = get_test_log_path {
418 rt.write(var, &test_log_dir)
419 }
420
421 Ok(())
422 }
423 });
424
425 Ok(())
426 }
427}