diff --git a/lib/vdi.py b/lib/vdi.py index 0ad83a580..500def39f 100644 --- a/lib/vdi.py +++ b/lib/vdi.py @@ -124,3 +124,17 @@ def wait_for_coalesce(self, fn: Callable[[], R] | None = None) -> R | None: wait_for(lambda: self.get_parent() != previous_parent, msg="Waiting for coalesce", timeout_secs=10 * 60) logging.info("Coalesce done") return ret + + def enable_cbt(self) -> None: + logging.info(f"Enabling CBT on VDI {self.uuid}") + self.sr.pool.master.xe('vdi-enable-cbt', {'uuid': self.uuid}) + + def disable_cbt(self) -> None: + logging.info(f"Disabling CBT on VDI {self.uuid}") + self.sr.pool.master.xe('vdi-disable-cbt', {'uuid': self.uuid}) + + def get_cbt_enabled(self) -> str: + return self.param_get('cbt-enabled') + + def is_cbt_enabled(self) -> bool: + return self.get_cbt_enabled() == 'true' diff --git a/tests/storage/cephfs/test_cephfs_sr.py b/tests/storage/cephfs/test_cephfs_sr.py index 45e82c484..cec55d951 100644 --- a/tests/storage/cephfs/test_cephfs_sr.py +++ b/tests/storage/cephfs/test_cephfs_sr.py @@ -4,13 +4,18 @@ import time from lib.commands import SSHCommandFailed -from lib.common import vm_image, wait_for +from lib.common import Defer, vm_image, wait_for from lib.host import Host from lib.pool import Pool from lib.sr import SR from lib.vdi import VDI from lib.vm import VM from tests.storage import vdi_is_open +from tests.storage.storage import ( + check_critical_journal_revert, + check_vdi_revert, + check_vdi_revert_journal, +) # Requirements: # - one XCP-ng host >= 8.2 @@ -45,6 +50,31 @@ def test_snapshot(self, vm_on_cephfs_sr: VM) -> None: finally: vm.shutdown(verify=True) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_cephfs_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_cephfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_cephfs_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_cephfs_sr, fistpoint, vm_on_cephfs_sr.host.pool.master) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_critical_journal_revert( + self, vm_on_cephfs_sr: VM, defer: Defer, exit_on_fistpoint: None, hostA2: Host + ) -> None: + check_critical_journal_revert(defer, vm_on_cephfs_sr, hostA2, "FileSR_revert_create_src") + # *** tests with reboots (longer tests). @pytest.mark.reboot diff --git a/tests/storage/ext/test_ext_sr.py b/tests/storage/ext/test_ext_sr.py index 899221191..9d6f9c8fe 100644 --- a/tests/storage/ext/test_ext_sr.py +++ b/tests/storage/ext/test_ext_sr.py @@ -23,6 +23,12 @@ vdi_is_open, xva_export_import, ) +from tests.storage.storage import ( + check_vdi_revert, + check_vdi_revert_cbt, + check_vdi_revert_journal, + check_vdi_revert_journal_cbt, +) # Requirements: # - one XCP-ng host with an additional unused disk for the SR @@ -62,6 +68,34 @@ def test_snapshot(self, vm_on_ext_sr: VM) -> None: finally: vm.shutdown(verify=True) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_ext_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_ext_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_cbt(self, vm_on_ext_sr: VM, defer: Defer) -> None: + check_vdi_revert_cbt(defer, vm_on_ext_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_journal_cbt(self, vm_on_ext_sr: VM, defer: Defer, exit_on_fistpoint: None): + check_vdi_revert_journal_cbt(defer, vm_on_ext_sr, "FileSR_revert_create_src") + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_ext_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_ext_sr, fistpoint) + @pytest.mark.small_vm @pytest.mark.parametrize("vdi_op", ["snapshot", "clone"]) def test_coalesce(self, storage_test_vm: VM, vdi_on_ext_sr: VDI, vdi_op: CoalesceOperation, defer: Defer) -> None: diff --git a/tests/storage/glusterfs/test_glusterfs_sr.py b/tests/storage/glusterfs/test_glusterfs_sr.py index acdcc5520..3f800872b 100644 --- a/tests/storage/glusterfs/test_glusterfs_sr.py +++ b/tests/storage/glusterfs/test_glusterfs_sr.py @@ -3,13 +3,14 @@ import logging from lib.commands import SSHCommandFailed -from lib.common import vm_image, wait_for +from lib.common import Defer, vm_image, wait_for from lib.host import Host from lib.pool import Pool from lib.sr import SR from lib.vdi import VDI from lib.vm import VM from tests.storage import vdi_is_open +from tests.storage.storage import check_critical_journal_revert, check_vdi_revert, check_vdi_revert_journal # Requirements: # - one XCP-ng host >= 8.2 with an additional unused disk for the SR @@ -61,6 +62,31 @@ def test_volume_stopped(self, host: Host, glusterfs_sr: SR) -> None: if not volume_running: host.ssh('gluster --mode=script volume start vol0') + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_glusterfs_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_glusterfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_glusterfs_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_glusterfs_sr, fistpoint, vm_on_glusterfs_sr.host.pool.master) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_critical_journal_revert( + self, vm_on_glusterfs_sr: VM, defer: Defer, exit_on_fistpoint: None, hostA2: Host + ) -> None: + check_critical_journal_revert(defer, vm_on_glusterfs_sr, hostA2, "FileSR_revert_create_src") + # *** tests with reboots (longer tests). @pytest.mark.reboot diff --git a/tests/storage/largeblock/test_largeblock_sr.py b/tests/storage/largeblock/test_largeblock_sr.py index 7b9d4224e..ea771ea17 100644 --- a/tests/storage/largeblock/test_largeblock_sr.py +++ b/tests/storage/largeblock/test_largeblock_sr.py @@ -2,9 +2,15 @@ import pytest -from lib.common import vm_image, wait_for +from lib.common import Defer, vm_image, wait_for from lib.vdi import ImageFormat from tests.storage import try_to_create_sr_with_missing_device, vdi_is_open +from tests.storage.storage import ( + check_vdi_revert, + check_vdi_revert_cbt, + check_vdi_revert_journal, + check_vdi_revert_journal_cbt, +) from typing import TYPE_CHECKING @@ -50,6 +56,34 @@ def test_snapshot(self, vm_on_largeblock_sr: VM) -> None: vm.test_snapshot_on_running_vm() vm.shutdown(verify=True) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_largeblock_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_largeblock_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_cbt(self, vm_on_largeblock_sr: VM, defer: Defer) -> None: + check_vdi_revert_cbt(defer, vm_on_largeblock_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_journal_cbt(self, vm_on_largeblock_sr: VM, defer: Defer, exit_on_fistpoint: None): + check_vdi_revert_journal_cbt(defer, vm_on_largeblock_sr, "FileSR_revert_create_src") + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_largeblock_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_largeblock_sr, fistpoint) + # *** tests with reboots (longer tests). @pytest.mark.reboot diff --git a/tests/storage/moosefs/test_moosefs_sr.py b/tests/storage/moosefs/test_moosefs_sr.py index a85852990..d02a4aa85 100644 --- a/tests/storage/moosefs/test_moosefs_sr.py +++ b/tests/storage/moosefs/test_moosefs_sr.py @@ -4,13 +4,14 @@ import time from lib.commands import SSHCommandFailed -from lib.common import vm_image, wait_for +from lib.common import Defer, vm_image, wait_for from lib.host import Host from lib.pool import Pool from lib.sr import SR from lib.vdi import VDI from lib.vm import VM from tests.storage import vdi_is_open +from tests.storage.storage import check_critical_journal_revert, check_vdi_revert, check_vdi_revert_journal # Requirements: # - one XCP-ng host >= 8.2 @@ -82,6 +83,31 @@ def test_moosefs_missing_client_pbd_plug_fails(self, host: Host, moosefs_sr: SR) if not moosefs_installed: host.yum_install(['moosefs-client']) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_moosefs_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_moosefs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_moosefs_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_moosefs_sr, fistpoint, vm_on_moosefs_sr.host.pool.master) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_critical_journal_revert( + self, vm_on_moosefs_sr: VM, defer: Defer, exit_on_fistpoint: None, hostA2: Host + ) -> None: + check_critical_journal_revert(defer, vm_on_moosefs_sr, hostA2, "FileSR_revert_create_src") + # *** tests with reboots (longer tests). @pytest.mark.reboot diff --git a/tests/storage/nfs/test_nfs_sr.py b/tests/storage/nfs/test_nfs_sr.py index 2f14202ab..368543478 100644 --- a/tests/storage/nfs/test_nfs_sr.py +++ b/tests/storage/nfs/test_nfs_sr.py @@ -20,6 +20,13 @@ vdi_is_open, xva_export_import, ) +from tests.storage.storage import ( + check_critical_journal_revert, + check_vdi_revert, + check_vdi_revert_cbt, + check_vdi_revert_journal, + check_vdi_revert_journal_cbt, +) # Requirements: # - one XCP-ng host >= 8.0 with an additional unused disk for the SR @@ -104,6 +111,41 @@ def test_snapshot(self, dispatch_nfs: VM) -> None: finally: vm.shutdown(verify=True) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_nfs_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_nfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_cbt(self, vm_on_nfs_sr: VM, defer: Defer) -> None: + check_vdi_revert_cbt(defer, vm_on_nfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_journal_cbt(self, vm_on_nfs_sr: VM, defer: Defer, exit_on_fistpoint: None): + check_vdi_revert_journal_cbt(defer, vm_on_nfs_sr, "FileSR_revert_create_src", vm_on_nfs_sr.host.pool.master) + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_nfs_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_nfs_sr, fistpoint, vm_on_nfs_sr.host.pool.master) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_critical_journal_revert( + self, vm_on_nfs_sr: VM, defer: Defer, exit_on_fistpoint: None, hostA2: Host + ) -> None: + check_critical_journal_revert(defer, vm_on_nfs_sr, hostA2, "FileSR_revert_create_src") + @pytest.mark.small_vm @pytest.mark.parametrize('dispatch_nfs', ['vdi_on_nfs_sr', 'vdi_on_nfs4_sr'], indirect=True) @pytest.mark.parametrize('vdi_op', ['snapshot', 'clone']) diff --git a/tests/storage/storage.py b/tests/storage/storage.py index bddd157f4..029c61cb8 100644 --- a/tests/storage/storage.py +++ b/tests/storage/storage.py @@ -1,11 +1,14 @@ from __future__ import annotations +import pytest + import logging from dataclasses import dataclass from lib import config from lib.commands import SSHCommandFailed from lib.common import QCOW2_MAX, VHD_MAX, Defer, MiB, PackageManagerEnum, strtobool, wait_for +from lib.fistpoint import FistPoint from lib.host import Host from lib.snapshot import Snapshot from lib.sr import SR @@ -511,3 +514,323 @@ def validate_partially_populated_device(vm: VM, dev: str, spans: list[StreamSpan for span in spans: if span.checksum is not None: span.validate(vm, dev) + +def check_vdi_revert(defer: Defer, vm: VM) -> None: + """ + Performs successives reverts to ensure that we can revert + and that the snapshot tree stays consistent across reverts + + We create the following snapshot tree: + snap 1 -> snap 2 + |-----> snap 3 + + Then, we revert in this order: 1 -> 2 -> 3 -> 1 -> 1 + + Args: + defer : fixture used for cleanup + vm : virtual machine used as a base for tests (clones it to keep things tidy) + """ + vm = vm.clone() + snapshots: list[Snapshot] = [] + + def cleanup(vm: VM, snapshots: list[Snapshot]): + for snap in snapshots: + snap.destroy(verify=True) + vm.destroy() + + defer(lambda: cleanup(vm, snapshots)) + + if not vm.is_running(): + vm.start() + vm.wait_for_vm_running_and_ssh_up() + + snap_1 = vm.snapshot() + snapshots.append(snap_1) + + snap_2_file = "/root/snap2" + vm.ssh_touch_file(snap_2_file) + snap_2 = vm.snapshot() + snapshots.append(snap_2) + + snap_1.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(snap_2_file) + + snap_3_file = "/root/snap3" + vm.ssh_touch_file(snap_3_file) + snap_3 = vm.snapshot() + snapshots.append(snap_3) + + snap_2.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(snap_3_file) + assert vm.file_exists(snap_2_file) + + snap_3.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(snap_2_file) + assert vm.file_exists(snap_3_file) + + snap_1.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(snap_2_file) + assert not vm.file_exists(snap_3_file) + + tmp_file = "/root/snap4" + vm.ssh_touch_file(tmp_file) + snap_1.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(snap_2_file) + assert not vm.file_exists(snap_3_file) + assert not vm.file_exists(tmp_file) + +def check_vdi_revert_cbt(defer: Defer, vm: VM): + """ + Perform successives revert from snapshots with/without CBT + and ensure that it stays enabled in a consistent manner + + CBT -> CBT : Keep CBT + CBT -> no CBT : Disable CBT + no CBT -> CBT : Enable CBT + + Args: + defer : fixture used for cleanup + vm : virtual machine used as a base for tests (clones it to keep things tidy) + """ + snapshots: list[Snapshot] = [] + vm = vm.clone() + sr = vm.get_sr() + + def cleanup(vm: VM, snapshots: list[Snapshot]): + for snap in snapshots: + snap.destroy(verify=True) + vm.destroy() + + defer(lambda: cleanup(vm, snapshots)) + + if not vm.is_running(): + vm.start() + vm.wait_for_vm_running_and_ssh_up() + snap_no_cbt_file = "/root/snap_no_cbt" + vm.ssh_touch_file(snap_no_cbt_file) + snap_no_cbt = vm.snapshot() + snapshots.append(snap_no_cbt) + + vm.vdis[0].enable_cbt() + assert len(vm.vdis) == 1 + snap_cbt_file = "/root/snap_cbt" + vm.ssh_touch_file(snap_cbt_file) + snap_cbt = vm.snapshot() + snapshots.append(snap_cbt) + + before_revert_file = "/root/snap_before_revert" + vm.ssh_touch_file(before_revert_file) + + # CBT -> CBT + snap_cbt.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(before_revert_file) + assert vm.file_exists(snap_no_cbt_file) + assert vm.file_exists(snap_cbt_file) + assert vm.vdis[0].is_cbt_enabled() + assert VDI(snap_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + assert not VDI(snap_no_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + + # CBT -> no CBT + snap_no_cbt.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(before_revert_file) + assert vm.file_exists(snap_no_cbt_file) + assert not vm.file_exists(snap_cbt_file) + assert not vm.vdis[0].is_cbt_enabled() + assert VDI(snap_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + assert not VDI(snap_no_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + + # no CBT -> CBT + snap_cbt.revert() + vm.start() + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(before_revert_file) + assert vm.file_exists(snap_no_cbt_file) + assert vm.file_exists(snap_cbt_file) + assert vm.vdis[0].is_cbt_enabled() + assert VDI(snap_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + assert not VDI(snap_no_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + +def check_vdi_revert_journal_cbt(defer: Defer, vm: VM, fistpoint: str, host: Host | None = None): + """ + Perform successives failed revert from snapshots with/without CBT + and ensure that it stays enabled in a consistent manner and that + rollback journal runs successfully. + + CBT -> CBT : Keep CBT + CBT -> no CBT : Keep CBT + no CBT -> CBT : No CBT + + Warning: + Provided fistpoints should raise an error during the revert execution! + If your backend doesn't raise an error, please use the `exit_on_fistpoint` fixture. + + Args: + defer : fixture used for cleanup + vm : virtual machine used as a base for tests (clones it to keep things tidy) + fistpoint : fistpoint to enable during the revert command + host : Optional host to run the VM on (default None) + """ + host_uuid = host.uuid if host else None + + vm = vm.clone() + sr = vm.get_sr() + + snapshots: list[Snapshot] = [] + + def cleanup(vm: VM, snapshots: list[Snapshot]): + for snap in snapshots: + snap.destroy(verify=True) + vm.destroy() + + defer(lambda: cleanup(vm, snapshots)) + + if not vm.is_running(): + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + + snap_no_cbt_file = "/root/snap_no_cbt" + vm.ssh_touch_file(snap_no_cbt_file) + snap_no_cbt = vm.snapshot() + snapshots.append(snap_no_cbt) + + vm.vdis[0].enable_cbt() + snap_cbt_file = "/root/snap_cbt" + vm.ssh_touch_file(snap_cbt_file) + snap_cbt = vm.snapshot() + snapshots.append(snap_cbt) + + before_revert_file = "/root/snap_before_revert" + vm.ssh_touch_file(before_revert_file) + + # CBT -> CBT + with FistPoint(vm.host, fistpoint), pytest.raises(SSHCommandFailed): + snap_cbt.revert() + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + assert vm.file_exists(before_revert_file) + assert vm.file_exists(snap_no_cbt_file) + assert vm.file_exists(snap_cbt_file) + assert vm.vdis[0].is_cbt_enabled() + assert VDI(snap_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + assert not VDI(snap_no_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + + # CBT -> no CBT + with FistPoint(vm.host, fistpoint), pytest.raises(SSHCommandFailed): + snap_no_cbt.revert() + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + assert vm.file_exists(before_revert_file) + assert vm.file_exists(snap_no_cbt_file) + assert vm.file_exists(snap_cbt_file) + assert vm.vdis[0].is_cbt_enabled() + assert VDI(snap_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + assert not VDI(snap_no_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + + # no CBT -> CBT + snap_no_cbt.revert() + with FistPoint(vm.host, fistpoint), pytest.raises(SSHCommandFailed): + snap_cbt.revert() + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(before_revert_file) + assert vm.file_exists(snap_no_cbt_file) + assert not vm.file_exists(snap_cbt_file) + assert not vm.vdis[0].is_cbt_enabled() + assert VDI(snap_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + assert not VDI(snap_no_cbt.vdi_uuids()[0], sr=sr).is_cbt_enabled() + +def check_vdi_revert_journal(defer: Defer, vm: VM, fistpoint: str, host: Host | None = None): + """ + Perform successives failed revert from snapshots + and ensure that the rollback journal is able to run. + + Warning: + Provided fistpoints should raise an error during the revert execution! + If your backend doesn't raise an error, please use the `exit_on_fistpoint` fixture. + + Args: + defer : fixture used for cleanup + vm : virtual machine used as a base for tests (clones it to keep things tidy) + fistpoint : fistpoint to enable during the revert command + host : Optional host to run the VM on (default None) + """ + vm = vm.clone() + snap = vm.snapshot() + host_uuid = host.uuid if host else None + + def cleanup(vm: VM, snap: Snapshot): + snap.destroy(verify=True) + vm.destroy() + + defer(lambda: cleanup(vm, snap)) + + if not vm.is_running(): + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + file = "/root/file" + vm.ssh_touch_file(file) + + with FistPoint(host if host else vm.host, fistpoint), pytest.raises(SSHCommandFailed): + snap.revert() + + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + assert vm.file_exists(file) + + snap.revert() + vm.start(host_uuid) + vm.wait_for_vm_running_and_ssh_up() + assert not vm.file_exists(file) + +def check_critical_journal_revert(defer: Defer, vm: VM, host: Host, fistpoint: str): + """ + Ensure that when a critical journal generated during a failed revert exists, + no SR operation is executed outside of the master node. + + Warning: + Provided fistpoints should raise an error during the revert execution! + If your backend doesn't raise an error, please use the `exit_on_fistpoint` fixture. + + Args: + defer : fixture used for cleanup + vm : virtual machine used as a base for tests (clones it to keep things tidy) + host : second host in the pool, different from the master + fistpoint : fistpoint to enable during the revert command + """ + vm = vm.clone() + sr = vm.get_sr() + snap = vm.snapshot() + sr.param_set("other-config", "false", "auto-scan") # Avoid journals being run during tests unexpectedly + + def cleanup(sr: SR, vm: VM, snap: Snapshot): + sr.scan() # Force journals to be processed + sr.param_remove("other-config", "auto-scan") + snap.destroy(verify=True) + vm.destroy() + + defer(lambda: cleanup(sr, vm, snap)) + + with FistPoint(vm.host, fistpoint), pytest.raises(SSHCommandFailed): + snap.revert() + + with pytest.raises( + SSHCommandFailed, + check=lambda e: ( + "The SR is not available [opterr=Critical journals are pending. A scan is required.]" in e.stdout + ), + ): + vm.start(on=host.uuid) diff --git a/tests/storage/xfs/test_xfs_sr.py b/tests/storage/xfs/test_xfs_sr.py index d73f86634..70ce2ba53 100644 --- a/tests/storage/xfs/test_xfs_sr.py +++ b/tests/storage/xfs/test_xfs_sr.py @@ -22,6 +22,12 @@ vdi_is_open, xva_export_import, ) +from tests.storage.storage import ( + check_vdi_revert, + check_vdi_revert_cbt, + check_vdi_revert_journal, + check_vdi_revert_journal_cbt, +) # Requirements: # - one XCP-ng host >= 8.2 with an additional unused disk for the SR @@ -93,6 +99,34 @@ def test_vdi_export_import(self, storage_test_vm: VM, xfs_sr: SR, image_format: defer: Defer) -> None: vdi_export_import(storage_test_vm, xfs_sr, image_format, temp_large_dir, defer) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_xfs_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_xfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_cbt(self, vm_on_xfs_sr: VM, defer: Defer) -> None: + check_vdi_revert_cbt(defer, vm_on_xfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_journal_cbt(self, vm_on_xfs_sr: VM, defer: Defer, exit_on_fistpoint: None): + check_vdi_revert_journal_cbt(defer, vm_on_xfs_sr, "FileSR_revert_create_src") + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_xfs_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_xfs_sr, fistpoint) + # *** tests with reboots (longer tests). @pytest.mark.reboot diff --git a/tests/storage/zfs/test_zfs_sr.py b/tests/storage/zfs/test_zfs_sr.py index f97ec26e6..6562c6b11 100755 --- a/tests/storage/zfs/test_zfs_sr.py +++ b/tests/storage/zfs/test_zfs_sr.py @@ -22,6 +22,12 @@ vdi_is_open, xva_export_import, ) +from tests.storage.storage import ( + check_vdi_revert, + check_vdi_revert_cbt, + check_vdi_revert_journal, + check_vdi_revert_journal_cbt, +) from .conftest import POOL_NAME @@ -95,6 +101,34 @@ def test_vdi_export_import(self, storage_test_vm: VM, zfs_sr: SR, image_format: defer: Defer) -> None: vdi_export_import(storage_test_vm, zfs_sr, image_format, temp_large_dir, defer) + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert(self, vm_on_zfs_sr: VM, defer: Defer) -> None: + check_vdi_revert(defer, vm_on_zfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_cbt(self, vm_on_zfs_sr: VM, defer: Defer) -> None: + check_vdi_revert_cbt(defer, vm_on_zfs_sr) + + @pytest.mark.small_vm + @pytest.mark.big_vm + def test_revert_journal_cbt(self, vm_on_zfs_sr: VM, defer: Defer, exit_on_fistpoint: None): + check_vdi_revert_journal_cbt(defer, vm_on_zfs_sr, "FileSR_revert_create_src") + + @pytest.mark.small_vm + @pytest.mark.big_vm + @pytest.mark.parametrize( + "fistpoint", + [ + "FileSR_revert_create_insert", + "FileSR_revert_create_src", + "FileSR_revert_create_dest", + ] + ) + def test_revert_journal(self, vm_on_zfs_sr: VM, defer: Defer, exit_on_fistpoint: None, fistpoint: str): + check_vdi_revert_journal(defer, vm_on_zfs_sr, fistpoint) + # *** tests with reboots (longer tests). @pytest.mark.reboot