flowey_lib_hvlite/
init_vmm_tests_env.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Setup the environment variables and directory structure that the VMM tests
5//! require to run.
6
7use 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        /// Directory to symlink / copy test contents into. Does not need to be
18        /// empty.
19        pub test_content_dir: ReadVar<PathBuf>,
20        /// Specify where VMM tests disk images are stored.
21        pub disk_images_dir: Option<ReadVar<PathBuf>>,
22        /// What triple VMM tests are built for.
23        ///
24        /// Used to detect cases of running Windows VMM tests via WSL2, and adjusting
25        /// reported paths appropriately.
26        pub vmm_tests_target: target_lexicon::Triple,
27
28        /// Register an openvmm binary
29        pub register_openvmm: Option<ReadVar<crate::build_openvmm::OpenvmmOutput>>,
30        /// Register a windows pipette binary
31        pub register_pipette_windows: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
32        /// Register a linux-musl pipette binary
33        pub register_pipette_linux_musl: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
34        /// Register a guest_test_uefi image
35        pub register_guest_test_uefi:
36            Option<ReadVar<crate::build_guest_test_uefi::GuestTestUefiOutput>>,
37        /// Register OpenHCL IGVM files
38        pub register_openhcl_igvm_files: Option<
39            ReadVar<
40                Vec<(
41                    OpenhclIgvmRecipe,
42                    crate::run_igvmfilegen::IgvmOutput,
43                )>,
44            >,
45        >,
46        /// Register TMK VMM binaries.
47        pub register_tmks: Option<ReadVar<crate::build_tmks::TmksOutput>>,
48        /// Register a TMK VMM native binary
49        pub register_tmk_vmm: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
50        /// Register a TMK VMM Linux musl binary
51        pub register_tmk_vmm_linux_musl: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
52        /// Register a vmgstool binary
53        pub register_vmgstool: Option<ReadVar<crate::build_vmgstool::VmgstoolOutput>>,
54        /// Register a Windows tpm_guest_tests binary
55        pub register_tpm_guest_tests_windows: Option<ReadVar<TpmGuestTestsOutput>>,
56        /// Register a Linux tpm_guest_tests binary
57        pub register_tpm_guest_tests_linux: Option<ReadVar<TpmGuestTestsOutput>>,
58
59        /// Get the path to the folder containing various logs emitted VMM tests.
60        pub get_test_log_path: Option<WriteVar<PathBuf>>,
61        /// Get a map of env vars required to be set when running VMM tests
62        pub get_env: WriteVar<BTreeMap<String, String>>,
63        pub release_igvm_files: Option<ReadVar<crate::download_release_igvm_files_from_gh::ReleaseOutput>>,
64        /// Use paths relative to `test_content_dir` for environment variables
65        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                // use a subdir for test logs
211                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                    // TODO OSS: update filenames to use openvmm naming (requires petri updates)
229                    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                    // Note that this overwrites the previous tmk_vmm. That's
295                    // OK, they should be the same. Fix this when the resolver
296                    // can handle multiple different outputs with the same name.
297                    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                // debug log the current contents of the dir
409                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}