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() +}