Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/vdi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
246 changes: 246 additions & 0 deletions lib/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,252 @@ def test_snapshot_on_running_vm(self) -> None:
finally:
snapshot.destroy(verify=True)

def test_vdi_revert(self) -> None:
Comment thread
glehmann marked this conversation as resolved.
Outdated
# Successives reverts to ensure the
# snapshot tree stays consistent
# Created snapshot Tree
# snap 1 -> snap 2
# |-----> snap 3
# Revert order: 1 -> 2 -> 3 -> 1 -> 1
vm = self.clone()
snapshots = []
if not vm.is_running():
vm.start()
vm.wait_for_vm_running_and_ssh_up()
try:
snap_1 = vm.snapshot()
snapshots.append(snap_1)

snap_2_file = f"/root/{uuid.uuid4()}"
Comment thread
glehmann marked this conversation as resolved.
Outdated
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()
vm.ssh(f'test ! -f {snap_2_file}')
Comment thread
glehmann marked this conversation as resolved.
Outdated

snap_3_file = f"/root/{uuid.uuid4()}"
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()
vm.ssh(f'test ! -f {snap_3_file}')
vm.ssh(f'test -f {snap_2_file}')

snap_3.revert()
vm.start()
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test ! -f {snap_2_file}')
vm.ssh(f'test -f {snap_3_file}')

snap_1.revert()
vm.start()
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test ! -f {snap_2_file}')
vm.ssh(f'test ! -f {snap_3_file}')

tmp_file = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(tmp_file)
snap_1.revert()
vm.start()
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test ! -f {snap_2_file}')
vm.ssh(f'test ! -f {snap_3_file}')
vm.ssh(f'test ! -f {tmp_file}')

finally:
for snap in snapshots:
Comment thread
glehmann marked this conversation as resolved.
Outdated
snap.destroy(verify=True)
vm.destroy()

def test_vdi_revert_cbt(self):
snapshots = []
vm = self.clone()
sr = vm.get_sr()

if not vm.is_running():
vm.start()
vm.wait_for_vm_running_and_ssh_up()
snap_no_cbt_file = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(snap_no_cbt_file)
snap_no_cbt = vm.snapshot()
snapshots.append(snap_no_cbt)

try:
vm.vdis[0].enable_cbt()
assert len(vm.vdis) == 1
snap_cbt_file = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(snap_cbt_file)
snap_cbt = vm.snapshot()
snapshots.append(snap_cbt)

before_revert_file = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(before_revert_file)

