From 3b43551d986b1140bb68e6ffcc202184dabe5069 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Tue, 12 Jan 2021 11:03:28 +0100 Subject: [PATCH] ci: Use device mapper to avoid copying Windows image The Windows image is quite large (about 20GiB), hence it takes some time to copy it for every test in order to avoid potential corruption. One way to mitigate that without compromising on safety between each test is by using device mapper. By creating a read-only base, we ensure the image won't be modified by any of the tests, and by creating one snapshot for each test, we avoid copying the entire image each time. A dedicated Copy On Write disk image is created to handle any change that might be performed on the base image, letting the tests behave as expected. Fixes #2155 Signed-off-by: Sebastien Boeuf --- scripts/run_integration_tests_windows.sh | 11 +++ tests/integration.rs | 116 +++++++++++++++++++++-- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/scripts/run_integration_tests_windows.sh b/scripts/run_integration_tests_windows.sh index 82abd9afb..3fbedf510 100755 --- a/scripts/run_integration_tests_windows.sh +++ b/scripts/run_integration_tests_windows.sh @@ -17,6 +17,14 @@ TARGET_CC="musl-gcc" CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/" fi +# Use device mapper to create a snapshot of the Windows image +img_blk_size=$(du -b -B 512 /root/workloads/windows-server-2019.raw | awk '{print $1;}') +loop_device=$(losetup --find --show --read-only /root/workloads/windows-server-2019.raw) +dmsetup create windows-base --table "0 $img_blk_size linear $loop_device 0" +dmsetup mknodes +dmsetup create windows-snapshot-base --table "0 $img_blk_size snapshot-origin /dev/mapper/windows-base" +dmsetup mknodes + cargo build --all --release $features_build --target $BUILD_TARGET strip target/$BUILD_TARGET/release/cloud-hypervisor @@ -27,4 +35,7 @@ export RUST_BACKTRACE=1 time cargo test $features_test "tests::windows::" -- --test-threads=1 RES=$? +dmsetup remove_all -f +losetup -D + exit $RES diff --git a/tests/integration.rs b/tests/integration.rs index 7a95a0a85..461d20cb0 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -117,6 +117,9 @@ mod tests { struct WindowsDiskConfig { image_name: String, osdisk_path: String, + loopback_device: String, + windows_snapshot_cow: String, + windows_snapshot: String, } impl WindowsDiskConfig { @@ -124,10 +127,37 @@ mod tests { WindowsDiskConfig { image_name, osdisk_path: String::new(), + loopback_device: String::new(), + windows_snapshot_cow: String::new(), + windows_snapshot: String::new(), } } } + impl Drop for WindowsDiskConfig { + fn drop(&mut self) { + // dmsetup remove windows-snapshot-1 + std::process::Command::new("dmsetup") + .arg("remove") + .arg(self.windows_snapshot.as_str()) + .output() + .expect("Expect removing Windows snapshot with 'dmsetup' to succeed"); + + // dmsetup remove windows-snapshot-cow-1 + std::process::Command::new("dmsetup") + .arg("remove") + .arg(self.windows_snapshot_cow.as_str()) + .output() + .expect("Expect removing Windows snapshot CoW with 'dmsetup' to succeed"); + + // losetup -d + std::process::Command::new("losetup") + .args(&["-d", self.loopback_device.as_str()]) + .output() + .expect("Expect removing loopback device to succeed"); + } + } + fn handle_child_output( r: Result<(), std::boxed::Box>, output: &std::process::Output, @@ -336,15 +366,89 @@ mod tests { let mut workload_path = dirs::home_dir().unwrap(); workload_path.push("workloads"); - let mut osdisk_base_path = workload_path; - osdisk_base_path.push(&self.image_name); + let mut osdisk_path = workload_path; + osdisk_path.push(&self.image_name); - let osdisk_path = String::from(tmp_dir.path().join("osdisk.img").to_str().unwrap()); + let osdisk_blk_size = fs::metadata(osdisk_path) + .expect("Expect retrieving Windows image metadata") + .len() + >> 9; - rate_limited_copy(osdisk_base_path, &osdisk_path) - .expect("copying of OS source disk image failed"); + let snapshot_cow_path = + String::from(tmp_dir.path().join("snapshot_cow").to_str().unwrap()); - self.osdisk_path = osdisk_path; + // Create and truncate CoW file for device mapper + let cow_file_size: u64 = 1 << 30; + let cow_file_blk_size = cow_file_size >> 9; + let cow_file = std::fs::File::create(snapshot_cow_path.as_str()) + .expect("Expect creating CoW image to succeed"); + cow_file + .set_len(cow_file_size) + .expect("Expect truncating CoW image to succeed"); + + // losetup --find --show /tmp/snapshot_cow + let loopback_device = std::process::Command::new("losetup") + .arg("--find") + .arg("--show") + .arg(snapshot_cow_path.as_str()) + .output() + .expect("Expect creating loopback device from snapshot CoW image to succeed"); + + self.loopback_device = String::from_utf8_lossy(&loopback_device.stdout) + .trim() + .to_string(); + + let random_extension = tmp_dir.path().file_name().unwrap(); + let windows_snapshot_cow = format!( + "windows-snapshot-cow-{}", + random_extension.to_str().unwrap() + ); + + // dmsetup create windows-snapshot-cow-1 --table '0 2097152 linear /dev/loop1 0' + std::process::Command::new("dmsetup") + .arg("create") + .arg(windows_snapshot_cow.as_str()) + .args(&[ + "--table", + format!("0 {} linear {} 0", cow_file_blk_size, self.loopback_device).as_str(), + ]) + .output() + .expect("Expect creating Windows snapshot CoW with 'dmsetup' to succeed"); + + let windows_snapshot = + format!("windows-snapshot-{}", random_extension.to_str().unwrap()); + + // dmsetup mknodes + std::process::Command::new("dmsetup") + .arg("mknodes") + .output() + .expect("Expect device mapper nodes to be ready"); + + // dmsetup create windows-snapshot-1 --table '0 41943040 snapshot /dev/mapper/windows-base /dev/mapper/windows-snapshot-cow-1 P 8' + std::process::Command::new("dmsetup") + .arg("create") + .arg(windows_snapshot.as_str()) + .args(&[ + "--table", + format!( + "0 {} snapshot /dev/mapper/windows-base /dev/mapper/{} P 8", + osdisk_blk_size, + windows_snapshot_cow.as_str() + ) + .as_str(), + ]) + .output() + .expect("Expect creating Windows snapshot with 'dmsetup' to succeed"); + + // dmsetup mknodes + std::process::Command::new("dmsetup") + .arg("mknodes") + .output() + .expect("Expect device mapper nodes to be ready"); + + self.osdisk_path = format!("/dev/mapper/{}", windows_snapshot); + self.windows_snapshot_cow = windows_snapshot_cow.clone(); + self.windows_snapshot = windows_snapshot.clone(); } fn disk(&self, disk_type: DiskType) -> Option {