1use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe;
8use crate::build_test_igvm_agent_rpc_server::TestIgvmAgentRpcServerOutput;
9use crate::build_tpm_guest_tests::TpmGuestTestsOutput;
10use crate::common::CommonArch;
11use crate::download_release_igvm_files_from_gh::OpenhclReleaseVersion;
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_openvmm_vhost: Option<ReadVar<crate::build_openvmm_vhost::OpenvmmVhostOutput>>,
32 pub register_pipette_windows: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
34 pub register_pipette_linux_musl: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
36 pub register_guest_test_uefi:
38 Option<ReadVar<crate::build_guest_test_uefi::GuestTestUefiOutput>>,
39 pub register_openhcl_igvm_files: Option<
41 ReadVar<
42 Vec<(
43 OpenhclIgvmRecipe,
44 crate::run_igvmfilegen::IgvmOutput,
45 )>,
46 >,
47 >,
48 pub register_tmks: Option<ReadVar<crate::build_tmks::TmksOutput>>,
50 pub register_tmk_vmm: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
52 pub register_tmk_vmm_linux_musl: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
54 pub register_vmgstool: Option<ReadVar<crate::build_vmgstool::VmgstoolOutput>>,
56 pub register_tpm_guest_tests_windows: Option<ReadVar<TpmGuestTestsOutput>>,
58 pub register_tpm_guest_tests_linux: Option<ReadVar<TpmGuestTestsOutput>>,
60 pub register_test_igvm_agent_rpc_server: Option<ReadVar<TestIgvmAgentRpcServerOutput>>,
62
63 pub get_test_log_path: Option<WriteVar<PathBuf>>,
65 pub get_env: WriteVar<BTreeMap<String, String>>,
67 pub release_igvm_files: Option<ReadVar<crate::download_release_igvm_files_from_gh::ReleaseOutput>>,
68 pub use_relative_paths: bool,
70 pub disable_remote_artifacts: bool,
73 pub reuse_prepped_vhds: bool,
75 }
76}
77
78new_simple_flow_node!(struct Node);
79
80impl SimpleFlowNode for Node {
81 type Request = Request;
82
83 fn imports(ctx: &mut ImportCtx<'_>) {
84 ctx.import::<crate::resolve_openvmm_deps::Node>();
85 ctx.import::<crate::resolve_openvmm_test_initrd::Node>();
86 ctx.import::<crate::resolve_openvmm_test_linux_kernel::Node>();
87 ctx.import::<crate::git_checkout_openvmm_repo::Node>();
88 ctx.import::<crate::download_uefi_mu_msvm::Node>();
89 }
90
91 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
92 let Request {
93 test_content_dir,
94 vmm_tests_target,
95 register_openvmm,
96 register_openvmm_vhost,
97 register_pipette_windows,
98 register_pipette_linux_musl,
99 register_guest_test_uefi,
100 register_tmks,
101 register_tmk_vmm,
102 register_tmk_vmm_linux_musl,
103 register_vmgstool,
104 register_tpm_guest_tests_windows,
105 register_tpm_guest_tests_linux,
106 register_test_igvm_agent_rpc_server,
107 disk_images_dir,
108 register_openhcl_igvm_files,
109 get_test_log_path,
110 get_env,
111 release_igvm_files,
112 use_relative_paths,
113 disable_remote_artifacts,
114 reuse_prepped_vhds,
115 } = request;
116
117 let arch = CommonArch::from_architecture(vmm_tests_target.architecture)?;
118
119 let test_linux_initrd =
120 ctx.reqv(|v| crate::resolve_openvmm_test_initrd::Request::Get(arch, v));
121 let test_linux_kernel = ctx.reqv(|v| {
122 crate::resolve_openvmm_test_linux_kernel::Request::Get(
123 crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::Kernel,
124 arch,
125 crate::resolve_openvmm_test_linux_kernel::DEFAULT_LINUX_TEST_KERNEL_VERSION,
126 v,
127 )
128 });
129 let test_linux_bzimage =
130 crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::BzImage
131 .is_available_for(arch)
132 .then(|| {
133 ctx.reqv(|v| {
134 crate::resolve_openvmm_test_linux_kernel::Request::Get(
135 crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::BzImage,
136 arch,
137 crate::resolve_openvmm_test_linux_kernel::DEFAULT_LINUX_TEST_KERNEL_VERSION,
138 v,
139 )
140 })
141 });
142
143 let uefi =
144 ctx.reqv(|v| crate::download_uefi_mu_msvm::Request::GetMsvmFd { arch, msvm_fd: v });
145
146 ctx.emit_rust_step("setting up vmm_tests env", |ctx| {
147 let test_content_dir = test_content_dir.claim(ctx);
148 let get_env = get_env.claim(ctx);
149 let get_test_log_path = get_test_log_path.claim(ctx);
150 let openvmm = register_openvmm.claim(ctx);
151 let openvmm_vhost = register_openvmm_vhost.claim(ctx);
152 let pipette_win = register_pipette_windows.claim(ctx);
153 let pipette_linux = register_pipette_linux_musl.claim(ctx);
154 let guest_test_uefi = register_guest_test_uefi.claim(ctx);
155 let tmks = register_tmks.claim(ctx);
156 let tmk_vmm = register_tmk_vmm.claim(ctx);
157 let tmk_vmm_linux_musl = register_tmk_vmm_linux_musl.claim(ctx);
158 let vmgstool = register_vmgstool.claim(ctx);
159 let test_igvm_agent_rpc_server = register_test_igvm_agent_rpc_server.claim(ctx);
160 let tpm_guest_tests_windows = register_tpm_guest_tests_windows.claim(ctx);
161 let tpm_guest_tests_linux = register_tpm_guest_tests_linux.claim(ctx);
162 let disk_image_dir = disk_images_dir.claim(ctx);
163 let openhcl_igvm_files = register_openhcl_igvm_files.claim(ctx);
164 let test_linux_initrd = test_linux_initrd.claim(ctx);
165 let test_linux_kernel = test_linux_kernel.claim(ctx);
166 let test_linux_bzimage = test_linux_bzimage.claim(ctx);
167 let uefi = uefi.claim(ctx);
168 let release_igvm_files_dir = release_igvm_files.claim(ctx);
169 move |rt| {
170 let test_linux_initrd = rt.read(test_linux_initrd);
171 let test_linux_kernel = rt.read(test_linux_kernel);
172 let test_linux_bzimage = test_linux_bzimage.map(|v| rt.read(v));
173 let uefi = rt.read(uefi);
174 let release_igvm_files_dir = rt.read(release_igvm_files_dir);
175 let test_content_dir = rt.read(test_content_dir);
176
177 let mut env = BTreeMap::new();
178
179 let windows_via_wsl2 = flowey_lib_common::_util::running_in_wsl(rt)
180 && matches!(
181 vmm_tests_target.operating_system,
182 target_lexicon::OperatingSystem::Windows
183 );
184
185 let working_dir_ref = test_content_dir.as_path();
186 let disk_image_dir = disk_image_dir.map(|v| rt.read(v));
187
188 let working_dir_win = windows_via_wsl2.then(|| {
189 flowey_lib_common::_util::wslpath::linux_to_win(rt, working_dir_ref)
190 .display()
191 .to_string()
192 });
193
194 let wsl_convert_path = |path: &Path| -> anyhow::Result<PathBuf> {
197 if windows_via_wsl2 {
198 Ok(flowey_lib_common::_util::wslpath::linux_to_win(rt, path))
199 } else {
200 path.absolute()
201 .with_context(|| format!("invalid path {}", path.display()))
202 }
203 };
204
205 let converted_content_dir = wsl_convert_path(&test_content_dir)?;
207 let test_log_dir = test_content_dir.join("test_results");
208 let converted_log_dir = wsl_convert_path(&test_log_dir)?;
209 let converted_disk_image_dir = disk_image_dir
210 .as_ref()
211 .map(|p| wsl_convert_path(p))
212 .transpose()?;
213
214 let make_portable_path = |path: PathBuf| -> anyhow::Result<String> {
216 let path = if use_relative_paths {
217 if windows_via_wsl2 {
218 let working_dir_trimmed =
219 working_dir_win.as_ref().unwrap().trim_end_matches('\\');
220 let path_win = path.display().to_string();
221 let path_trimmed = path_win.trim_end_matches('\\');
222 PathBuf::from(format!(
223 "$PSScriptRoot{}",
224 path_trimmed
225 .strip_prefix(working_dir_trimmed)
226 .with_context(|| format!(
227 "{} not in {}",
228 path_win, working_dir_trimmed
229 ),)?
230 ))
231 } else {
232 path.strip_prefix(working_dir_ref)
233 .with_context(|| {
234 format!(
235 "{} not in {}",
236 path.display(),
237 working_dir_ref.display()
238 )
239 })?
240 .to_path_buf()
241 }
242 } else {
243 path
244 };
245 Ok(path.display().to_string())
246 };
247
248 env.insert(
249 "VMM_TESTS_CONTENT_DIR".into(),
250 make_portable_path(converted_content_dir)?,
251 );
252
253 if !test_log_dir.exists() {
255 fs_err::create_dir(&test_log_dir)?
256 };
257 env.insert(
258 "TEST_OUTPUT_PATH".into(),
259 make_portable_path(converted_log_dir)?,
260 );
261
262 if let Some(disk_image_dir) = converted_disk_image_dir {
263 env.insert(
264 "VMM_TEST_IMAGES".into(),
265 make_portable_path(disk_image_dir)?,
266 );
267 }
268
269 if disable_remote_artifacts {
270 env.insert("PETRI_REMOTE_ARTIFACTS".into(), "0".into());
271 }
272
273 if reuse_prepped_vhds {
274 env.insert("PETRI_REUSE_PREPPED_VHDS".into(), "1".into());
275 }
276
277 if let Some(openvmm) = openvmm {
278 match rt.read(openvmm) {
280 crate::build_openvmm::OpenvmmOutput::WindowsBin { exe, pdb: _ } => {
281 fs_err::copy(exe, test_content_dir.join("openvmm.exe"))?;
282 }
283 crate::build_openvmm::OpenvmmOutput::LinuxBin { bin, dbg: _ } => {
284 let dst = test_content_dir.join("openvmm");
285 fs_err::copy(bin, dst.clone())?;
286 dst.make_executable()?;
287 }
288 }
289 }
290
291 if let Some(openvmm_vhost) = openvmm_vhost {
292 let crate::build_openvmm_vhost::OpenvmmVhostOutput { bin, dbg: _ } =
293 rt.read(openvmm_vhost);
294 let dst = test_content_dir.join("openvmm_vhost");
295 fs_err::copy(bin, &dst)?;
296 dst.make_executable()?;
297 }
298
299 if let Some(pipette_win) = pipette_win {
300 match rt.read(pipette_win) {
301 crate::build_pipette::PipetteOutput::WindowsBin { exe, pdb: _ } => {
302 fs_err::copy(exe, test_content_dir.join("pipette.exe"))?;
303 }
304 _ => anyhow::bail!("did not find `pipette.exe` in RegisterPipetteWindows"),
305 }
306 }
307
308 if let Some(pipette_linux) = pipette_linux {
309 match rt.read(pipette_linux) {
310 crate::build_pipette::PipetteOutput::LinuxBin { bin, dbg: _ } => {
311 fs_err::copy(bin, test_content_dir.join("pipette"))?;
312 }
313 _ => {
314 anyhow::bail!("did not find `pipette.exe` in RegisterPipetteLinuxMusl")
315 }
316 }
317 }
318
319 if let Some(guest_test_uefi) = guest_test_uefi {
320 let crate::build_guest_test_uefi::GuestTestUefiOutput {
321 efi: _,
322 pdb: _,
323 img,
324 } = rt.read(guest_test_uefi);
325 fs_err::copy(img, test_content_dir.join("guest_test_uefi.img"))?;
326 }
327
328 if let Some(tmks) = tmks {
329 let crate::build_tmks::TmksOutput { bin, dbg: _ } = rt.read(tmks);
330 fs_err::copy(bin, test_content_dir.join("simple_tmk"))?;
331 }
332
333 if let Some(tmk_vmm) = tmk_vmm {
334 match rt.read(tmk_vmm) {
335 crate::build_tmk_vmm::TmkVmmOutput::WindowsBin { exe, .. } => {
336 fs_err::copy(exe, test_content_dir.join("tmk_vmm.exe"))?;
337 }
338 crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, .. } => {
339 let dst = test_content_dir.join("tmk_vmm");
340 fs_err::copy(bin, &dst)?;
341 dst.make_executable()?;
342 }
343 }
344 }
345
346 if let Some(tmk_vmm_linux_musl) = tmk_vmm_linux_musl {
347 let crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, dbg: _ } =
348 rt.read(tmk_vmm_linux_musl)
349 else {
350 anyhow::bail!("invalid tmk_vmm output")
351 };
352 fs_err::copy(bin, test_content_dir.join("tmk_vmm"))?;
356 }
357
358 if let Some(vmgstool) = vmgstool {
359 match rt.read(vmgstool) {
360 crate::build_vmgstool::VmgstoolOutput::WindowsBin { exe, .. } => {
361 fs_err::copy(exe, test_content_dir.join("vmgstool.exe"))?;
362 }
363 crate::build_vmgstool::VmgstoolOutput::LinuxBin { bin, .. } => {
364 let dst = test_content_dir.join("vmgstool");
365 fs_err::copy(bin, &dst)?;
366 dst.make_executable()?;
367 }
368 }
369 }
370
371 if let Some(tpm_guest_tests_windows) = tpm_guest_tests_windows {
372 let TpmGuestTestsOutput::WindowsBin { exe, .. } =
373 rt.read(tpm_guest_tests_windows)
374 else {
375 anyhow::bail!("expected Windows tpm_guest_tests artifact")
376 };
377 fs_err::copy(exe, test_content_dir.join("tpm_guest_tests.exe"))?;
378 }
379
380 if let Some(tpm_guest_tests_linux) = tpm_guest_tests_linux {
381 let TpmGuestTestsOutput::LinuxBin { bin, .. } = rt.read(tpm_guest_tests_linux)
382 else {
383 anyhow::bail!("expected Linux tpm_guest_tests artifact")
384 };
385 let dst = test_content_dir.join("tpm_guest_tests");
386 fs_err::copy(bin, &dst)?;
387 dst.make_executable()?;
388 }
389
390 if let Some(test_igvm_agent_rpc_server) = test_igvm_agent_rpc_server {
391 let TestIgvmAgentRpcServerOutput { exe, .. } =
392 rt.read(test_igvm_agent_rpc_server);
393 fs_err::copy(exe, test_content_dir.join("test_igvm_agent_rpc_server.exe"))?;
394 }
395
396 if let Some(openhcl_igvm_files) = openhcl_igvm_files {
397 for (recipe, openhcl_igvm) in rt.read(openhcl_igvm_files) {
398 let crate::run_igvmfilegen::IgvmOutput { igvm_bin, .. } = openhcl_igvm;
399
400 let filename = match recipe {
401 OpenhclIgvmRecipe::X64 => "openhcl-x64.bin",
402 OpenhclIgvmRecipe::X64Devkern => "openhcl-x64-devkern.bin",
403 OpenhclIgvmRecipe::X64Cvm => "openhcl-x64-cvm.bin",
404 OpenhclIgvmRecipe::X64TestLinuxDirect => {
405 "openhcl-x64-test-linux-direct.bin"
406 }
407 OpenhclIgvmRecipe::Aarch64 => "openhcl-aarch64.bin",
408 OpenhclIgvmRecipe::Aarch64Devkern => "openhcl-aarch64-devkern.bin",
409 _ => {
410 log::info!("petri doesn't support this OpenHCL recipe: {recipe:?}");
411 continue;
412 }
413 };
414
415 fs_err::copy(igvm_bin, test_content_dir.join(filename))?;
416 }
417 }
418
419 if let Some(release_igvm_files) = release_igvm_files_dir {
420 let latest_release_version = OpenhclReleaseVersion::latest();
421
422 if let Some(src) = &release_igvm_files.openhcl {
423 let new_name = format!("{latest_release_version}-x64-openhcl.bin");
424 fs_err::copy(src, test_content_dir.join(new_name))?;
425 }
426
427 if let Some(src) = &release_igvm_files.openhcl_aarch64 {
428 let new_name = format!("{latest_release_version}-aarch64-openhcl.bin");
429 fs_err::copy(src, test_content_dir.join(new_name))?;
430 }
431
432 if let Some(src) = &release_igvm_files.openhcl_direct {
433 let new_name = format!("{latest_release_version}-x64-direct-openhcl.bin");
434 fs_err::copy(src, test_content_dir.join(new_name))?;
435 }
436 }
437
438 let (arch_dir, kernel_file_name) = match arch {
439 CommonArch::X86_64 => ("x64", "vmlinux"),
440 CommonArch::Aarch64 => ("aarch64", "Image"),
441 };
442 fs_err::create_dir_all(test_content_dir.join(arch_dir))?;
443 fs_err::copy(
444 test_linux_initrd,
445 test_content_dir.join(arch_dir).join("initrd"),
446 )?;
447 fs_err::copy(
448 test_linux_kernel,
449 test_content_dir.join(arch_dir).join(kernel_file_name),
450 )?;
451 if let Some(bzimage_path) = test_linux_bzimage {
452 fs_err::copy(
453 bzimage_path,
454 test_content_dir.join(arch_dir).join("bzImage"),
455 )?;
456 }
457
458 let uefi_dir = test_content_dir.join(match arch {
459 CommonArch::Aarch64 => {
460 "hyperv.uefi.mscoreuefi.AARCH64.RELEASE/MsvmAARCH64/RELEASE_CLANGPDB/FV"
461 }
462 CommonArch::X86_64 => {
463 "hyperv.uefi.mscoreuefi.x64.RELEASE/MsvmX64/RELEASE_VS2022/FV"
464 }
465 });
466 fs_err::create_dir_all(&uefi_dir)?;
467 fs_err::copy(uefi, uefi_dir.join("MSVM.fd"))?;
468
469 log::debug!("final folder content: {}", test_content_dir.display());
471 for entry in test_content_dir.read_dir()? {
472 let entry = entry?;
473 log::debug!("contains: {:?}", entry.file_name());
474 }
475
476 rt.write(get_env, &env);
477
478 if let Some(var) = get_test_log_path {
479 rt.write(var, &test_log_dir)
480 }
481
482 Ok(())
483 }
484 });
485
486 Ok(())
487 }
488}