# CBT -> CBT
snap_cbt.revert()
vm.start()
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test ! -f {before_revert_file}')
vm.ssh(f'test -f {snap_no_cbt_file}')
vm.ssh(f'test -f {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()
vm.ssh(f'test ! -f {before_revert_file}')
vm.ssh(f'test -f {snap_no_cbt_file}')
vm.ssh(f'test ! -f {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()
vm.ssh(f'test ! -f {before_revert_file}')
vm.ssh(f'test -f {snap_no_cbt_file}')
vm.ssh(f'test -f {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()

finally:
for snap in snapshots:
snap.destroy(verify=True)
vm.destroy()

def test_vdi_revert_journal_cbt(self, request: pytest.FixtureRequest, fistpoint: str, host: Host | None = None):
from lib.fistpoint import FistPoint # noqa: I001 Avoid circular import
host_uuid = host.uuid if host else None

request.getfixturevalue("exit_on_fistpoint")
Comment thread
glehmann marked this conversation as resolved.
Outdated
vm = self.clone()
sr = vm.get_sr()

snapshots = []
if not vm.is_running():
vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()

snap_no_cbt_file = f"/root/{uuid.uuid4()}"
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 = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(snap_cbt_file)
snap_cbt = vm.snapshot()
snapshots.append(snap_cbt)

before_revert_file = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(before_revert_file)
try:
# CBT -> CBT
with FistPoint(vm.host, fistpoint), pytest.raises(commands.SSHCommandFailed):
snap_cbt.revert()
vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test -f {before_revert_file}')
vm.ssh(f'test -f {snap_no_cbt_file}')
vm.ssh(f'test -f {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(commands.SSHCommandFailed):
snap_no_cbt.revert()
vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test -f {before_revert_file}')
vm.ssh(f'test -f {snap_no_cbt_file}')
vm.ssh(f'test -f {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(commands.SSHCommandFailed):
snap_cbt.revert()
vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test ! -f {before_revert_file}')
vm.ssh(f'test -f {snap_no_cbt_file}')
vm.ssh(f'test ! -f {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()

finally:
for snap in snapshots:
snap.destroy(verify=True)
vm.destroy()

def test_vdi_revert_journal(self, request: pytest.FixtureRequest, fistpoint: str, host: Host | None = None):
from lib.fistpoint import FistPoint # noqa: I001 Avoid circular import
request.getfixturevalue("exit_on_fistpoint")
vm = self.clone()
snap = vm.snapshot()
host_uuid = host.uuid if host else None
if not vm.is_running():
vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()
try:
file = f"/root/{uuid.uuid4()}"
vm.ssh_touch_file(file)

with FistPoint(host if host else vm.host, fistpoint), pytest.raises(commands.SSHCommandFailed):
snap.revert()

vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test -f {file}')

snap.revert()
vm.start(host_uuid)
vm.wait_for_vm_running_and_ssh_up()
vm.ssh(f'test ! -f {file}')
finally:
snap.destroy(verify=True)
vm.destroy()

def test_critical_journal_revert(self, request: pytest.FixtureRequest, host: Host, fistpoint: str):
from lib.fistpoint import FistPoint # noqa: I001 Avoid circular import
Comment thread
glehmann marked this conversation as resolved.
Outdated
vm = self.clone()
sr = vm.get_sr()
request.getfixturevalue("exit_on_fistpoint")
snap = vm.snapshot()
sr.param_set("other-config", "false", "auto-scan") # Avoid journals being run during tests unexpectedly
try:
with FistPoint(vm.host, fistpoint), pytest.raises(commands.SSHCommandFailed):
snap.revert()

with pytest.raises(
commands.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)
finally:
sr.scan() # Force journals to be processed
sr.param_remove("other-config", "auto-scan")
snap.destroy(verify=True)
vm.destroy()

def get_messages(self, name: str) -> List[str]:
args: dict[str, str | bool | dict[str, str]] = {
'obj-uuid': self.uuid,
Expand Down
23 changes: 23 additions & 0 deletions tests/storage/cephfs/test_cephfs_sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,29 @@ 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) -> None:
vm_on_cephfs_sr.test_vdi_revert()

@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, request: pytest.FixtureRequest, fistpoint: str):
vm_on_cephfs_sr.test_vdi_revert_journal(request, 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, request: pytest.FixtureRequest, hostA2: Host) -> None:
vm_on_cephfs_sr.test_critical_journal_revert(request, hostA2, "FileSR_revert_create_src")

# *** tests with reboots (longer tests).

@pytest.mark.reboot
Expand Down
28 changes: 28 additions & 0 deletions tests/storage/ext/test_ext_sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,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) -> None:
vm_on_ext_sr.test_vdi_revert()

@pytest.mark.small_vm
@pytest.mark.big_vm
def test_revert_cbt(self, vm_on_ext_sr: VM) -> None:
vm_on_ext_sr.test_vdi_revert_cbt()

@pytest.mark.small_vm
@pytest.mark.big_vm
def test_revert_journal_cbt(self, vm_on_ext_sr: VM, request: pytest.FixtureRequest):
vm_on_ext_sr.test_vdi_revert_journal_cbt(request, "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, request: pytest.FixtureRequest, fistpoint: str):
vm_on_ext_sr.test_vdi_revert_journal(request, 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:
Expand Down
28 changes: 28 additions & 0 deletions tests/storage/glusterfs/test_glusterfs_sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,34 @@ 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) -> None:
vm_on_glusterfs_sr.test_vdi_revert()

@pytest.mark.small_vm
@pytest.mark.big_vm
@pytest.mark.parametrize(
"fistpoint",

@gthvn1 gthvn1 Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, what does 'fistpoint' mean here? I have a few ideas, but they don't really seem to fit? Is it kind of a joke?
I saw it in the code as well that is why I'm curious here :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fistpoints are special files that are used to alter the behaviour of the smapi.
Basically, when present, they activates some sorts of breakpoints at specific points of the program.
We can trigger different kinds of callbacks, the default one is to pause for 10s. When combined with the exit_on_fistpoint fistpoint, it enforces a crash when reaching those breakpoints.

This is what makes the revert fail in controlled conditions, they are designed for end to end testing.

And yes, the name is funny, they exists since the first git commit of the sm project.

[
"FileSR_revert_create_insert",
"FileSR_revert_create_src",
"FileSR_revert_create_dest",
]
)
def test_revert_journal(self, vm_on_glusterfs_sr: VM, request: pytest.FixtureRequest, fistpoint: str):
vm_on_glusterfs_sr.test_vdi_revert_journal(request, 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,
request: pytest.FixtureRequest,
hostA2: Host,
) -> None:
vm_on_glusterfs_sr.test_critical_journal_revert(request, hostA2, "FileSR_revert_create_src")

# *** tests with reboots (longer tests).

@pytest.mark.reboot
Expand Down
Loading
Loading