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