Skip to content

Commit ef08fbb

Browse files
authored
Merge pull request #340 from xcp-ng/dtt-fp
Implement fistpoint for smapi in tests
2 parents 39dc07e + 26a2e78 commit ef08fbb

File tree

6 files changed

+201
-0
lines changed

6 files changed

+201
-0
lines changed

conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,15 @@ def nfs_iso_sr(host, nfs_iso_device_config):
744744
# teardown
745745
sr.forget()
746746

747+
@pytest.fixture(scope='function')
748+
def exit_on_fistpoint(host):
749+
from lib.fistpoint import FistPoint
750+
logging.info(">> Enabling exit on fistpoint")
751+
FistPoint.enable_exit_on_fistpoint(host)
752+
yield
753+
logging.info("<< Disabling exit on fistpoint")
754+
FistPoint.disable_exit_on_fistpoint(host)
755+
747756
@pytest.fixture(scope='module')
748757
def cifs_iso_sr(host, cifs_iso_device_config):
749758
""" A Samba/CIFS SR. """

lib/fistpoint.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import logging
2+
3+
from lib.commands import SSHCommandFailed
4+
from lib.host import Host
5+
6+
from typing import Final
7+
8+
FISTPOINT_DIR: Final = "/tmp"
9+
LVHDRT_EXIT_FIST: Final = "fist_LVHDRT_exit"
10+
11+
12+
class FistPoint:
13+
"""
14+
A fistpoint is an action that you can enable in the smapi for tests.
15+
16+
It allows for example, add a sleep at some point or raise an exception.
17+
For example:
18+
```
19+
with FistPoint(vm.host, "blktap_activate_inject_failure"):
20+
with pytest.raises(SSHCommandFailed):
21+
vm.start()
22+
vm.shutdown(force=True)
23+
```
24+
Activating the fistpoint `blktap_activate_inject_failure` mean that the VDI
25+
activation will fail. This fistpoint always raise an exception but most
26+
fistpoint just add a sleep at a point in the code.
27+
Using the fixture `exit_on_fistpoint` make all fistpoints raise an
28+
exception instead by enabling a special fistpoint called `fist_LVHDRT_exit`
29+
"""
30+
31+
fistpointName: str
32+
33+
def __init__(self, host: Host, name: str):
34+
self.fistpointName = self._get_name(name)
35+
self.host = host
36+
37+
@staticmethod
38+
def enable_exit_on_fistpoint(host: Host):
39+
host.create_file(FistPoint._get_path(LVHDRT_EXIT_FIST), "")
40+
41+
@staticmethod
42+
def disable_exit_on_fistpoint(host: Host):
43+
host.ssh(["rm", FistPoint._get_path(LVHDRT_EXIT_FIST)])
44+
45+
@staticmethod
46+
def _get_name(name: str) -> str:
47+
if name.startswith("fist_"):
48+
return name
49+
else:
50+
return f"fist_{name}"
51+
52+
@staticmethod
53+
def _get_path(name) -> str:
54+
return f"{FISTPOINT_DIR}/{name}"
55+
56+
def enable(self):
57+
logging.info(f"Enable fistpoint {self.fistpointName}")
58+
self.host.create_file(self._get_path(self.fistpointName), "")
59+
60+
def disable(self):
61+
logging.info(f"Disabling fistpoint {self.fistpointName}")
62+
try:
63+
self.host.ssh(["rm", self._get_path(self.fistpointName)])
64+
except SSHCommandFailed as e:
65+
logging.info(f"Failed trying to disable fistpoint {self._get_path(self.fistpointName)} with error {e}")
66+
raise
67+
68+
def __enter__(self):
69+
self.enable()
70+
return self
71+
72+
def __exit__(self, *_):
73+
self.disable()

lib/host.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,15 @@ def get_sr_from_vdi_uuid(self, vdi_uuid: str) -> Optional[SR]:
740740
if not sr_uuid:
741741
return None
742742
return SR(sr_uuid, self.pool)
743+
744+
def lvs(self, vgName: Optional[str] = None, ignore_MGT: bool = True) -> List[str]:
745+
ret: List[str] = []
746+
cmd = ["lvs", "--noheadings", "-o", "LV_NAME"]
747+
if vgName:
748+
cmd.append(vgName)
749+
output = self.ssh(cmd)
750+
for line in output.splitlines():
751+
if ignore_MGT and "MGT" in line:
752+
continue
753+
ret.append(line.strip())
754+
return ret

lib/vdi.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ def clone(self):
4343
def readonly(self) -> bool:
4444
return strtobool(self.param_get("read-only"))
4545

46+
def get_virtual_size(self) -> int:
47+
return int(self.param_get("virtual-size"))
48+
49+
def resize(self, new_size: int) -> None:
50+
logging.info(f"Resizing VDI {self.uuid} to {new_size}")
51+
self.sr.pool.master.xe("vdi-resize", {"uuid": self.uuid, "disk-size": str(new_size)})
52+
4653
def __str__(self):
4754
return f"VDI {self.uuid} on SR {self.sr.uuid}"
4855

tests/storage/ext/test_ext_sr.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
import pytest
44

5+
import logging
6+
7+
from lib.commands import SSHCommandFailed
58
from lib.common import vm_image, wait_for
9+
from lib.fistpoint import FistPoint
10+
from lib.vdi import VDI
611
from tests.storage import try_to_create_sr_with_missing_device, vdi_is_open
712

813
from typing import TYPE_CHECKING
@@ -63,6 +68,44 @@ def test_snapshot(self, vm_on_ext_sr):
6368

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

