From 258f82602769fee0f6284a0321ba8d57210d8b6a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Fri, 17 Oct 2025 20:03:41 -0700 Subject: [PATCH] tests: Add initial support of CVM test on MSHV This patch adds the skeleton of the CVM test support and modify existing scripts and test framework to enable such scenario. Split the sha1sum to support both regular and CVM guest. Add one test case for CVM. Will further add more test cases. Signed-off-by: Muminul Islam --- Cargo.lock | 1 + cloud-hypervisor/tests/integration.rs | 15 ++++++ scripts/dev_cli.sh | 50 +++++++++++++++++++ scripts/run_integration_tests_cvm.sh | 33 ++++++++++++ .../run_integration_tests_live_migration.sh | 4 +- scripts/run_integration_tests_rate_limiter.sh | 4 +- scripts/run_integration_tests_x86_64.sh | 4 +- scripts/run_metrics.sh | 1 + scripts/sha1sums-x86_64 | 4 -- scripts/sha1sums-x86_64-common | 4 ++ scripts/test-util.sh | 2 + test_infra/Cargo.toml | 1 + test_infra/src/lib.rs | 44 +++++++++++++++- 13 files changed, 155 insertions(+), 12 deletions(-) create mode 100755 scripts/run_integration_tests_cvm.sh create mode 100644 scripts/sha1sums-x86_64-common diff --git a/Cargo.lock b/Cargo.lock index cbca77a69..70f2dc43f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2122,6 +2122,7 @@ dependencies = [ "dirs", "epoll", "libc", + "rand", "serde_json", "ssh2", "thiserror 2.0.18", diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 5093d0537..594a1140b 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -12862,3 +12862,18 @@ mod fw_cfg { handle_child_output(r, &output); } } + +#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] +mod common_cvm { + use vm_memory::GuestAddress; + + use crate::*; + #[test] + fn test_focal_simple_launch() { + let disk_config = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string()); + let mut guest = Guest::new(Box::new(disk_config)); + guest.vm_type = GuestVmType::Confidential; + guest.boot_timeout = DEFAULT_CVM_TCP_LISTENER_TIMEOUT; + _test_simple_launch(&guest) + } +} diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index d055ed743..e537c499c 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -28,6 +28,9 @@ CTR_CLH_ROOT_DIR="/cloud-hypervisor" CTR_CLH_CARGO_BUILT_DIR="${CTR_CLH_ROOT_DIR}/build" CTR_CLH_CARGO_TARGET="${CTR_CLH_CARGO_BUILT_DIR}/cargo_target" CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads" +SRC_IGVM_FILES_PATH="/usr/share/cloud-hypervisor/cvm" +DEST_IGVM_FILES_PATH="$CLH_INTEGRATION_WORKLOADS/igvm_files" +CTR_IGVM_FILES_PATH="/igvm_files" # Container networking option CTR_CLH_NET="bridge" @@ -173,6 +176,23 @@ process_volumes_args() { done } +# Copy IGVM files to the workloads directory +# This is needed for the IGVM integration tests to run +# $1 - source path +# $2 - destination path +copy_igvm_files() { + src=$1 + dest=$2 + + if [ -d "$src" ]; then + say "Copying IGVM files from $src to $dest" + cp "$src"/* "$dest" + else + say_err "IGVM File path '$src' not found on host" + exit 1 + fi +} + cmd_help() { echo "" echo "Cloud Hypervisor $(basename "$0")" @@ -200,6 +220,7 @@ cmd_help() { echo " --integration-windows Run the Windows guest integration tests." echo " --integration-live-migration Run the live-migration integration tests." echo " --integration-rate-limiter Run the rate-limiter integration tests." + echo " --integration-cvm Run the Confidential VM integration tests." echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu" echo " --metrics Generate performance metrics" echo " --coverage Generate code coverage information" @@ -333,6 +354,7 @@ cmd_tests() { integration_windows=false integration_live_migration=false integration_rate_limiter=false + integration_cvm=false metrics=false coverage=false libc="gnu" @@ -351,6 +373,7 @@ cmd_tests() { "--integration-windows") { integration_windows=true; } ;; "--integration-live-migration") { integration_live_migration=true; } ;; "--integration-rate-limiter") { integration_rate_limiter=true; } ;; + "--integration-cvm") { integration_cvm=true; } ;; "--metrics") { metrics=true; } ;; "--coverage") { coverage=true; } ;; "--libc") @@ -449,6 +472,33 @@ cmd_tests() { dbus-run-session ./scripts/run_integration_tests_"$(uname -m)".sh "$@" || fix_dir_perms $? || exit $? fi + if [ "$integration_cvm" = true ]; then + mkdir -p "$DEST_IGVM_FILES_PATH" + copy_igvm_files "$SRC_IGVM_FILES_PATH" "$DEST_IGVM_FILES_PATH" + say "Running CVM integration tests for $target..." + $DOCKER_RUNTIME run \ + --workdir "$CTR_CLH_ROOT_DIR" \ + --rm \ + --privileged \ + --security-opt seccomp=unconfined \ + --ipc=host \ + --net="$CTR_CLH_NET" \ + --mount type=tmpfs,destination=/tmp \ + --volume /dev:/dev \ + --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ + --volume "$DEST_IGVM_FILES_PATH:$CTR_IGVM_FILES_PATH" \ + ${exported_volumes:+"$exported_volumes"} \ + --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ + --env USER="root" \ + --env BUILD_TARGET="$target" \ + --env RUSTFLAGS="$rustflags" \ + --env TARGET_CC="$target_cc" \ + --env AUTH_DOWNLOAD_TOKEN="$AUTH_DOWNLOAD_TOKEN" \ + --env LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ + "$CTR_IMAGE" \ + ./scripts/run_integration_tests_cvm.sh "$@" || fix_dir_perms $? || exit $? + fi + if [ "$integration_vfio" = true ]; then say "Running VFIO integration tests for $target..." $DOCKER_RUNTIME run \ diff --git a/scripts/run_integration_tests_cvm.sh b/scripts/run_integration_tests_cvm.sh new file mode 100755 index 000000000..edf543fc8 --- /dev/null +++ b/scripts/run_integration_tests_cvm.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2048,SC2086,SC2154,SC1094 +set -x + +# shellcheck source=/dev/null +source "$HOME"/.cargo/env +source "$(dirname "${BASH_SOURCE[0]}")/test-util.sh" + +WORKLOADS_DIR="$HOME/workloads" +mkdir -p "$WORKLOADS_DIR" + +process_common_args "$@" + +test_features="--features mshv,igvm,sev_snp" +build_features="mshv,igvm,sev_snp" + +download_x86_guest_images +cp scripts/sha1sums-x86_64-common "$WORKLOADS_DIR" + +pushd "$WORKLOADS_DIR" || exit +if ! sha1sum sha1sums-x86_64-common --check; then + echo "sha1sum validation of images failed, remove invalid images to fix the issue." + exit 1 +fi +popd || exit + +cargo build --features $build_features --all --release --target "$BUILD_TARGET" + +export RUST_BACKTRACE=1 +cargo nextest run $test_features "common_cvm::$test_filter" -- ${test_binary_args[*]} +RES=$? + +exit $RES diff --git a/scripts/run_integration_tests_live_migration.sh b/scripts/run_integration_tests_live_migration.sh index f88beed65..11cf59b41 100755 --- a/scripts/run_integration_tests_live_migration.sh +++ b/scripts/run_integration_tests_live_migration.sh @@ -28,7 +28,7 @@ if [ -n "${MIGRATABLE_VERSION}" ]; then fi migratable_version=${MIGRATABLE_VERSION} fi -cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" +cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64-custom-20210609-0.qcow2" FOCAL_OS_IMAGE_URL="https://ch-images.azureedge.net/$FOCAL_OS_IMAGE_NAME" @@ -48,7 +48,7 @@ if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! grep focal sha1sums-x86_64 | sha1sum --check; then +if ! grep focal sha1sums-x86_64-common | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/run_integration_tests_rate_limiter.sh b/scripts/run_integration_tests_rate_limiter.sh index e4e627426..abdf161c9 100755 --- a/scripts/run_integration_tests_rate_limiter.sh +++ b/scripts/run_integration_tests_rate_limiter.sh @@ -18,7 +18,7 @@ if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi -cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" +cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" JAMMY_OS_IMAGE_NAME="jammy-server-cloudimg-amd64-custom-20241017-0.qcow2" JAMMY_OS_IMAGE_URL="https://ch-images.azureedge.net/$JAMMY_OS_IMAGE_NAME" @@ -38,7 +38,7 @@ if [ ! -f "$JAMMY_OS_RAW_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! grep jammy sha1sums-x86_64 | sha1sum --check; then +if ! grep jammy sha1sums-x86_64-common | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/run_integration_tests_x86_64.sh b/scripts/run_integration_tests_x86_64.sh index 225914050..80ac27934 100755 --- a/scripts/run_integration_tests_x86_64.sh +++ b/scripts/run_integration_tests_x86_64.sh @@ -18,7 +18,7 @@ if [ "$hypervisor" = "mshv" ]; then test_features="--features mshv" fi -cp scripts/sha1sums-x86_64 "$WORKLOADS_DIR" +cp scripts/sha1sums-x86_64* "$WORKLOADS_DIR" if [ ! -f "$WORKLOADS_DIR/hypervisor-fw" ]; then download_hypervisor_fw @@ -105,7 +105,7 @@ if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then fi pushd "$WORKLOADS_DIR" || exit -if ! sha1sum sha1sums-x86_64 --check; then +if ! sha1sum sha1sums-x86_64 sha1sums-x86_64-common --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi diff --git a/scripts/run_metrics.sh b/scripts/run_metrics.sh index 988d90aaf..8feeeecb7 100755 --- a/scripts/run_metrics.sh +++ b/scripts/run_metrics.sh @@ -62,6 +62,7 @@ if ! grep focal sha1sums-"${TEST_ARCH}" | sha1sum --check; then echo "sha1sum validation of images failed, remove invalid images to fix the issue." exit 1 fi + popd || exit if [ "${TEST_ARCH}" == "aarch64" ]; then diff --git a/scripts/sha1sums-x86_64 b/scripts/sha1sums-x86_64 index e19881603..c49f00b26 100644 --- a/scripts/sha1sums-x86_64 +++ b/scripts/sha1sums-x86_64 @@ -1,7 +1,3 @@ d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz -f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-20210609-0.qcow2 -7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw -5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 -c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw 540ac358429305d7aa94e15363665d1c9d845982 hypervisor-fw 4e96fd0914a44005d40707b2b0c7e829e4086bd5 CLOUDHV.fd diff --git a/scripts/sha1sums-x86_64-common b/scripts/sha1sums-x86_64-common new file mode 100644 index 000000000..b7fb9923c --- /dev/null +++ b/scripts/sha1sums-x86_64-common @@ -0,0 +1,4 @@ +f1eccdc5e1b515dbad294426ab081b47ebfb97c0 focal-server-cloudimg-amd64-custom-20210609-0.qcow2 +7f5a8358243a96adf61f5c20139b29f308f2c0e3 focal-server-cloudimg-amd64-custom-20210609-0.raw +5f10738920efb74f0bf854cadcd1b1fd544e49c8 jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 +c1dfbe7abde400e675844568dbe9d3914222f6de jammy-server-cloudimg-amd64-custom-20241017-0.raw diff --git a/scripts/test-util.sh b/scripts/test-util.sh index 035084eef..895843933 100644 --- a/scripts/test-util.sh +++ b/scripts/test-util.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# shellcheck disable=SC1009,SC2048,SC2086,SC1073,SC1040,SC1072 +# shellcheck source=/dev/null set -x hypervisor="kvm" diff --git a/test_infra/Cargo.toml b/test_infra/Cargo.toml index e6ea592c3..ec5700a6c 100644 --- a/test_infra/Cargo.toml +++ b/test_infra/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" dirs = { workspace = true } epoll = { workspace = true } libc = { workspace = true } +rand = "0.9.2" serde_json = { workspace = true } ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } thiserror = { workspace = true } diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index d9a04bce9..c62d47832 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -18,6 +18,7 @@ use std::str::FromStr; use std::time::Duration; use std::{env, fmt, fs, io, thread}; +use rand::RngCore; use serde_json::Value; use ssh2::Session; use thiserror::Error; @@ -75,6 +76,7 @@ pub struct GuestNetworkConfig { pub const DEFAULT_TCP_LISTENER_MESSAGE: &str = "booted"; pub const DEFAULT_TCP_LISTENER_PORT: u16 = 8000; pub const DEFAULT_TCP_LISTENER_TIMEOUT: u32 = 120; +pub const DEFAULT_CVM_TCP_LISTENER_TIMEOUT: u32 = 120; #[derive(Error, Debug)] pub enum WaitForBootError { @@ -891,6 +893,7 @@ pub struct Guest { pub boot_timeout: u32, pub kernel_path: Option, pub kernel_cmdline: Option, + pub console_type: Option, } // Return the next id that can be used for this guest. This is stored in a @@ -959,6 +962,7 @@ impl Guest { boot_timeout: DEFAULT_TCP_LISTENER_TIMEOUT, kernel_path: None, kernel_cmdline: None, + console_type: None, } } @@ -1481,8 +1485,20 @@ impl<'a> GuestCommand<'a> { } pub fn default_kernel_cmdline(&mut self) -> &mut Self { - if let Some(kernel) = &self.guest.kernel_path { - self.command.args(["--kernel", kernel]); + if self.guest.vm_type == GuestVmType::Confidential { + let console_str = if let Some(c) = &self.guest.console_type { + c.as_str() + } else { + "hvc0" + }; + let igvm = direct_igvm_boot_path(Some(console_str)) + .expect("IGVM boot file not found for console type: {console_str}"); + self.command.args(["--igvm", igvm.to_str().unwrap()]); + self.command + .args(["--host-data", generate_host_data().as_str()]); + self.command.args(["--platform", "sev_snp=on"]); + } else if let Some(kernel) = &self.guest.kernel_path { + self.command.args(["--kernel", kernel.as_str()]); if let Some(cmdline) = &self.guest.kernel_cmdline { self.command.args(["--cmdline", cmdline]); } @@ -1893,3 +1909,27 @@ pub enum GuestVmType { Regular, Confidential, } + +// 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 + let console_str = console.unwrap_or("hvc0"); + + if console_str != "hvc0" && console_str != "ttyS0" { + panic!("IGVM console should be hvc0 or ttyS0, got: {console_str}"); + } + + let igvm_filepath = format!("/igvm_files/linux-{console_str}.bin"); + if Path::new(&igvm_filepath).exists() { + Some(PathBuf::from(igvm_filepath)) + } else { + None + } +} + +// Generate a random 64-character hex string for host data +fn generate_host_data() -> String { + let mut bytes = [0u8; 32]; + rand::rng().fill_bytes(&mut bytes); + bytes.iter().map(|b| format!("{b:02x}")).collect() +}