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