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::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        /// Directory to symlink / copy test contents into. Does not need to be
17        /// empty.
18        pub test_content_dir: ReadVar<PathBuf>,
19        /// Specify where VMM tests disk images are stored.
20        pub disk_images_dir: Option<ReadVar<PathBuf>>,
21        /// What triple VMM tests are built for.
22        ///
23        /// Used to detect cases of running Windows VMM tests via WSL2, and adjusting
24        /// reported paths appropriately.
25        pub vmm_tests_target: target_lexicon::Triple,
26
27        /// Register an openvmm binary
28        pub register_openvmm: Option<ReadVar<crate::build_openvmm::OpenvmmOutput>>,
29        /// Register a windows pipette binary
30        pub register_pipette_windows: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
31        /// Register a linux-musl pipette binary
32        pub register_pipette_linux_musl: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
33        /// Register a guest_test_uefi image
34        pub register_guest_test_uefi:
35            Option<ReadVar<crate::build_guest_test_uefi::GuestTestUefiOutput>>,
36        /// Register OpenHCL IGVM files
37        pub register_openhcl_igvm_files: Option<
38            ReadVar<
39                Vec<(
40                    OpenhclIgvmRecipe,
41                    crate::run_igvmfilegen::IgvmOutput,
42                )>,
43            >,
44        >,
45        /// Register TMK VMM binaries.
46        pub register_tmks: Option<ReadVar<crate::build_tmks::TmksOutput>>,
47        /// Register a TMK VMM native binary
48        pub register_tmk_vmm: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
49        /// Register a TMK VMM Linux musl binary
50        pub register_tmk_vmm_linux_musl: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
51        /// Register a vmgstool binary
52        pub register_vmgstool: Option<ReadVar<crate::build_vmgstool::VmgstoolOutput>>,
53
54        /// Get the path to the folder containing various logs emitted VMM tests.
55        pub get_test_log_path: Option<WriteVar<PathBuf>>,
56        /// Get a map of env vars required to be set when running VMM tests
57        pub get_env: WriteVar<BTreeMap<String, String>>,
58        pub release_igvm_files: Option<ReadVar<crate::download_release_igvm_files_from_gh::ReleaseOutput>>,
59        /// Use paths relative to `test_content_dir` for environment variables
60        pub use_relative_paths: bool,
61    }
62}
63
64new_simple_flow_node!(struct Node);
65
66impl SimpleFlowNode for Node {
67    type Request = Request;
68
69    fn imports(ctx: &mut ImportCtx<'_>) {
70        ctx.import::<crate::download_openvmm_deps::Node>();
71        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
72        ctx.import::<crate::download_uefi_mu_msvm::Node>();
73    }
74
75    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
76        let Request {
77            test_content_dir,
78            vmm_tests_target,
79            register_openvmm,
80            register_pipette_windows,
81            register_pipette_linux_musl,
82            register_guest_test_uefi,
83            register_tmks,
84            register_tmk_vmm,
85            register_tmk_vmm_linux_musl,
86            register_vmgstool,
87            disk_images_dir,
88            register_openhcl_igvm_files,
89            get_test_log_path,
90            get_env,
91            release_igvm_files,
92            use_relative_paths,
93        } = request;
94
95        let openvmm_deps_arch = match vmm_tests_target.architecture {
96            target_lexicon::Architecture::X86_64 => OpenvmmDepsArch::X86_64,
97            target_lexicon::Architecture::Aarch64(_) => OpenvmmDepsArch::Aarch64,
98            arch => anyhow::bail!("unsupported arch {arch}"),
99        };
100
101        let test_linux_initrd = ctx.reqv(|v| {
102            crate::download_openvmm_deps::Request::GetLinuxTestInitrd(openvmm_deps_arch, v)
103        });
104        let test_linux_kernel = ctx.reqv(|v| {
105            crate::download_openvmm_deps::Request::GetLinuxTestKernel(openvmm_deps_arch, v)
106        });
107
108        let mu_msvm_arch = match vmm_tests_target.architecture {
109            target_lexicon::Architecture::X86_64 => MuMsvmArch::X86_64,
110            target_lexicon::Architecture::Aarch64(_) => MuMsvmArch::Aarch64,
111            arch => anyhow::bail!("unsupported arch {arch}"),
112        };
113        let uefi = ctx.reqv(|v| crate::download_uefi_mu_msvm::Request::GetMsvmFd {
114            arch: mu_msvm_arch,
115            msvm_fd: v,
116        });
117
118        ctx.emit_rust_step("setting up vmm_tests env", |ctx| {
119            let test_content_dir = test_content_dir.claim(ctx);
120            let get_env = get_env.claim(ctx);
121            let get_test_log_path = get_test_log_path.claim(ctx);
122            let openvmm = register_openvmm.claim(ctx);
123            let pipette_win = register_pipette_windows.claim(ctx);
124            let pipette_linux = register_pipette_linux_musl.claim(ctx);
125            let guest_test_uefi = register_guest_test_uefi.claim(ctx);
126            let tmks = register_tmks.claim(ctx);
127            let tmk_vmm = register_tmk_vmm.claim(ctx);
128            let tmk_vmm_linux_musl = register_tmk_vmm_linux_musl.claim(ctx);
129            let vmgstool = register_vmgstool.claim(ctx);
130            let disk_image_dir = disk_images_dir.claim(ctx);
131            let openhcl_igvm_files = register_openhcl_igvm_files.claim(ctx);
132            let test_linux_initrd = test_linux_initrd.claim(ctx);
133            let test_linux_kernel = test_linux_kernel.claim(ctx);
134            let uefi = uefi.claim(ctx);
135            let release_igvm_files_dir = release_igvm_files.claim(ctx);
136            move |rt| {
137                let test_linux_initrd = rt.read(test_linux_initrd);
138                let test_linux_kernel = rt.read(test_linux_kernel);
139                let uefi = rt.read(uefi);
140                let release_igvm_files_dir = rt.read(release_igvm_files_dir);
141                let test_content_dir = rt.read(test_content_dir);
142
143                let mut env = BTreeMap::new();
144
145                let windows_via_wsl2 = flowey_lib_common::_util::running_in_wsl(rt)
146                    && matches!(
147                        vmm_tests_target.operating_system,
148                        target_lexicon::OperatingSystem::Windows
149                    );
150
151                let working_dir_ref = test_content_dir.as_path();
152                let working_dir_win = windows_via_wsl2.then(|| {
153                    flowey_lib_common::_util::wslpath::linux_to_win(working_dir_ref)
154                        .display()
155                        .to_string()
156                });
157                let maybe_convert_path = |path: &Path| -> anyhow::Result<String> {
158                    let path = if windows_via_wsl2 {
159                        flowey_lib_common::_util::wslpath::linux_to_win(path)
160                    } else {
161                        path.absolute()
162                            .with_context(|| format!("invalid path {}", path.display()))?
163                    };
164                    let path = if use_relative_paths {
165                        if windows_via_wsl2 {
166                            let working_dir_trimmed =
167                                working_dir_win.as_ref().unwrap().trim_end_matches('\\');
168                            let path_win = path.display().to_string();
169                            let path_trimmed = path_win.trim_end_matches('\\');
170                            PathBuf::from(format!(
171                                "$PSScriptRoot{}",
172                                path_trimmed
173                                    .strip_prefix(working_dir_trimmed)
174                                    .with_context(|| format!(
175                                        "{} not in {}",
176                                        path_win, working_dir_trimmed
177                                    ),)?
178                            ))
179                        } else {
180                            path.strip_prefix(working_dir_ref)
181                                .with_context(|| {
182                                    format!(
183                                        "{} not in {}",
184                                        path.display(),
185                                        working_dir_ref.display()
186                                    )
187                                })?
188                                .to_path_buf()
189                        }
190                    } else {
191                        path
192                    };
193                    Ok(path.display().to_string())
194                };
195
196                env.insert(
197                    "VMM_TESTS_CONTENT_DIR".into(),
198                    maybe_convert_path(&test_content_dir)?,
199                );
200
201                // use a subdir for test logs
202                let test_log_dir = test_content_dir.join("test_results");
203                if !test_log_dir.exists() {
204                    fs_err::create_dir(&test_log_dir)?
205                };
206                env.insert(
207                    "TEST_OUTPUT_PATH".into(),
208                    maybe_convert_path(&test_log_dir)?,
209                );
210
211                if let Some(disk_image_dir) = disk_image_dir {
212                    env.insert(
213                        "VMM_TEST_IMAGES".into(),
214                        maybe_convert_path(&rt.read(disk_image_dir))?,
215                    );
216                }
217
218                if let Some(openvmm) = openvmm {
219                    // TODO OSS: update filenames to use openvmm naming (requires petri updates)
220                    match rt.read(openvmm) {
221                        crate::build_openvmm::OpenvmmOutput::WindowsBin { exe, pdb: _ } => {
222                            fs_err::copy(exe, test_content_dir.join("openvmm.exe"))?;
223                        }
224                        crate::build_openvmm::OpenvmmOutput::LinuxBin { bin, dbg: _ } => {
225                            let dst = test_content_dir.join("openvmm");
226                            fs_err::copy(bin, dst.clone())?;
227                            dst.make_executable()?;
228                        }
229                    }
230                }
231
232                if let Some(pipette_win) = pipette_win {
233                    match rt.read(pipette_win) {
234                        crate::build_pipette::PipetteOutput::WindowsBin { exe, pdb: _ } => {
235                            fs_err::copy(exe, test_content_dir.join("pipette.exe"))?;
236                        }
237                        _ => anyhow::bail!("did not find `pipette.exe` in RegisterPipetteWindows"),
238                    }
239                }
240
241                if let Some(pipette_linux) = pipette_linux {
242                    match rt.read(pipette_linux) {
243                        crate::build_pipette::PipetteOutput::LinuxBin { bin, dbg: _ } => {
244                            fs_err::copy(bin, test_content_dir.join("pipette"))?;
245                        }
246                        _ => {
247                            anyhow::bail!("did not find `pipette.exe` in RegisterPipetteLinuxMusl")
248                        }
249                    }
250                }
251
252                if let Some(guest_test_uefi) = guest_test_uefi {
253                    let crate::build_guest_test_uefi::GuestTestUefiOutput {
254                        efi: _,
255                        pdb: _,
256                        img,
257                    } = rt.read(guest_test_uefi);
258                    fs_err::copy(img, test_content_dir.join("guest_test_uefi.img"))?;
259                }
260
261                if let Some(tmks) = tmks {
262                    let crate::build_tmks::TmksOutput { bin, dbg: _ } = rt.read(tmks);
263                    fs_err::copy(bin, test_content_dir.join("simple_tmk"))?;
264                }
265
266                if let Some(tmk_vmm) = tmk_vmm {
267                    match rt.read(tmk_vmm) {
268                        crate::build_tmk_vmm::TmkVmmOutput::WindowsBin { exe, .. } => {
269                            fs_err::copy(exe, test_content_dir.join("tmk_vmm.exe"))?;
270                        }
271                        crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, .. } => {
272                            let dst = test_content_dir.join("tmk_vmm");
273                            fs_err::copy(bin, &dst)?;
274                            dst.make_executable()?;
275                        }
276                    }
277                }
278
279                if let Some(tmk_vmm_linux_musl) = tmk_vmm_linux_musl {
280                    let crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, dbg: _ } =
281                        rt.read(tmk_vmm_linux_musl)
282                    else {
283                        anyhow::bail!("invalid tmk_vmm output")
284                    };
285                    // Note that this overwrites the previous tmk_vmm. That's
286                    // OK, they should be the same. Fix this when the resolver
287                    // can handle multiple different outputs with the same name.
288                    fs_err::copy(bin, test_content_dir.join("tmk_vmm"))?;
289                }
290
291                if let Some(vmgstool) = vmgstool {
292                    match rt.read(vmgstool) {
293                        crate::build_vmgstool::VmgstoolOutput::WindowsBin { exe, .. } => {
294                            fs_err::copy(exe, test_content_dir.join("vmgstool.exe"))?;
295                        }
296                        crate::build_vmgstool::VmgstoolOutput::LinuxBin { bin, .. } => {
297                            let dst = test_content_dir.join("vmgstool");
298                            fs_err::copy(bin, &dst)?;
299                            dst.make_executable()?;
300                        }
301                    }
302                }
303
304                if let Some(openhcl_igvm_files) = openhcl_igvm_files {
305                    for (recipe, openhcl_igvm) in rt.read(openhcl_igvm_files) {
306                        let crate::run_igvmfilegen::IgvmOutput { igvm_bin, .. } = openhcl_igvm;
307
308                        let filename = match recipe {
309                            OpenhclIgvmRecipe::X64 => "openhcl-x64.bin",
310                            OpenhclIgvmRecipe::X64Devkern => "openhcl-x64-devkern.bin",
311                            OpenhclIgvmRecipe::X64Cvm => "openhcl-x64-cvm.bin",
312                            OpenhclIgvmRecipe::X64TestLinuxDirect => {
313                                "openhcl-x64-test-linux-direct.bin"
314                            }
315                            OpenhclIgvmRecipe::Aarch64 => "openhcl-aarch64.bin",
316                            OpenhclIgvmRecipe::Aarch64Devkern => "openhcl-aarch64-devkern.bin",
317                            _ => {
318                                log::info!("petri doesn't support this OpenHCL recipe: {recipe:?}");
319                                continue;
320                            }
321                        };
322
323                        fs_err::copy(igvm_bin, test_content_dir.join(filename))?;
324                    }
325                }
326
327                if let Some(release_igvm_files) = release_igvm_files_dir {
328                    let latest_release_version = OpenhclReleaseVersion::latest();
329
330                    if let Some(src) = &release_igvm_files.openhcl {
331                        let new_name = format!("{latest_release_version}-x64-openhcl.bin");
332                        fs_err::copy(src, test_content_dir.join(new_name))?;
333                    }
334
335                    if let Some(src) = &release_igvm_files.openhcl_aarch64 {
336                        let new_name = format!("{latest_release_version}-aarch64-openhcl.bin");
337                        fs_err::copy(src, test_content_dir.join(new_name))?;
338                    }
339
340                    if let Some(src) = &release_igvm_files.openhcl_direct {
341                        let new_name = format!("{latest_release_version}-x64-direct-openhcl.bin");
342                        fs_err::copy(src, test_content_dir.join(new_name))?;
343                    }
344                }
345
346                let (arch_dir, kernel_file_name) = match openvmm_deps_arch {
347                    OpenvmmDepsArch::X86_64 => ("x64", "vmlinux"),
348                    OpenvmmDepsArch::Aarch64 => ("aarch64", "Image"),
349                };
350                fs_err::create_dir_all(test_content_dir.join(arch_dir))?;
351                fs_err::copy(
352                    test_linux_initrd,
353                    test_content_dir.join(arch_dir).join("initrd"),
354                )?;
355                fs_err::copy(
356                    test_linux_kernel,
357                    test_content_dir.join(arch_dir).join(kernel_file_name),
358                )?;
359
360                let uefi_dir = test_content_dir
361                    .join(format!(
362                        "hyperv.uefi.mscoreuefi.{}.RELEASE",
363                        match mu_msvm_arch {
364                            MuMsvmArch::Aarch64 => "AARCH64",
365                            MuMsvmArch::X86_64 => "x64",
366                        }
367                    ))
368                    .join(format!(
369                        "Msvm{}",
370                        match mu_msvm_arch {
371                            MuMsvmArch::Aarch64 => "AARCH64",
372                            MuMsvmArch::X86_64 => "X64",
373                        }
374                    ))
375                    .join("RELEASE_VS2022")
376                    .join("FV");
377                fs_err::create_dir_all(&uefi_dir)?;
378                fs_err::copy(uefi, uefi_dir.join("MSVM.fd"))?;
379
380                // debug log the current contents of the dir
381                log::debug!("final folder content: {}", test_content_dir.display());
382                for entry in test_content_dir.read_dir()? {
383                    let entry = entry?;
384                    log::debug!("contains: {:?}", entry.file_name());
385                }
386
387                rt.write(get_env, &env);
388
389                if let Some(var) = get_test_log_path {
390                    rt.write(var, &test_log_dir)
391                }
392
393                Ok(())
394            }
395        });
396
397        Ok(())
398    }
399}