71+
@pytest.mark.small_vm
72+
@pytest.mark.big_vm
73+
def test_blktap_activate_failure(self, vm_on_ext_sr):
74+
from lib.fistpoint import FistPoint
75+
vm = vm_on_ext_sr
76+
with FistPoint(vm.host, "blktap_activate_inject_failure"), pytest.raises(SSHCommandFailed):
77+
vm.start()
78+
vm.shutdown(force=True)
79+
80+
@pytest.mark.small_vm
81+
@pytest.mark.big_vm
82+
def test_resize(self, vm_on_ext_sr):
83+
vm = vm_on_ext_sr
84+
vdi = VDI(vm.vdi_uuids()[0], host=vm.host)
85+
old_size = vdi.get_virtual_size()
86+
new_size = old_size + (1 * 1024 * 1024 * 1024) # Adding a 1GiB to size
87+
88+
vdi.resize(new_size)
89+
90+
assert vdi.get_virtual_size() == new_size
91+
92+
@pytest.mark.small_vm
93+
@pytest.mark.big_vm
94+
def test_failing_resize(self, host, ext_sr, vm_on_ext_sr, exit_on_fistpoint):
95+
vm = vm_on_ext_sr
96+
vdi = VDI(vm.vdi_uuids()[0], host=vm.host)
97+
old_size = vdi.get_virtual_size()
98+
new_size = old_size + (1 * 1024 * 1024 * 1024) # Adding a 1GiB to size
99+
100+
with FistPoint(vm.host, "LVHDRT_inflate_after_setSize"):
101+
try:
102+
vdi.resize(new_size)
103+
except SSHCommandFailed:
104+
logging.info(f"Launching SR scan for {ext_sr} after failure")
105+
host.xe("sr-scan", {"uuid": ext_sr})
106+
107+
assert vdi.get_virtual_size() == new_size
108+
66109
@pytest.mark.reboot
67110
@pytest.mark.small_vm
68111
def test_reboot(self, host, ext_sr, vm_on_ext_sr):

tests/storage/lvm/test_lvm_sr.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
import pytest
44

5+
import logging
6+
7+
from lib.commands import SSHCommandFailed
58
from lib.common import vm_image, wait_for
9+
from lib.fistpoint import FistPoint
10+
from lib.vdi import VDI
611
from tests.storage import try_to_create_sr_with_missing_device, vdi_is_open
712

813
from typing import TYPE_CHECKING
@@ -61,6 +66,58 @@ def test_snapshot(self, vm_on_lvm_sr):
6166
finally:
6267
vm.shutdown(verify=True)
6368

69+
@pytest.mark.small_vm
70+
@pytest.mark.big_vm
71+
def test_failing_resize_on_inflate_after_setSize(self, host, lvm_sr, vm_on_lvm_sr, exit_on_fistpoint):
72+
vm = vm_on_lvm_sr
73+
lvinflate = ""
74+
vdi = VDI(vm.vdi_uuids()[0], host=vm.host)
75+
new_size = vdi.get_virtual_size() + (1 * 1024 * 1024 * 1024) # Adding a 1GiB to size
76+
77+
with FistPoint(vm.host, "LVHDRT_inflate_after_setSize"), pytest.raises(SSHCommandFailed) as exc_info:
78+
vdi.resize(new_size)
79+
logging.info(exc_info)
80+
81+
lvlist = host.lvs(f"VG_XenStorage-{lvm_sr.uuid}")
82+
for lv in lvlist:
83+
if lv.startswith("inflate_"):
84+
logging.info(f"Found inflate journal following error: {lv}")
85+
lvinflate = lv
86+
87+
logging.info(f"Launching SR scan for {lvm_sr.uuid} to resolve failure")
88+
try:
89+
host.xe("sr-scan", {"uuid": lvm_sr.uuid})
90+
except SSHCommandFailed as e:
91+
assert False, f"Failing to scan following a inflate error {e}"
92+
assert lvinflate not in host.lvs(f"VG_XenStorage-{lvm_sr.uuid}"), \
93+
"Inflate journal still exist following the scan"
94+
95+
@pytest.mark.small_vm
96+
@pytest.mark.big_vm
97+
def test_failing_resize_on_inflate_after_setSizePhys(self, host, lvm_sr, vm_on_lvm_sr, exit_on_fistpoint):
98+
vm = vm_on_lvm_sr
99+
lvinflate = ""
100+
vdi = VDI(vm.vdi_uuids()[0], host=vm.host)
101+
new_size = vdi.get_virtual_size() + (1 * 1024 * 1024 * 1024) # Adding a 1GiB to size
102+
103+
with FistPoint(vm.host, "LVHDRT_inflate_after_setSizePhys"), pytest.raises(SSHCommandFailed) as exc_info:
104+
vdi.resize(new_size)
105+
logging.info(exc_info)
106+
107+
lvlist = host.lvs(f"VG_XenStorage-{lvm_sr.uuid}")
108+
for lv in lvlist:
109+
if lv.startswith("inflate_"):
110+
logging.info(f"Found inflate journal following error: {lv}")
111+
lvinflate = lv
112+
113+
logging.info(f"Launching SR scan for {lvm_sr.uuid} to resolve failure")
114+
try:
115+
host.xe("sr-scan", {"uuid": lvm_sr.uuid})
116+
except SSHCommandFailed as e:
117+
assert False, f"Failing to scan following a inflate error {e}"
118+
assert lvinflate not in host.lvs(f"VG_XenStorage-{lvm_sr.uuid}"), \
119+
"Inflate journal still exist following the scan"
120+
64121
# *** tests with reboots (longer tests).
65122

66123
@pytest.mark.reboot

0 commit comments

Comments
 (0)