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