Skip to main content

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::common::CommonArch;
11use crate::download_release_igvm_files_from_gh::OpenhclReleaseVersion;
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 an openvmm_vhost binary (Linux only)
31        pub register_openvmm_vhost: Option<ReadVar<crate::build_openvmm_vhost::OpenvmmVhostOutput>>,
32        /// Register a windows pipette binary
33        pub register_pipette_windows: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
34        /// Register a linux-musl pipette binary
35        pub register_pipette_linux_musl: Option<ReadVar<crate::build_pipette::PipetteOutput>>,
36        /// Register a guest_test_uefi image
37        pub register_guest_test_uefi:
38            Option<ReadVar<crate::build_guest_test_uefi::GuestTestUefiOutput>>,
39        /// Register OpenHCL IGVM files
40        pub register_openhcl_igvm_files: Option<
41            ReadVar<
42                Vec<(
43                    OpenhclIgvmRecipe,
44                    crate::run_igvmfilegen::IgvmOutput,
45                )>,
46            >,
47        >,
48        /// Register TMK VMM binaries.
49        pub register_tmks: Option<ReadVar<crate::build_tmks::TmksOutput>>,
50        /// Register a TMK VMM native binary
51        pub register_tmk_vmm: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
52        /// Register a TMK VMM Linux musl binary
53        pub register_tmk_vmm_linux_musl: Option<ReadVar<crate::build_tmk_vmm::TmkVmmOutput>>,
54        /// Register a vmgstool binary
55        pub register_vmgstool: Option<ReadVar<crate::build_vmgstool::VmgstoolOutput>>,
56        /// Register a Windows tpm_guest_tests binary
57        pub register_tpm_guest_tests_windows: Option<ReadVar<TpmGuestTestsOutput>>,
58        /// Register a Linux tpm_guest_tests binary
59        pub register_tpm_guest_tests_linux: Option<ReadVar<TpmGuestTestsOutput>>,
60        /// Register a Windows test_igvm_agent_rpc_server binary
61        pub register_test_igvm_agent_rpc_server: Option<ReadVar<TestIgvmAgentRpcServerOutput>>,
62
63        /// Get the path to the folder containing various logs emitted VMM tests.
64        pub get_test_log_path: Option<WriteVar<PathBuf>>,
65        /// Get a map of env vars required to be set when running VMM tests
66        pub get_env: WriteVar<BTreeMap<String, String>>,
67        pub release_igvm_files: Option<ReadVar<crate::download_release_igvm_files_from_gh::ReleaseOutput>>,
68        /// Use paths relative to `test_content_dir` for environment variables
69        pub use_relative_paths: bool,
70        /// Disable lazy remote artifact fetching (set PETRI_REMOTE_ARTIFACTS=0).
71        /// Should be true in CI where all images are pre-downloaded.
72        pub disable_remote_artifacts: bool,
73        /// Whether to reuse VHDs created with prep_steps
74        pub reuse_prepped_vhds: bool,
75    }
76}
77
78new_simple_flow_node!(struct Node);
79
80impl SimpleFlowNode for Node {
81    type Request = Request;
82
83    fn imports(ctx: &mut ImportCtx<'_>) {
84        ctx.import::<crate::resolve_openvmm_deps::Node>();
85        ctx.import::<crate::resolve_openvmm_test_initrd::Node>();
86        ctx.import::<crate::resolve_openvmm_test_linux_kernel::Node>();
87        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
88        ctx.import::<crate::download_uefi_mu_msvm::Node>();
89    }
90
91    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
92        let Request {
93            test_content_dir,
94            vmm_tests_target,
95            register_openvmm,
96            register_openvmm_vhost,
97            register_pipette_windows,
98            register_pipette_linux_musl,
99            register_guest_test_uefi,
100            register_tmks,
101            register_tmk_vmm,
102            register_tmk_vmm_linux_musl,
103            register_vmgstool,
104            register_tpm_guest_tests_windows,
105            register_tpm_guest_tests_linux,
106            register_test_igvm_agent_rpc_server,
107            disk_images_dir,
108            register_openhcl_igvm_files,
109            get_test_log_path,
110            get_env,
111            release_igvm_files,
112            use_relative_paths,
113            disable_remote_artifacts,
114            reuse_prepped_vhds,
115        } = request;
116
117        let arch = CommonArch::from_architecture(vmm_tests_target.architecture)?;
118
119        let test_linux_initrd =
120            ctx.reqv(|v| crate::resolve_openvmm_test_initrd::Request::Get(arch, v));
121        let test_linux_kernel = ctx.reqv(|v| {
122            crate::resolve_openvmm_test_linux_kernel::Request::Get(
123                crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::Kernel,
124                arch,
125                crate::resolve_openvmm_test_linux_kernel::DEFAULT_LINUX_TEST_KERNEL_VERSION,
126                v,
127            )
128        });
129        let test_linux_bzimage =
130            crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::BzImage
131                .is_available_for(arch)
132                .then(|| {
133                    ctx.reqv(|v| {
134                        crate::resolve_openvmm_test_linux_kernel::Request::Get(
135                            crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::BzImage,
136                            arch,
137                            crate::resolve_openvmm_test_linux_kernel::DEFAULT_LINUX_TEST_KERNEL_VERSION,
138                            v,
139                        )
140                    })
141                });
142
143        let uefi =
144            ctx.reqv(|v| crate::download_uefi_mu_msvm::Request::GetMsvmFd { arch, msvm_fd: v });
145
146        ctx.emit_rust_step("setting up vmm_tests env", |ctx| {
147            let test_content_dir = test_content_dir.claim(ctx);
148            let get_env = get_env.claim(ctx);
149            let get_test_log_path = get_test_log_path.claim(ctx);
150            let openvmm = register_openvmm.claim(ctx);
151            let openvmm_vhost = register_openvmm_vhost.claim(ctx);
152            let pipette_win = register_pipette_windows.claim(ctx);
153            let pipette_linux = register_pipette_linux_musl.claim(ctx);
154            let guest_test_uefi = register_guest_test_uefi.claim(ctx);
155            let tmks = register_tmks.claim(ctx);
156            let tmk_vmm = register_tmk_vmm.claim(ctx);
157            let tmk_vmm_linux_musl = register_tmk_vmm_linux_musl.claim(ctx);
158            let vmgstool = register_vmgstool.claim(ctx);
159            let test_igvm_agent_rpc_server = register_test_igvm_agent_rpc_server.claim(ctx);
160            let tpm_guest_tests_windows = register_tpm_guest_tests_windows.claim(ctx);
161            let tpm_guest_tests_linux = register_tpm_guest_tests_linux.claim(ctx);
162            let disk_image_dir = disk_images_dir.claim(ctx);
163            let openhcl_igvm_files = register_openhcl_igvm_files.claim(ctx);
164            let test_linux_initrd = test_linux_initrd.claim(ctx);
165            let test_linux_kernel = test_linux_kernel.claim(ctx);
166            let test_linux_bzimage = test_linux_bzimage.claim(ctx);
167            let uefi = uefi.claim(ctx);
168            let release_igvm_files_dir = release_igvm_files.claim(ctx);
169            move |rt| {
170                let test_linux_initrd = rt.read(test_linux_initrd);
171                let test_linux_kernel = rt.read(test_linux_kernel);
172                let test_linux_bzimage = test_linux_bzimage.map(|v| rt.read(v));
173                let uefi = rt.read(uefi);
174                let release_igvm_files_dir = rt.read(release_igvm_files_dir);
175                let test_content_dir = rt.read(test_content_dir);
176
177                let mut env = BTreeMap::new();
178
179                let windows_via_wsl2 = flowey_lib_common::_util::running_in_wsl(rt)
180                    && matches!(
181                        vmm_tests_target.operating_system,
182                        target_lexicon::OperatingSystem::Windows
183                    );
184
185                let working_dir_ref = test_content_dir.as_path();
186                let disk_image_dir = disk_image_dir.map(|v| rt.read(v));
187
188                let working_dir_win = windows_via_wsl2.then(|| {
189                    flowey_lib_common::_util::wslpath::linux_to_win(rt, working_dir_ref)
190                        .display()
191                        .to_string()
192                });
193
194                // Convert a path via wslpath if running under WSL2,
195                // otherwise just make it absolute.
196                let wsl_convert_path = |path: &Path| -> anyhow::Result<PathBuf> {
197                    if windows_via_wsl2 {
198                        Ok(flowey_lib_common::_util::wslpath::linux_to_win(rt, path))
199                    } else {
200                        path.absolute()
201                            .with_context(|| format!("invalid path {}", path.display()))
202                    }
203                };
204
205                // Eagerly convert all known paths.
206                let converted_content_dir = wsl_convert_path(&test_content_dir)?;
207                let test_log_dir = test_content_dir.join("test_results");
208                let converted_log_dir = wsl_convert_path(&test_log_dir)?;
209                let converted_disk_image_dir = disk_image_dir
210                    .as_ref()
211                    .map(|p| wsl_convert_path(p))
212                    .transpose()?;
213
214                // Make a converted path relative if requested.
215                let make_portable_path = |path: PathBuf| -> anyhow::Result<String> {
216                    let path = if use_relative_paths {
217                        if windows_via_wsl2 {
218                            let working_dir_trimmed =
219                                working_dir_win.as_ref().unwrap().trim_end_matches('\\');
220                            let path_win = path.display().to_string();
221                            let path_trimmed = path_win.trim_end_matches('\\');
222                            PathBuf::from(format!(
223                                "$PSScriptRoot{}",
224                                path_trimmed
225                                    .strip_prefix(working_dir_trimmed)
226                                    .with_context(|| format!(
227                                        "{} not in {}",
228                                        path_win, working_dir_trimmed
229                                    ),)?
230                            ))
231                        } else {
232                            path.strip_prefix(working_dir_ref)
233                                .with_context(|| {
234                                    format!(
235                                        "{} not in {}",
236                                        path.display(),
237                                        working_dir_ref.display()
238                                    )
239                                })?
240                                .to_path_buf()
241                        }
242                    } else {
243                        path
244                    };
245                    Ok(path.display().to_string())
246                };
247
248                env.insert(
249                    "VMM_TESTS_CONTENT_DIR".into(),
250                    make_portable_path(converted_content_dir)?,
251                );
252
253                // use a subdir for test logs
254                if !test_log_dir.exists() {
255                    fs_err::create_dir(&test_log_dir)?
256                };
257                env.insert(
258                    "TEST_OUTPUT_PATH".into(),
259                    make_portable_path(converted_log_dir)?,
260                );
261
262                if let Some(disk_image_dir) = converted_disk_image_dir {
263                    env.insert(
264                        "VMM_TEST_IMAGES".into(),
265                        make_portable_path(disk_image_dir)?,
266                    );
267                }
268
269                if disable_remote_artifacts {
270                    env.insert("PETRI_REMOTE_ARTIFACTS".into(), "0".into());
271                }
272
273                if reuse_prepped_vhds {
274                    env.insert("PETRI_REUSE_PREPPED_VHDS".into(), "1".into());
275                }
276
277                if let Some(openvmm) = openvmm {
278                    // TODO OSS: update filenames to use openvmm naming (requires petri updates)
279                    match rt.read(openvmm) {
280                        crate::build_openvmm::OpenvmmOutput::WindowsBin { exe, pdb: _ } => {
281                            fs_err::copy(exe, test_content_dir.join("openvmm.exe"))?;
282                        }
283                        crate::build_openvmm::OpenvmmOutput::LinuxBin { bin, dbg: _ } => {
284                            let dst = test_content_dir.join("openvmm");
285                            fs_err::copy(bin, dst.clone())?;
286                            dst.make_executable()?;
287                        }
288                    }
289                }
290
291                if let Some(openvmm_vhost) = openvmm_vhost {
292                    let crate::build_openvmm_vhost::OpenvmmVhostOutput { bin, dbg: _ } =
293                        rt.read(openvmm_vhost);
294                    let dst = test_content_dir.join("openvmm_vhost");
295                    fs_err::copy(bin, &dst)?;
296                    dst.make_executable()?;
297                }
298
299                if let Some(pipette_win) = pipette_win {
300                    match rt.read(pipette_win) {
301                        crate::build_pipette::PipetteOutput::WindowsBin { exe, pdb: _ } => {
302                            fs_err::copy(exe, test_content_dir.join("pipette.exe"))?;
303                        }
304                        _ => anyhow::bail!("did not find `pipette.exe` in RegisterPipetteWindows"),
305                    }
306                }
307
308                if let Some(pipette_linux) = pipette_linux {
309                    match rt.read(pipette_linux) {
310                        crate::build_pipette::PipetteOutput::LinuxBin { bin, dbg: _ } => {
311                            fs_err::copy(bin, test_content_dir.join("pipette"))?;
312                        }
313                        _ => {
314                            anyhow::bail!("did not find `pipette.exe` in RegisterPipetteLinuxMusl")
315                        }
316                    }
317                }
318
319                if let Some(guest_test_uefi) = guest_test_uefi {
320                    let crate::build_guest_test_uefi::GuestTestUefiOutput {
321                        efi: _,
322                        pdb: _,
323                        img,
324                    } = rt.read(guest_test_uefi);
325                    fs_err::copy(img, test_content_dir.join("guest_test_uefi.img"))?;
326                }
327
328                if let Some(tmks) = tmks {
329                    let crate::build_tmks::TmksOutput { bin, dbg: _ } = rt.read(tmks);
330                    fs_err::copy(bin, test_content_dir.join("simple_tmk"))?;
331                }
332
333                if let Some(tmk_vmm) = tmk_vmm {
334                    match rt.read(tmk_vmm) {
335                        crate::build_tmk_vmm::TmkVmmOutput::WindowsBin { exe, .. } => {
336                            fs_err::copy(exe, test_content_dir.join("tmk_vmm.exe"))?;
337                        }
338                        crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, .. } => {
339                            let dst = test_content_dir.join("tmk_vmm");
340                            fs_err::copy(bin, &dst)?;
341                            dst.make_executable()?;
342                        }
343                    }
344                }
345
346                if let Some(tmk_vmm_linux_musl) = tmk_vmm_linux_musl {
347                    let crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin, dbg: _ } =
348                        rt.read(tmk_vmm_linux_musl)
349                    else {
350                        anyhow::bail!("invalid tmk_vmm output")
351                    };
352                    // Note that this overwrites the previous tmk_vmm. That's
353                    // OK, they should be the same. Fix this when the resolver
354                    // can handle multiple different outputs with the same name.
355                    fs_err::copy(bin, test_content_dir.join("tmk_vmm"))?;
356                }
357
358                if let Some(vmgstool) = vmgstool {
359                    match rt.read(vmgstool) {
360                        crate::build_vmgstool::VmgstoolOutput::WindowsBin { exe, .. } => {
361                            fs_err::copy(exe, test_content_dir.join("vmgstool.exe"))?;
362                        }
363                        crate::build_vmgstool::VmgstoolOutput::LinuxBin { bin, .. } => {
364                            let dst = test_content_dir.join("vmgstool");
365                            fs_err::copy(bin, &dst)?;
366                            dst.make_executable()?;
367                        }
368                    }
369                }
370
371                if let Some(tpm_guest_tests_windows) = tpm_guest_tests_windows {
372                    let TpmGuestTestsOutput::WindowsBin { exe, .. } =
373                        rt.read(tpm_guest_tests_windows)
374                    else {
375                        anyhow::bail!("expected Windows tpm_guest_tests artifact")
376                    };
377                    fs_err::copy(exe, test_content_dir.join("tpm_guest_tests.exe"))?;
378                }
379
380                if let Some(tpm_guest_tests_linux) = tpm_guest_tests_linux {
381                    let TpmGuestTestsOutput::LinuxBin { bin, .. } = rt.read(tpm_guest_tests_linux)
382                    else {
383                        anyhow::bail!("expected Linux tpm_guest_tests artifact")
384                    };
385                    let dst = test_content_dir.join("tpm_guest_tests");
386                    fs_err::copy(bin, &dst)?;
387                    dst.make_executable()?;
388                }
389
390                if let Some(test_igvm_agent_rpc_server) = test_igvm_agent_rpc_server {
391                    let TestIgvmAgentRpcServerOutput { exe, .. } =
392                        rt.read(test_igvm_agent_rpc_server);
393                    fs_err::copy(exe, test_content_dir.join("test_igvm_agent_rpc_server.exe"))?;
394                }
395
396                if let Some(openhcl_igvm_files) = openhcl_igvm_files {
397                    for (recipe, openhcl_igvm) in rt.read(openhcl_igvm_files) {
398                        let crate::run_igvmfilegen::IgvmOutput { igvm_bin, .. } = openhcl_igvm;
399
400                        let filename = match recipe {
401                            OpenhclIgvmRecipe::X64 => "openhcl-x64.bin",
402                            OpenhclIgvmRecipe::X64Devkern => "openhcl-x64-devkern.bin",
403                            OpenhclIgvmRecipe::X64Cvm => "openhcl-x64-cvm.bin",
404                            OpenhclIgvmRecipe::X64TestLinuxDirect => {
405                                "openhcl-x64-test-linux-direct.bin"
406                            }
407                            OpenhclIgvmRecipe::Aarch64 => "openhcl-aarch64.bin",
408                            OpenhclIgvmRecipe::Aarch64Devkern => "openhcl-aarch64-devkern.bin",
409                            _ => {
410                                log::info!("petri doesn't support this OpenHCL recipe: {recipe:?}");
411                                continue;
412                            }
413                        };
414
415                        fs_err::copy(igvm_bin, test_content_dir.join(filename))?;
416                    }
417                }
418
419                if let Some(release_igvm_files) = release_igvm_files_dir {
420                    let latest_release_version = OpenhclReleaseVersion::latest();
421
422                    if let Some(src) = &release_igvm_files.openhcl {
423                        let new_name = format!("{latest_release_version}-x64-openhcl.bin");
424                        fs_err::copy(src, test_content_dir.join(new_name))?;
425                    }
426
427                    if let Some(src) = &release_igvm_files.openhcl_aarch64 {
428                        let new_name = format!("{latest_release_version}-aarch64-openhcl.bin");
429                        fs_err::copy(src, test_content_dir.join(new_name))?;
430                    }
431
432                    if let Some(src) = &release_igvm_files.openhcl_direct {
433                        let new_name = format!("{latest_release_version}-x64-direct-openhcl.bin");
434                        fs_err::copy(src, test_content_dir.join(new_name))?;
435                    }
436                }
437
438                let (arch_dir, kernel_file_name) = match arch {
439                    CommonArch::X86_64 => ("x64", "vmlinux"),
440                    CommonArch::Aarch64 => ("aarch64", "Image"),
441                };
442                fs_err::create_dir_all(test_content_dir.join(arch_dir))?;
443                fs_err::copy(
444                    test_linux_initrd,
445                    test_content_dir.join(arch_dir).join("initrd"),
446                )?;
447                fs_err::copy(
448                    test_linux_kernel,
449                    test_content_dir.join(arch_dir).join(kernel_file_name),
450                )?;
451                if let Some(bzimage_path) = test_linux_bzimage {
452                    fs_err::copy(
453                        bzimage_path,
454                        test_content_dir.join(arch_dir).join("bzImage"),
455                    )?;
456                }
457
458                let uefi_dir = test_content_dir.join(match arch {
459                    CommonArch::Aarch64 => {
460                        "hyperv.uefi.mscoreuefi.AARCH64.RELEASE/MsvmAARCH64/RELEASE_CLANGPDB/FV"
461                    }
462                    CommonArch::X86_64 => {
463                        "hyperv.uefi.mscoreuefi.x64.RELEASE/MsvmX64/RELEASE_VS2022/FV"
464                    }
465                });
466                fs_err::create_dir_all(&uefi_dir)?;
467                fs_err::copy(uefi, uefi_dir.join("MSVM.fd"))?;
468
469                // debug log the current contents of the dir
470                log::debug!("final folder content: {}", test_content_dir.display());
471                for entry in test_content_dir.read_dir()? {
472                    let entry = entry?;
473                    log::debug!("contains: {:?}", entry.file_name());
474                }
475
476                rt.write(get_env, &env);
477
478                if let Some(var) = get_test_log_path {
479                    rt.write(var, &test_log_dir)
480                }
481
482                Ok(())
483            }
484        });
485
486        Ok(())
487    }
488}