diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 7e93d1494..fbffcd555 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -1589,6 +1589,149 @@ mod common_parallel { handle_child_output(r, &output); } + // Direct=on data disk on a 4k sector loop FS, aligned O_DIRECT round trip. + fn _test_virtio_block_direct_io_data_disk_4k(image_type: ImageType) { + let (qemu_fmt, qemu_extra): (&str, &[&str]) = match image_type { + ImageType::Raw => ("raw", &[]), + ImageType::Qcow2 => ("qcow2", &[]), + ImageType::FixedVhd => ("vpc", &["-o", "subformat=fixed"]), + ImageType::Vhdx => ("vhdx", &[]), + ImageType::Unknown => panic!("unsupported image_type {image_type}"), + }; + let image_type_str = image_type.to_string(); + + let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); + let guest = Guest::new(Box::new(disk_config)); + let kernel_path = direct_kernel_boot_path(); + + // The loop backing file needs a real O_DIRECT capable FS, not tmpfs. + let mut workloads_path = dirs::home_dir().unwrap(); + workloads_path.push("workloads"); + let img_dir = TempDir::new_in(workloads_path.as_path()).unwrap(); + let fs_img_path = img_dir.as_path().join("fs_4ksec.img"); + + assert!( + exec_host_command_output(&format!("truncate -s 1G {}", fs_img_path.to_str().unwrap())) + .status + .success(), + "truncate failed" + ); + + let loop_dev_path = create_loop_device(fs_img_path.to_str().unwrap(), 4096, 5); + + assert!( + exec_host_command_output(&format!("mkfs.ext4 -q {loop_dev_path}")) + .status + .success(), + "mkfs.ext4 failed" + ); + + let mnt_dir = img_dir.as_path().join("mnt"); + fs::create_dir_all(&mnt_dir).unwrap(); + assert!( + exec_host_command_output(&format!( + "mount {} {}", + loop_dev_path, + mnt_dir.to_str().unwrap() + )) + .status + .success(), + "mount failed" + ); + + let test_disk = mnt_dir.join(format!("data.{image_type_str}")); + let mut create_args: Vec<&str> = vec!["create", "-f", qemu_fmt]; + create_args.extend_from_slice(qemu_extra); + let res = run_qemu_img(&test_disk, &create_args, Some(&["64M"])); + assert!(res.status.success(), "qemu-img create failed: {res:?}"); + + let mut child = GuestCommand::new(&guest) + .args(["--cpus", "boot=4"]) + .args(["--memory", "size=512M"]) + .args(["--kernel", kernel_path.to_str().unwrap()]) + .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) + .args([ + "--disk", + format!( + "path={}", + guest.disk_config.disk(DiskType::OperatingSystem).unwrap() + ) + .as_str(), + format!( + "path={}", + guest.disk_config.disk(DiskType::CloudInit).unwrap() + ) + .as_str(), + format!( + "path={},direct=on,image_type={image_type_str}", + test_disk.to_str().unwrap() + ) + .as_str(), + ]) + .default_net() + .capture_output() + .spawn() + .unwrap(); + + // qcow2 and vhdx report 512 LBS from the on disk format. Raw and + // fixed VHD pass through the host 4096. + let expected_log_sec: u32 = match image_type { + ImageType::Qcow2 | ImageType::Vhdx => 512, + _ => 4096, + }; + + let r = std::panic::catch_unwind(|| { + guest.wait_vm_boot().unwrap(); + + let log_sec: u32 = guest + .ssh_command("lsblk -t | grep vdc | awk '{print $6}'") + .unwrap() + .trim() + .parse() + .unwrap_or_default(); + assert_eq!(log_sec, expected_log_sec, "unexpected logical sector size"); + + guest + .ssh_command( + "sudo dd if=/dev/urandom of=/tmp/pattern bs=4096 count=8 && \ + sudo dd if=/tmp/pattern of=/dev/vdc bs=4096 count=8 seek=1 \ + oflag=direct conv=fsync && \ + sudo dd if=/dev/vdc of=/tmp/readback bs=4096 count=8 skip=1 \ + iflag=direct && \ + cmp /tmp/pattern /tmp/readback", + ) + .expect("aligned 4k direct IO round trip failed"); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + let _ = exec_host_command_output(&format!("umount {}", mnt_dir.to_str().unwrap())); + let _ = exec_host_command_output(&format!("losetup -d {loop_dev_path}")); + + handle_child_output(r, &output); + } + + #[test] + fn test_virtio_block_direct_io_data_disk_4k_raw() { + _test_virtio_block_direct_io_data_disk_4k(ImageType::Raw); + } + + #[test] + fn test_virtio_block_direct_io_data_disk_4k_qcow2() { + _test_virtio_block_direct_io_data_disk_4k(ImageType::Qcow2); + } + + #[test] + fn test_virtio_block_direct_io_data_disk_4k_vhd() { + _test_virtio_block_direct_io_data_disk_4k(ImageType::FixedVhd); + } + + #[test] + fn test_virtio_block_direct_io_data_disk_4k_vhdx() { + _test_virtio_block_direct_io_data_disk_4k(ImageType::Vhdx); + } + #[test] fn test_virtio_block_qcow2_dirty_bit_unclean_shutdown() { let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME_QCOW2.to_string());