From 2c08663a3a6e322dcc4a389399f4e89fa33184a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Mass=C3=A9?= Date: Mon, 24 Nov 2025 08:56:55 -0500 Subject: [PATCH] e2e tests, unit tests + fixes --- src/lib/core.sh | 91 ++++- test/e2e/cloud-init/standard-user-data | 5 +- test/e2e/cloud-init/with-fs-user-data | 8 +- test/e2e/cloud-init/with-zvol-user-data | 9 +- test/e2e/zvirt.bats | 496 ++++++++++++++++++++++-- test/unit/core.bats | 276 ++++++++++++- 6 files changed, 793 insertions(+), 92 deletions(-) diff --git a/src/lib/core.sh b/src/lib/core.sh index 8835ba9..c4e4207 100644 --- a/src/lib/core.sh +++ b/src/lib/core.sh @@ -115,11 +115,6 @@ function parse_args () { should_exit=1 fi - if [ "$batch" -eq 1 ] && [ "$live" -ne 1 ]; then - echo "Error: Batch mode requires live snapshot mode." - should_exit=1 - fi - if [[ ! "$snapshot_name" =~ ^[a-zA-Z0-9._-]+$ ]]; then echo "Error: Snapshot name '$snapshot_name' contains invalid characters. Only alphanumeric characters, dots (.), underscores (_) and hyphens (-) are allowed." should_exit=1 @@ -199,6 +194,12 @@ function domain_checks () { state=$(domain_state "$domain") + # Store those values in cache for later use + domain_params_cache["$domain/state"]="${state}" + domain_params_cache["$domain/dataset"]="${zfs_dataset}" + domain_params_cache["$domain/mountpoint"]="${zfs_mountpoint}" + domain_params_cache["$domain/zvols"]="${zfs_zvols[*]}" + case "$action" in snapshot) # Check domain state @@ -223,7 +224,7 @@ function domain_checks () { done # Check if save file already exists for live snapshot - if [ -f "${zfs_mountpoint}/domain.save" ]; then + if [ "$live" -eq 1 ] && has_save_file "$domain"; then error "$domain: Save file '${zfs_mountpoint}/domain.save' already exists." ; error=1 fi ;; @@ -254,15 +255,10 @@ function domain_checks () { return 1 fi - # Store those values in cache for later use - domain_params_cache["$domain/state"]="${state}" - domain_params_cache["$domain/dataset"]="${zfs_dataset}" - domain_params_cache["$domain/mountpoint"]="${zfs_mountpoint}" - domain_params_cache["$domain/zvols"]="${zfs_zvols[*]}" - return 0 } +# Gets the mountpoint of the specified ZFS dataset. function get_zfs_dataset_mountpoint () { local zfs_dataset="$1" zfs get -H -o value mountpoint "${zfs_dataset}" @@ -393,6 +389,7 @@ function preflight_checks () { return $error } +# Removes the save file for the specified domain. function remove_save_file () { local domain="$1" zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" @@ -402,10 +399,61 @@ function remove_save_file () { fi } +# Checks if the save file exists for the specified domain. +function has_save_file () { + local domain="$1" + zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" + if [ -f "${zfs_mountpoint}/domain.save" ]; then + return 0 + else + return 1 + fi +} + +# Thaws the specified domain filesystem. +function fsthaw_domain () { + local domain="$1" + virsh domfsthaw "$domain" +} + +# Freezes the specified domain filesystem. +function fsfreeze_domain () { + local domain="$1" + virsh domfsfreeze "$domain" +} + +# Thaws all domains in the list. +function fsthaw_all_domains () { + local domains=( "$@" ) + + for domain in "${domains[@]}"; do + log_verbose "$domain: Thawing domain..." + state="${domain_params_cache["$domain/state"]}" + if [ "$state" == "running" ]; then + fsthaw_domain "$domain" + fi + done +} + +# Freezes all domains in the list. +function fsfreeze_all_domains () { + local domains=( "$@" ) + + for domain in "${domains[@]}"; do + log_verbose "$domain: Freezing domain..." + state="${domain_params_cache["$domain/state"]}" + if [ "$state" == "running" ]; then + fsfreeze_domain "$domain" + fi + done +} + # Takes snapshots for all specified domains. function take_snapshots () { - if [ "$batch" -eq 1 ]; then + if [ "$batch" -eq 1 ] && [ "$live" -eq 1 ]; then pause_all_domains "${domains[@]}" + elif [ "$batch" -eq 1 ] && [ "$live" -eq 0 ]; then + fsfreeze_all_domains "${domains[@]}" fi for domain in "${domains[@]}"; do @@ -417,12 +465,20 @@ function take_snapshots () { remove_save_file "$domain" fi else + if [ "$batch" -eq 0 ] && [ "$state" == "running" ]; then + fsfreeze_domain "$domain" + fi take_crash_consistent_snapshot "$domain" "$snapshot_name" + if [ "$batch" -eq 0 ] && [ "$state" == "running" ]; then + fsthaw_domain "$domain" + fi fi done - if [ "$batch" -eq 1 ]; then + if [ "$batch" -eq 1 ] && [ "$live" -eq 1 ]; then resume_all_domains "${domains[@]}" + elif [ "$batch" -eq 1 ] && [ "$live" -eq 0 ]; then + fsthaw_all_domains "${domains[@]}" fi return 0 @@ -432,7 +488,12 @@ function take_snapshots () { function revert_snapshots () { for domain in "${domains[@]}"; do revert_snapshot "$domain" "$snapshot_name" - restore_domain "$domain" + if has_save_file "$domain"; then + restore_domain "$domain" + if [ "$batch" -eq 1 ]; then + remove_save_file "$domain" + fi + fi done if [ "$batch" -eq 1 ]; then diff --git a/test/e2e/cloud-init/standard-user-data b/test/e2e/cloud-init/standard-user-data index 6f37128..70ce942 100644 --- a/test/e2e/cloud-init/standard-user-data +++ b/test/e2e/cloud-init/standard-user-data @@ -3,9 +3,8 @@ bootcmd: - setsebool -P virt_qemu_ga_run_unconfined on - setsebool -P virt_qemu_ga_read_nonsecurity_files on - -runcmd: -- install -o root -g root -m 0777 -d /test/rootfs +- setsebool -P virt_rw_qemu_ga_data on +- install -o root -g root -m 0777 --context=system_u:object_r:virt_qemu_ga_data_t:s0 -d /test/rootfs users: - name: e2e diff --git a/test/e2e/cloud-init/with-fs-user-data b/test/e2e/cloud-init/with-fs-user-data index 43cbabe..a7cbb6f 100644 --- a/test/e2e/cloud-init/with-fs-user-data +++ b/test/e2e/cloud-init/with-fs-user-data @@ -1,15 +1,13 @@ #cloud-config mounts: -- [ data, /test/virtiofs, virtiofs, "defaults,nofail", "0", "0" ] +- [ data, /test/virtiofs, virtiofs, "defaults,context=system_u:object_r:virt_qemu_ga_data_t:s0", "0", "0" ] bootcmd: - setsebool -P virt_qemu_ga_run_unconfined on - setsebool -P virt_qemu_ga_read_nonsecurity_files on - -runcmd: -- install -o root -g root -m 0777 -d /test/virtiofs -- mount -a +- setsebool -P virt_rw_qemu_ga_data on +- install -o root -g root -d /test/virtiofs users: - name: e2e diff --git a/test/e2e/cloud-init/with-zvol-user-data b/test/e2e/cloud-init/with-zvol-user-data index a805a7f..1b0217e 100644 --- a/test/e2e/cloud-init/with-zvol-user-data +++ b/test/e2e/cloud-init/with-zvol-user-data @@ -13,16 +13,13 @@ fs_setup: partition: auto mounts: - - [ LABEL=zvol, /test/zvol, xfs, "defaults,nofail", "0", "2" ] + - [ LABEL=zvol, /test/zvol, xfs, "defaults,context=system_u:object_r:virt_qemu_ga_data_t:s0", "0", "2" ] bootcmd: - setsebool -P virt_qemu_ga_run_unconfined on - setsebool -P virt_qemu_ga_read_nonsecurity_files on - -runcmd: -- install -o root -g root -m 0777 -d /test/zvol -- chmod 0777 /test/zvol -- mount -a +- setsebool -P virt_rw_qemu_ga_data on +- mkdir -p /test/zvol users: - name: e2e diff --git a/test/e2e/zvirt.bats b/test/e2e/zvirt.bats index 3d40318..c7840f4 100644 --- a/test/e2e/zvirt.bats +++ b/test/e2e/zvirt.bats @@ -11,6 +11,13 @@ setup() { "${BATS_TEST_DIRNAME}/../../src/zvirt" "$@" } + declare -g e2e_test_enable_debug=0 + e2e_test_debug_log(){ + if [ "$e2e_test_enable_debug" -eq 1 ]; then + echo "$@" >&3 + fi + } + qemu_exec() { domain="$1" shift || true @@ -23,17 +30,17 @@ setup() { done local command="{\"execute\": \"guest-exec\", \"arguments\": {\"path\": \"$1\", \"arg\": [ $json_args ], \"capture-output\": true }}" output="$(virsh qemu-agent-command "$domain" "$command")" - #echo "qemu_exec: command output: $output" >&3 + #e2e_test_debug_log "qemu_exec: command output: $output" pid="$(echo "$output" | jq -r '.return.pid')" if [ -z "$pid" ] || [ "$pid" == "null" ]; then - echo "qemu_exec: failed to get pid from command output" >&3 + e2e_test_debug_log "qemu_exec: failed to get pid from command output" return 1 fi sleep .25 while true; do local status_command="{\"execute\": \"guest-exec-status\", \"arguments\": {\"pid\": $pid}}" status_output="$(virsh qemu-agent-command "$domain" "$status_command")" - #echo "qemu_exec: status output: $status_output" >&3 + #e2e_test_debug_log "qemu_exec: status output: $status_output" exited="$(echo "$status_output" | jq -r '.return.exited')" if [ "$exited" == "true" ]; then stdout_base64="$(echo "$status_output" | jq -r '.return["out-data"]')" @@ -79,20 +86,31 @@ EOF } cleanup() { - echo "teardown: Cleaning up created domains and images..." >&3 + e2e_test_debug_log "teardown: Cleaning up created domains and images..." for domain in standard with-fs with-zvol; do + state="$(virsh domstate "$domain" 2>/dev/null || true)" + if [[ -n "$state" && "$state" != "shut off" ]]; then + virsh destroy "$domain" + fi if virsh dominfo "$domain" &>/dev/null; then - virsh destroy "$domain" || true - virsh undefine "$domain" --nvram || true + virsh undefine "$domain" --nvram fi - zfs destroy -r data/domains/"$domain" || true + done + sleep 1 + sync + sleep 1 + for domain in standard with-fs with-zvol; do + if zfs list data/domains/"$domain" &>/dev/null; then + zfs destroy -rR data/domains/"$domain" + fi + sleep .2 rm -rf "/var/lib/libvirt/images/${domain}" done } create_domains() { # Create the standard VM - echo "setup: Creating the standard VM..." >&3 + e2e_test_debug_log "setup: Creating the standard VM..." mkdir -p /var/lib/libvirt/images/standard zfs create -p data/domains/standard -o mountpoint=/var/lib/libvirt/images/standard convert_cloud_image "$fedora_img" "/var/lib/libvirt/images/standard/root.img" @@ -113,7 +131,7 @@ EOF --boot=uefi # Create the with-fs VM - echo "setup: Creating the with-fs VM..." >&3 + e2e_test_debug_log "setup: Creating the with-fs VM..." mkdir -p /var/lib/libvirt/images/with-fs /srv/with-fs chmod 0777 /srv/with-fs zfs create -p data/domains/with-fs -o mountpoint=/var/lib/libvirt/images/with-fs @@ -138,7 +156,7 @@ EOF --filesystem=type=mount,accessmode=passthrough,driver.type=virtiofs,driver.queue=1024,source.dir=/srv/with-fs,target.dir=data # Create the with-zvol VM - echo "setup: Creating the with-zvol VM..." >&3 + e2e_test_debug_log "setup: Creating the with-zvol VM..." mkdir -p /var/lib/libvirt/images/with-zvol zfs create -p data/domains/with-zvol -o mountpoint=/var/lib/libvirt/images/with-zvol zfs create -V 10G data/domains/with-zvol/data @@ -162,31 +180,39 @@ EOF } readiness_wait() { - echo "setup: Waiting for VMs to become ready..." >&3 + e2e_test_debug_log "setup: Waiting for VMs to become ready..." for domain in standard with-fs with-zvol; do - echo "setup: Waiting for qemu guest agent to be running in domain '$domain'..." >&3 + e2e_test_debug_log "setup: Waiting for qemu guest agent to be running in domain '$domain'..." until virsh qemu-agent-command "$domain" '{"execute":"guest-ping"}' &>/dev/null; do sleep 2 done done - echo "setup: all VMs started successfully" >&3 + e2e_test_debug_log "setup: all VMs started successfully" for domain in standard with-fs with-zvol; do - echo "setup: Waiting for cloud-init to complete in domain '$domain'..." >&3 + e2e_test_debug_log "setup: Waiting for cloud-init to complete in domain '$domain'..." until qemu_exec "$domain" test -f /var/lib/cloud/instance/boot-finished; do sleep 2 done done - echo "setup: VMs are ready" >&3 + if ! qemu_exec with-fs grep -q /test/virtiofs /proc/mounts; then + e2e_test_debug_log "setup: virtiofs not mounted in 'with-fs' domain" + return 1 + fi + if ! qemu_exec with-zvol grep -q /test/zvol /proc/mounts; then + e2e_test_debug_log "setup: zvol not mounted in 'with-zvol' domain" + return 1 + fi + e2e_test_debug_log "setup: VMs are ready" } local fedora_url="https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2" local fedora_img="/var/lib/libvirt/images/$(basename "$fedora_url")" if [ ! -f "$fedora_img" ]; then - echo "setup: downloading Fedora Cloud image to $fedora_img" >&3 + e2e_test_debug_log "setup: downloading Fedora Cloud image to $fedora_img" mkdir -p /var/lib/libvirt/images/library curl -sSfL -o "$fedora_img" "$fedora_url" fi - echo "setup: Fedora Cloud image is at $fedora_img" >&3 + e2e_test_debug_log "setup: Fedora Cloud image is at $fedora_img" # Cleanup any leftover artifacts from previous runs cleanup @@ -199,15 +225,18 @@ teardown() { } @test "zvirt: setup selftest" { - echo "setup: provisioning completed" >&3 + e2e_test_debug_log "setup: provisioning completed" } @test "zvirt: take live snapshot in batch mode" { # Create witness files in all three domains before taking snapshots - qemu_exec standard touch /test/rootfs/before-backup1 - qemu_exec with-fs touch /test/rootfs/before-backup1 - qemu_exec with-zvol touch /test/zvol/before-backup1 - [[ -f /srv/with-fs/before-backup1 ]] + qemu_exec standard touch /test/rootfs/witness-file + qemu_exec with-fs touch /test/virtiofs/witness-file + qemu_exec with-zvol touch /test/zvol/witness-file + + # Verify that the witness files exist in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_success # Take live snapshots for all three domains run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 -l @@ -227,13 +256,13 @@ teardown() { # Assert that the files created before the snapshot exist run qemu_exec standard ls -1 /test/rootfs assert_success - assert_output "before-backup1" - run qemu_exec with-fs ls -1 /test/rootfs + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs assert_success - assert_output "before-backup1" + assert_output "witness-file" run qemu_exec with-zvol ls -1 /test/zvol assert_success - assert_output "before-backup1" + assert_output "witness-file" # List snapshots and verify their existence run zvirt list -d standard -d with-zvol -d with-fs @@ -253,25 +282,408 @@ Snapshots for domain 'with-fs': assert_output --partial "with-zvol:" assert_output --partial "with-fs:" assert_output --partial "Pre-flight checks failed." + + # Delete the witness files + run qemu_exec standard rm /test/rootfs/witness-file + assert_success + run qemu_exec with-fs rm /test/virtiofs/witness-file + assert_success + run qemu_exec with-zvol rm /test/zvol/witness-file + assert_success + + # Sync all filesystems + run qemu_exec standard sync + assert_success + run qemu_exec with-fs sync + assert_success + run qemu_exec with-zvol sync + assert_success + + # Verify that the witness files have been deleted in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_failure + + # Stop all domains + run virsh destroy standard + assert_success + run virsh destroy with-fs + assert_success + run virsh destroy with-zvol + assert_success + + # Revert snapshots in batch mode + run zvirt revert -b -d standard -d with-zvol -d with-fs -s backup1 + assert_success + + # Check all domains are running again + run virsh domstate standard + assert_success + assert_output "running" + run virsh domstate with-fs + assert_success + assert_output "running" + run virsh domstate with-zvol + assert_success + assert_output "running" + + # Verify that the witness files still exist after revert + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" } +@test "zvirt: take live snapshot without batch mode" { + # Create witness files in all three domains before taking snapshots + qemu_exec standard touch /test/rootfs/witness-file + qemu_exec with-fs touch /test/virtiofs/witness-file + qemu_exec with-zvol touch /test/zvol/witness-file + + # Verify that the witness files exist in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_success -# @test "call_parse_args: take a crash-consistent snapshot for two domains" { -# run zvirt snapshot -d standard -d with-zvol -d with-fs backup2 -# assert_success -# } + # Take live snapshots for all three domains + run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 -l + assert_success -# @test "call_parse_args: revert snapshot for a domain" { -# virsh destroy standard || true -# run zvirt revert -d standard -s backup2 -# assert_success -# } + # Verify that the domains are still running + run virsh domstate standard + assert_success + assert_output "running" + run virsh domstate with-fs + assert_success + assert_output "running" + run virsh domstate with-zvol + assert_success + assert_output "running" -# @test "call_parse_args: revert snapshot for all domains in batch mode" { -# virsh destroy standard || true -# virsh destroy with-zvol || true -# virsh destroy with-fs || true -# run zvirt revert -b -d standard -d with-zvol -d with-fs -s backup1 -# assert_success -# } + # Assert that the files created before the snapshot exist + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" + # List snapshots and verify their existence + run zvirt list -d standard -d with-zvol -d with-fs + assert_success + assert_output "Snapshots for domain 'standard': + - backup1 +Snapshots for domain 'with-zvol': + - backup1 +Snapshots for domain 'with-fs': + - backup1" + + # Attempt to take the same snapshot again and expect failure + run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 -l + assert_failure + assert_output --partial "Snapshot 'backup1' already exists." + assert_output --partial "standard:" + assert_output --partial "with-zvol:" + assert_output --partial "with-fs:" + assert_output --partial "Pre-flight checks failed." + + # Delete the witness files + run qemu_exec standard rm /test/rootfs/witness-file + assert_success + run qemu_exec with-fs rm /test/virtiofs/witness-file + assert_success + run qemu_exec with-zvol rm /test/zvol/witness-file + assert_success + + # Sync all filesystems + run qemu_exec standard sync + assert_success + run qemu_exec with-fs sync + assert_success + run qemu_exec with-zvol sync + assert_success + + # Verify that the witness files have been deleted in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_failure + + # Stop all domains + run virsh destroy standard + assert_success + run virsh destroy with-fs + assert_success + run virsh destroy with-zvol + assert_success + + # Revert snapshots in batch mode + run zvirt revert -d standard -d with-zvol -d with-fs -s backup1 + assert_success + + # Check all domains are running again + run virsh domstate standard + assert_success + assert_output "running" + run virsh domstate with-fs + assert_success + assert_output "running" + run virsh domstate with-zvol + assert_success + assert_output "running" + + # Verify that the witness files still exist after revert + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" +} + +@test "zvirt: take crash-consistent snapshot without batch mode" { + # Create witness files in all three domains before taking snapshots + qemu_exec standard touch /test/rootfs/witness-file + qemu_exec with-fs touch /test/virtiofs/witness-file + qemu_exec with-zvol touch /test/zvol/witness-file + + # Verify that the witness files exist in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_success + + # Take crash-consistent snapshots for all three domains + run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 + assert_success + + # Verify that the domains are still running + run virsh domstate standard + assert_success + assert_output "running" + run virsh domstate with-fs + assert_success + assert_output "running" + run virsh domstate with-zvol + assert_success + assert_output "running" + + # Assert that the files created before the snapshot exist + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" + + # List snapshots and verify their existence + run zvirt list -d standard -d with-zvol -d with-fs + assert_success + assert_output "Snapshots for domain 'standard': + - backup1 +Snapshots for domain 'with-zvol': + - backup1 +Snapshots for domain 'with-fs': + - backup1" + + # Attempt to take the same snapshot again and expect failure + run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 + assert_failure + assert_output --partial "Snapshot 'backup1' already exists." + assert_output --partial "standard:" + assert_output --partial "with-zvol:" + assert_output --partial "with-fs:" + assert_output --partial "Pre-flight checks failed." + + # Delete the witness files + run qemu_exec standard rm /test/rootfs/witness-file + assert_success + run qemu_exec with-fs rm /test/virtiofs/witness-file + assert_success + run qemu_exec with-zvol rm /test/zvol/witness-file + assert_success + + # Sync all filesystems + run qemu_exec standard sync + assert_success + run qemu_exec with-fs sync + assert_success + run qemu_exec with-zvol sync + assert_success + + # Wait a moment to ensure all writes are flushed + sleep 2 + + # Verify that the witness files have been deleted in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_failure + + # Stop all domains + run virsh destroy standard + assert_success + run virsh destroy with-fs + assert_success + run virsh destroy with-zvol + assert_success + + # Revert snapshots in batch mode + run zvirt revert -d standard -d with-zvol -d with-fs -s backup1 + assert_success + + # Check all domains have been shut off + run virsh domstate standard + assert_success + assert_output "shut off" + run virsh domstate with-fs + assert_success + assert_output "shut off" + run virsh domstate with-zvol + assert_success + assert_output "shut off" + + # Start all domains + run virsh start standard + assert_success + run virsh start with-fs + assert_success + run virsh start with-zvol + assert_success + + # Wait for all domains to be fully ready + readiness_wait + + # Verify that the witness files still exist after revert + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" +} + +@test "zvirt: take crash-consistent snapshot with batch mode" { + # Create witness files in all three domains before taking snapshots + qemu_exec standard touch /test/rootfs/witness-file + qemu_exec with-fs touch /test/virtiofs/witness-file + qemu_exec with-zvol touch /test/zvol/witness-file + + # Verify that the witness files exist in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_success + + # Take crash-consistent snapshots for all three domains + run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 + assert_success + + # Verify that the domains are still running + run virsh domstate standard + assert_success + assert_output "running" + run virsh domstate with-fs + assert_success + assert_output "running" + run virsh domstate with-zvol + assert_success + assert_output "running" + + # Assert that the files created before the snapshot exist + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" + + # List snapshots and verify their existence + run zvirt list -d standard -d with-zvol -d with-fs + assert_success + assert_output "Snapshots for domain 'standard': + - backup1 +Snapshots for domain 'with-zvol': + - backup1 +Snapshots for domain 'with-fs': + - backup1" + + # Attempt to take the same snapshot again and expect failure + run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 + assert_failure + assert_output --partial "Snapshot 'backup1' already exists." + assert_output --partial "standard:" + assert_output --partial "with-zvol:" + assert_output --partial "with-fs:" + assert_output --partial "Pre-flight checks failed." + + # Delete the witness files + run qemu_exec standard rm /test/rootfs/witness-file + assert_success + run qemu_exec with-fs rm /test/virtiofs/witness-file + assert_success + run qemu_exec with-zvol rm /test/zvol/witness-file + assert_success + + # Sync all filesystems + run qemu_exec standard sync + assert_success + run qemu_exec with-fs sync + assert_success + run qemu_exec with-zvol sync + assert_success + + # Wait a moment to ensure all writes are flushed + sleep 2 + + # Verify that the witness files have been deleted in the virtiofs host mount + run test -f /srv/with-fs/witness-file + assert_failure + + # Stop all domains + run virsh destroy standard + assert_success + run virsh destroy with-fs + assert_success + run virsh destroy with-zvol + assert_success + + # Revert snapshots in batch mode + run zvirt revert -b -d standard -d with-zvol -d with-fs -s backup1 + assert_success + + # Check all domains are running again + run virsh domstate standard + assert_success + assert_output "running" + run virsh domstate with-fs + assert_success + assert_output "running" + run virsh domstate with-zvol + assert_success + assert_output "running" + + # Wait for all domains to be fully ready + readiness_wait + + # Verify that the witness files still exist after revert + run qemu_exec standard ls -1 /test/rootfs + assert_success + assert_output "witness-file" + run qemu_exec with-fs ls -1 /test/virtiofs + assert_success + assert_output "witness-file" + run qemu_exec with-zvol ls -1 /test/zvol + assert_success + assert_output "witness-file" +} diff --git a/test/unit/core.bats b/test/unit/core.bats index 0f313b9..2b9bbe3 100644 --- a/test/unit/core.bats +++ b/test/unit/core.bats @@ -165,6 +165,23 @@ snapshot2" assert_failure } +@test "has_save_file: nominal case" { + # Temporary directory for save files + local temp_dir="$(mktemp -d)" + mkdir -p "$temp_dir/foo" "$temp_dir/bar" + # Only foo has a save file + touch "$temp_dir/foo/domain.save" + + # Fill up the cache + declare -A domain_params_cache=( ["foo/mountpoint"]="$temp_dir/foo" ["bar/mountpoint"]="$temp_dir/bar" ) + + # Run the test + run in_bash has_save_file foo + assert_success + run in_bash has_save_file bar + assert_failure +} + @test "take_live_snapshot: nominal case" { # Mock the underlying tools declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) @@ -330,6 +347,78 @@ data/domains/baz/virtiofs" [[ "$(mock_get_call_num ${virsh_mock})" -eq 2 ]] } +@test "fsthaw_all_domains: nominal case" { + # Mock the underlying tools + local domains=( "foo" "bar" ) + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + fsthaw_mock="$(mock_create)" + fsthaw_domain() { + if [[ "$*" == "foo" ]]; then + $fsthaw_mock "$@" + return $? + fi + return 1 + } + export -f fsthaw_domain + export fsthaw_mock + + # Run the test + run in_bash fsthaw_all_domains "${domains[@]}" + assert_success + [[ "$(mock_get_call_num ${fsthaw_mock})" -eq 1 ]] +} + +@test "fsfreeze_all_domains: nominal case" { + # Mock the underlying tools + local domains=( "foo" "bar" ) + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + fsfreeze_mock="$(mock_create)" + fsfreeze_domain() { + if [[ "$*" == "foo" ]]; then + $fsfreeze_mock "$@" + return $? + fi + return 1 + } + export -f fsfreeze_domain + export fsfreeze_mock + + # Run the test + run in_bash fsfreeze_all_domains "${domains[@]}" + assert_success + [[ "$(mock_get_call_num ${fsfreeze_mock})" -eq 1 ]] +} + +@test "fsthaw_domain: nominal case" { + # Mock the underlying tools + virsh() { + [[ "$*" == "domfsthaw foo" ]] && return 0 + return 1 + } + export -f virsh + + # Run the test + run in_bash virsh domfsthaw "foo" + assert_success + run in_bash virsh domfsthaw "bar" + assert_failure +} + +@test "fsfreeze_domain: nominal case" { + # Mock the underlying tools + virsh() { + [[ "$*" == "domfsfreeze foo" ]] && return 0 + return 1 + } + export -f virsh + + # Run the test + run in_bash virsh domfsfreeze "foo" + assert_success + run in_bash virsh domfsfreeze "bar" + assert_failure +} + @test "domain_checks: nominal case" { # Mock the underlying tools domain_exists() { @@ -393,6 +482,24 @@ data/domains/baz/virtiofs" assert_success run in_bash domain_checks revert bar backup1 assert_success + + # Live mode with existing save file + has_save_file() { + return 0 + } + export -f has_save_file + live=1 + run in_bash domain_checks snapshot foo backup2 + assert_failure + + # Live mode with non-existing save file + has_save_file() { + return 1 + } + export -f has_save_file + live=1 + run in_bash domain_checks snapshot foo backup2 + assert_success } @test "list_snapshots: nominal case" { @@ -451,7 +558,21 @@ snapshot2" restore_domain() { return 1; } resume_all_domains() { return 1; } remove_save_file() { return 1; } - export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file + fsfreeze_all_domains() { return 1; } + fsthaw_all_domains() { return 1; } + fsfreeze_domain() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + fsthaw_domain() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) @@ -478,29 +599,26 @@ snapshot2" fi return 1 } - pause_all_domains() { - if [[ "$*" == "foo bar" ]]; then - return 0 - fi - return 1 - } + pause_all_domains() { return 1; } take_live_snapshot() { return 1; } restore_domain() { return 1; } - resume_all_domains() { + resume_all_domains() { return 1; } + remove_save_file() { return 1; } + fsfreeze_all_domains() { if [[ "$*" == "foo bar" ]]; then return 0 fi return 1 - } - remove_save_file() { - regex="^(foo|bar) backup$" - if [[ "$*" =~ $regex ]]; then + fsthaw_all_domains() { + if [[ "$*" == "foo bar" ]]; then return 0 fi return 1 } - export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file + fsfreeze_domain() { return 1; } + fsthaw_domain() { return 1; } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) @@ -541,7 +659,86 @@ snapshot2" } resume_all_domains() { return 1; } remove_save_file() { return 1; } - export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file + fsfreeze_all_domains() { return 1; } + fsthaw_all_domains() { return 1; } + fsfreeze_domain() { + if [[ "$*" == "bar" ]]; then + return 0 + fi + return 1 + } + fsthaw_domain() { + if [[ "$*" == "bar" ]]; then + return 0 + fi + return 1 + } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain + + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + + # Run the test + domains=( "foo" "bar" ) + snapshot_name="backup" + batch=0 + live=1 + run in_bash take_snapshots + assert_success + + # Add a non-existing domain to the list + domains+=( "baz" ) + run in_bash take_snapshots + assert_failure +} + +@test "take_snapshots: batch=1, live=1" { + # Mock the underlying tools + take_crash_consistent_snapshot() { + if [[ "$*" == "bar backup" ]]; then + return 0 + fi + return 1 + } + pause_all_domains() { + if [[ "$*" == "foo bar" ]]; then + return 0 + fi + return 1 + } + take_live_snapshot() { + if [[ "$*" == "foo backup" ]]; then + return 0 + fi + return 1 + } + restore_domain() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + resume_all_domains() { + if [[ "$*" == "foo bar" ]]; then + return 0 + fi + return 1 + } + remove_save_file() { return 1; } + fsfreeze_all_domains() { return 1; } + fsthaw_all_domains() { return 1; } + fsfreeze_domain() { + if [[ "$*" == "bar" ]]; then + return 0 + fi + return 1 + } + fsthaw_domain() { + if [[ "$*" == "bar" ]]; then + return 0 + fi + return 1 + } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) @@ -569,15 +766,31 @@ snapshot2" return 1 } restore_domain() { - regex="^(foo|bar)$" - if [[ "$*" =~ $regex ]]; then + if [[ "$*" == "foo" ]]; then return 0 fi return 1 } resume_all_domains() { return 1; } - export -f revert_snapshot restore_domain resume_all_domains - + has_save_file() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + remove_save_file() { return 1; } + domain_state() { + if [[ "$*" == "foo" ]]; then + echo "paused" + return 0 + elif [[ "$*" == "bar" ]]; then + echo "shut off" + return 0 + fi + return 1 + } + export -f revert_snapshot restore_domain resume_all_domains has_save_file remove_save_file domain_state + # Run the test domains=( "foo" "bar" ) snapshot_name="backup" @@ -601,8 +814,7 @@ snapshot2" return 1 } restore_domain() { - regex="^(foo|bar)$" - if [[ "$*" =~ $regex ]]; then + if [[ "$*" == "foo" ]]; then return 0 fi return 1 @@ -613,7 +825,29 @@ snapshot2" fi return 1 } - export -f revert_snapshot restore_domain resume_all_domains + has_save_file() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + remove_save_file() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + domain_state() { + if [[ "$*" == "foo" ]]; then + echo "paused" + return 0 + elif [[ "$*" == "bar" ]]; then + echo "shut off" + return 0 + fi + return 1 + } + export -f revert_snapshot restore_domain resume_all_domains has_save_file remove_save_file domain_state # Run the test domains=( "foo" "bar" )