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