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_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        /// Directory to symlink / copy test contents into. Does not need to be
19        /// empty.
20        pub test_content_dir: ReadVar<PathBuf>,
21        /// Specify where VMM tests disk images are stored.
22        pub disk_images_dir: Option<ReadVar<PathBuf>>,
23        /// What triple VMM tests are built for.
24        ///
25        /// Used to detect cases of running Windows VMM tests via WSL2, and adjusting
26        /// reported paths appropriately.
27        pub vmm_tests_target: target_lexicon::Triple,
28
29        /// Register an openvmm binary
30        pub register_openvmm: Option<ReadVar<crate::build_openvmm::OpenvmmOutput>>,
31        /// Register a windows pipette binary
32        pub register_pipette_windows: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
33        /// Register a linux-musl pipette binary
34        pub register_pipette_linux_musl: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
35        /// Register a guest_test_uefi image
36        pub register_guest_test_uefi:
37            Option<ReadVar<crate::build_guest_test_uefi::GuestTestUefiOutput>>,
38        /// Register OpenHCL IGVM files
39        pub register_openhcl_igvm_files: Option<
40            ReadVar<
41                Vec<(
42                    OpenhclIgvmRecipe,
43                    crate::run_igvmfilegen::IgvmOutput,
44                )>,
45            >,
46        >,
47        /// Register TMK VMM binaries.
48        pub register_tmks: Option<ReadVar<crate::build_tmks::TmksOutput>>,
49        /// Register a TMK VMM native binary
50        pub register_tmk_vmm: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
51        /// Register a TMK VMM Linux musl binary
52        pub register_tmk_vmm_linux_musl: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
53        /// Register a vmgstool binary
54        pub register_vmgstool: Option<ReadVar<crate::build_vmgstool::VmgstoolOutput>>,
55        /// Register a Windows tpm_guest_tests binary
56        pub register_tpm_guest_tests_windows: Option<ReadVar<TpmGuestTestsOutput>>,
57        /// Register a Linux tpm_guest_tests binary
58        pub register_tpm_guest_tests_linux: Option<ReadVar<TpmGuestTestsOutput>>,
59        /// Register a Windows test_igvm_agent_rpc_server binary
60        pub register_test_igvm_agent_rpc_server: Option<ReadVar<TestIgvmAgentRpcServerOutput>>,
61
62        /// Get the path to the folder containing various logs emitted VMM tests.
63        pub get_test_log_path: Option<WriteVar<PathBuf>>,
64        /// Get a map of env vars required to be set when running VMM tests
65        pub get_env: WriteVar<BTreeMap<String, String>>,
66        pub release_igvm_files: Option<ReadVar<crate::download_release_igvm_files_from_gh::ReleaseOutput>>,
67        /// Use paths relative to `test_content_dir` for environment variables
68        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                // Convert a path via wslpath if running under WSL2,
175                // otherwise just make it absolute.
176                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                // Eagerly convert all known paths.
186                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                // Make a converted path relative if requested.
195                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                // use a subdir for test logs
234                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                    // TODO OSS: update filenames to use openvmm naming (requires petri updates)
251                    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                    // Note that this overwrites the previous tmk_vmm. That's
317                    // OK, they should be the same. Fix this when the resolver
318                    // can handle multiple different outputs with the same name.
319                    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                // debug log the current contents of the dir
437                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}