mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
tests: enable KVM SEV-SNP confidential VM integration tests
Bring the confidential VM (CVM) integration tests up on the KVM SEV-SNP
backend in addition to MSHV. On KVM the IGVM is an Oak stage0 firmware
image and the guest kernel is supplied separately: stage0 reads the
kernel, cmdline and E820 over fw_cfg. The test harness selects this
model when a guest kernel is present at /igvm_files/bzImage, mirroring
how the stage0 IGVM is discovered; MSHV keeps using the monolithic IGVM
with the kernel baked in.
- test_infra: stage0 + direct-kernel + fw_cfg boot wiring (both the
command line and the HTTP/D-Bus API path) plus an on_kvm_sev_snp()
helper for tests to branch on.
- tests: the CVM tests that don't work on the KVM SEV-SNP path yet are
gated with #[cfg(not(feature = "kvm"))] inside the common_cvm module.
The MSHV build enables mshv,igvm,sev_snp (no kvm feature) while the
KVM build enables kvm,igvm,sev_snp,fw_cfg, so the cfg compiles these
tests into the MSHV binary only and drops them on KVM; both
hypervisors run the single common_cvm nextest profile. They all still
run on MSHV:
* test_pci_multiple_segments - stage0 places all 64-bit BARs in a
single global window, so a BAR allocated in a different
per-segment window is relocated cross-window and wedges boot.
* test_dmi_uuid / test_dmi_oem_strings /
test_dmi_system_and_chassis - SMBIOS is not delivered to SEV-SNP
guests on the KVM stage0 boot path, so the guest's DMI tables
read empty. VMM follow-up.
* test_vdpa_block - needs host vdpa_sim_blk setup, and vDPA DMA
into SEV-SNP-encrypted memory is unsupported (the guest hangs).
Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
This commit is contained in:
committed by
Rob Bradford
parent
b1d33ec9aa
commit
2fc37a3235
@@ -2547,7 +2547,7 @@ pub(crate) fn _test_direct_kernel_boot_noacpi(guest: &Guest) {
|
|||||||
guest.wait_vm_boot().unwrap();
|
guest.wait_vm_boot().unwrap();
|
||||||
|
|
||||||
assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
|
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);
|
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();
|
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 r = std::panic::catch_unwind(|| {
|
||||||
let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb);
|
let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb);
|
||||||
eprintln!("Guest memory overhead: {overhead} vs {MAXIMUM_VMM_OVERHEAD_KB}");
|
eprintln!("Guest memory overhead: {overhead} vs {max_overhead}");
|
||||||
assert!(overhead <= MAXIMUM_VMM_OVERHEAD_KB);
|
assert!(overhead <= max_overhead);
|
||||||
});
|
});
|
||||||
|
|
||||||
kill_child(&mut child);
|
kill_child(&mut child);
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ use wait_timeout::ChildExt;
|
|||||||
const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72;
|
const QCOW2_INCOMPATIBLE_FEATURES_OFFSET: u64 = 72;
|
||||||
// 10MB is our maximum accepted overhead.
|
// 10MB is our maximum accepted overhead.
|
||||||
pub(crate) const MAXIMUM_VMM_OVERHEAD_KB: u32 = 10 * 1024;
|
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
|
// This enum exists to make it more convenient to
|
||||||
// implement test for both D-Bus and REST APIs.
|
// implement test for both D-Bus and REST APIs.
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ mod common_cvm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "kvm"))]
|
||||||
fn test_pci_multiple_segments() {
|
fn test_pci_multiple_segments() {
|
||||||
// Use 8 segments to test the multiple segment support since it's more than the default 6
|
// Use 8 segments to test the multiple segment support since it's more than the default 6
|
||||||
// supported by Linux
|
// supported by Linux
|
||||||
@@ -208,18 +209,21 @@ mod common_cvm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "kvm"))]
|
||||||
fn test_dmi_uuid() {
|
fn test_dmi_uuid() {
|
||||||
let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME);
|
let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME);
|
||||||
_test_dmi_uuid(&guest);
|
_test_dmi_uuid(&guest);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "kvm"))]
|
||||||
fn test_dmi_oem_strings() {
|
fn test_dmi_oem_strings() {
|
||||||
let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME);
|
let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME);
|
||||||
_test_dmi_oem_strings(&guest);
|
_test_dmi_oem_strings(&guest);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "kvm"))]
|
||||||
fn test_dmi_system_and_chassis() {
|
fn test_dmi_system_and_chassis() {
|
||||||
let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME);
|
let guest = basic_cvm_guest!(JAMMY_IMAGE_NAME);
|
||||||
_test_dmi_system_and_chassis(&guest);
|
_test_dmi_system_and_chassis(&guest);
|
||||||
@@ -324,6 +328,7 @@ mod common_cvm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "kvm"))]
|
||||||
fn test_vdpa_block() {
|
fn test_vdpa_block() {
|
||||||
assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success());
|
assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success());
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
**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
|
## Performance metrics
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ mkdir -p "$WORKLOADS_DIR/junit"
|
|||||||
|
|
||||||
process_common_args "$@"
|
process_common_args "$@"
|
||||||
|
|
||||||
test_features="--features mshv,igvm,sev_snp"
|
if [ "$hypervisor" = "mshv" ]; then
|
||||||
build_features="mshv,igvm,sev_snp"
|
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_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2"
|
||||||
JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME"
|
JAMMY_OS_IMAGE="$WORKLOADS_DIR/$JAMMY_OS_IMAGE_NAME"
|
||||||
|
|||||||
@@ -1411,6 +1411,14 @@ impl Guest {
|
|||||||
"cmdline": self.kernel_cmdline.as_deref().unwrap(),
|
"cmdline": self.kernel_cmdline.as_deref().unwrap(),
|
||||||
"host_data": generate_host_data(),
|
"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 {
|
} else {
|
||||||
body["payload"] = serde_json::json!({
|
body["payload"] = serde_json::json!({
|
||||||
"kernel": self.kernel_path.as_deref().unwrap(),
|
"kernel": self.kernel_path.as_deref().unwrap(),
|
||||||
@@ -2027,6 +2035,19 @@ impl<'a> GuestCommand<'a> {
|
|||||||
"--igvm",
|
"--igvm",
|
||||||
igvm.to_str().expect("IGVM path is not valid UTF-8"),
|
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
|
self.command
|
||||||
.args(["--host-data", generate_host_data().as_str()]);
|
.args(["--host-data", generate_host_data().as_str()]);
|
||||||
self.command.args([
|
self.command.args([
|
||||||
@@ -2542,6 +2563,16 @@ impl Display for GuestVmType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sev_snp_direct_kernel() -> Option<PathBuf> {
|
||||||
|
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
|
// Get the direct igvm boot file path based on the console type
|
||||||
fn direct_igvm_boot_path(console: Option<&str>) -> Option<PathBuf> {
|
fn direct_igvm_boot_path(console: Option<&str>) -> Option<PathBuf> {
|
||||||
// get the default hvc0 igvm file if console string is not passed
|
// get the default hvc0 igvm file if console string is not passed
|
||||||
|
|||||||
Reference in New Issue
Block a user