diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 9d42a3f80..fbc0d8fb5 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2547,7 +2547,7 @@ pub(crate) fn _test_direct_kernel_boot_noacpi(guest: &Guest) { guest.wait_vm_boot().unwrap(); assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1); - assert!(guest.get_total_memory().unwrap_or_default() > 480_000); + guest.validate_memory(None); }); kill_child(&mut child); @@ -2650,10 +2650,16 @@ pub(crate) fn _test_memory_overhead(guest: &Guest, guest_memory_size_kb: u32) { guest.wait_vm_boot().unwrap(); + let max_overhead = if on_kvm_sev_snp() { + MAXIMUM_VMM_OVERHEAD_KB_SEV_SNP + } else { + MAXIMUM_VMM_OVERHEAD_KB + }; + let r = std::panic::catch_unwind(|| { let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb); - eprintln!("Guest memory overhead: {overhead} vs {MAXIMUM_VMM_OVERHEAD_KB}"); - assert!(overhead <= MAXIMUM_VMM_OVERHEAD_KB); + eprintln!("Guest memory overhead: {overhead} vs {max_overhead}"); + assert!(overhead <= max_overhead); }); kill_child(&mut child); diff --git a/cloud-hypervisor/tests/common/utils.rs b/cloud-hypervisor/tests/common/utils.rs index 97a86c2db..4139df727 100644 --- a/cloud-hypervisor/tests/common/utils.rs +++ b/cloud-hypervisor/tests/common/utils.rs @@ -23,6 +23,8 @@ use wait_timeout::ChildExt; const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72; // 10MB is our maximum accepted overhead. pub(crate) const MAXIMUM_VMM_OVERHEAD_KB: u32 = 10 * 1024; +// The KVM SEV-SNP build (igvm+sev_snp+fw_cfg) has a larger size +pub(crate) const MAXIMUM_VMM_OVERHEAD_KB_SEV_SNP: u32 = 12 * 1024; // This enum exists to make it more convenient to // implement test for both D-Bus and REST APIs. diff --git a/cloud-hypervisor/tests/integration_cvm.rs b/cloud-hypervisor/tests/integration_cvm.rs index 1b6bd0c4b..ad498287a 100644 --- a/cloud-hypervisor/tests/integration_cvm.rs +++ b/cloud-hypervisor/tests/integration_cvm.rs @@ -98,6 +98,7 @@ mod common_cvm { } #[test] + #[cfg(not(feature = "kvm"))] fn test_pci_multiple_segments() { // Use 8 segments to test the multiple segment support since it's more than the default 6 // supported by Linux @@ -208,18 +209,21 @@ mod common_cvm { } #[test] + #[cfg(not(feature = "kvm"))] fn test_dmi_uuid() { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_dmi_uuid(&guest); } #[test] + #[cfg(not(feature = "kvm"))] fn test_dmi_oem_strings() { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_dmi_oem_strings(&guest); } #[test] + #[cfg(not(feature = "kvm"))] fn test_dmi_system_and_chassis() { let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME); _test_dmi_system_and_chassis(&guest); @@ -324,6 +328,7 @@ mod common_cvm { } #[test] + #[cfg(not(feature = "kvm"))] fn test_vdpa_block() { assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success()); diff --git a/docs/testing.md b/docs/testing.md index f06a96d6e..3c59932f0 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -354,6 +354,33 @@ mshv,igvm,sev_snp` and requires IGVM files to be present at **Test group:** `common_cvm` (`nproc / 4` threads, retries 3). +#### KVM SEV-SNP + +```shell +scripts/dev_cli.sh tests --integration-cvm --hypervisor kvm +``` + +With `--hypervisor kvm` the script builds with `--features +kvm,igvm,sev_snp,fw_cfg`. On KVM the IGVM is an Oak stage0 firmware image +and the guest kernel is supplied separately (read by stage0 over fw_cfg); +the harness selects this boot model when a guest kernel is present at +`/igvm_files/bzImage`. When no `--test-filter` +is given it runs the full `common_cvm` set. + +Prerequisites: + +- An AMD SEV-SNP-capable KVM host with `/dev/kvm` and `/dev/sev` present + and SNP enabled. +- A KVM-bootable stage0 IGVM file at `/usr/share/cloud-hypervisor/cvm` and + a guest kernel at `/igvm_files/bzImage`. + +To scope the run to a single test explicitly: + +```shell +scripts/dev_cli.sh tests --integration-cvm --hypervisor kvm \ + -- --test-filter test_jammy_simple_launch +``` + ## Performance metrics ```shell diff --git a/scripts/run_integration_tests_cvm.sh b/scripts/run_integration_tests_cvm.sh index 1bd300f48..673b3880a 100755 --- a/scripts/run_integration_tests_cvm.sh +++ b/scripts/run_integration_tests_cvm.sh @@ -12,8 +12,12 @@ mkdir -p "$WORKLOADS_DIR/junit" process_common_args "$@" -test_features="--features mshv,igvm,sev_snp" -build_features="mshv,igvm,sev_snp" +if [ "$hypervisor" = "mshv" ]; then + build_features="mshv,igvm,sev_snp" +else # kvm + build_features="kvm,igvm,sev_snp,fw_cfg" +fi +test_features="--features $build_features" JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME" diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 063509547..e942ea852 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1411,6 +1411,14 @@ impl Guest { "cmdline": self.kernel_cmdline.as_deref().unwrap(), "host_data": generate_host_data(), }); + // On the KVM direct-kernel path the kernel is supplied separately + // and read by stage0 over fw_cfg (see on_kvm_sev_snp). + if let Some(kernel) = sev_snp_direct_kernel() { + body["payload"]["kernel"] = serde_json::json!(kernel.to_str().unwrap()); + body["payload"]["fw_cfg_config"] = serde_json::json!({ + "initramfs": false, + }); + } } else { body["payload"] = serde_json::json!({ "kernel": self.kernel_path.as_deref().unwrap(), @@ -2027,6 +2035,19 @@ impl<'a> GuestCommand<'a> { "--igvm", igvm.to_str().expect("IGVM path is not valid UTF-8"), ]); + // On the KVM direct-kernel path the kernel is supplied separately + // (see on_kvm_sev_snp); pass it plus the guest cmdline so + // console/serial tests route output as in a regular direct boot. + if let Some(kernel) = sev_snp_direct_kernel() { + self.command.args([ + "--kernel", + kernel.to_str().expect("kernel path is not valid UTF-8"), + ]); + if let Some(cmdline) = &self.guest.kernel_cmdline { + self.command.args(["--cmdline", cmdline]); + } + self.command.args(["--fw-cfg-config", "initramfs=off"]); + } self.command .args(["--host-data", generate_host_data().as_str()]); self.command.args([ @@ -2542,6 +2563,16 @@ impl Display for GuestVmType { } } +fn sev_snp_direct_kernel() -> Option { + let kernel_path = PathBuf::from("/igvm_files/bzImage"); + kernel_path.exists().then_some(kernel_path) +} + +// True on the KVM SEV-SNP direct-kernel path. +pub fn on_kvm_sev_snp() -> bool { + sev_snp_direct_kernel().is_some() +} + // Get the direct igvm boot file path based on the console type fn direct_igvm_boot_path(console: Option<&str>) -> Option { // get the default hvc0 igvm file if console string is not passed