Skip to content

Commit 89f71dc

Browse files
committed
storage: create native 4k device qcow2 tests
Signed-off-by: Erwan Croze <erwan.croze@vates.tech>
1 parent f7467dd commit 89f71dc

10 files changed

Lines changed: 497 additions & 2 deletions

jobs.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,66 @@ class JobData(TypedDict):
368368
"paths": ["tests/storage"],
369369
"markers": "unused_4k_disks and quicktest",
370370
},
371+
"qcow2-4k-main": {
372+
"description": "tests QCOW2 image format on EXT and LVM SRs backed by native 4KiB block devices. "
373+
"avoids quicktest, migrations and reboots",
374+
"requirements": [
375+
"A pool >= 8.3 with at least 1 host.",
376+
"An additional free native 4KiB disk on the first host.",
377+
"A small VM that can be imported on the SRs.",
378+
],
379+
"nb_pools": 1,
380+
"params": {
381+
"--vm": "single/small_vm",
382+
},
383+
"paths": ["tests/storage/ext", "tests/storage/lvm"],
384+
"markers": "(small_vm or no_vm) and unused_4k_disks and not reboot and not quicktest",
385+
"name_filter": "_4k and not migration",
386+
},
387+
"qcow2-4k-migrations": {
388+
"description": "storage migration tests for QCOW2 VDIs on EXT and LVM SRs backed by native 4KiB block devices",
389+
"requirements": [
390+
"A pool >= 8.3 with at least 2 hosts, each with a local SR.",
391+
"An additional free native 4KiB disk on the first host.",
392+
"A second pool with a SR to receive migrated VMs.",
393+
"A small VM that can be imported on the SRs.",
394+
],
395+
"nb_pools": 2,
396+
"params": {
397+
"--vm": "single/small_vm",
398+
},
399+
"paths": ["tests/storage/ext", "tests/storage/lvm"],
400+
"markers": "unused_4k_disks",
401+
"name_filter": "_4k and migration",
402+
},
403+
"qcow2-4k-reboots": {
404+
"description": "QCOW2 on native 4KiB block device tests on EXT and LVM SRs that involve rebooting hosts",
405+
"requirements": [
406+
"A pool >= 8.3 with at least 1 host.",
407+
"An additional free native 4KiB disk on the first host.",
408+
"A small VM that can be imported on the SRs.",
409+
],
410+
"nb_pools": 1,
411+
"params": {
412+
"--vm": "single/small_vm",
413+
},
414+
"paths": ["tests/storage/ext", "tests/storage/lvm"],
415+
"markers": "unused_4k_disks and reboot",
416+
"name_filter": "_4k",
417+
},
418+
"qcow2-4k-quicktest": {
419+
"description": "runs `quicktest` for QCOW2 VDIs on EXT and LVM SRs backed by native 4KiB block devices",
420+
"requirements": [
421+
"A pool >= 8.3 with at least 1 host",
422+
"An additional free native 4KiB disk on the first host.",
423+
],
424+
"nb_pools": 1,
425+
"params": {
426+
},
427+
"paths": ["tests/storage/ext", "tests/storage/lvm"],
428+
"markers": "unused_4k_disks and quicktest",
429+
"name_filter": "_4k",
430+
},
371431
"sb-main": {
372432
"description": "tests uefistored/varstored and SecureBoot using a small unix VM (or no VM when none needed)",
373433
"requirements": [

lib/vdi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
ImageFormat = Literal['qcow2', 'raw', 'vhd']
2525

26+
QCOW2_IMAGE_FORMAT: ImageFormat = 'qcow2'
27+
2628
class VDI:
2729
xe_prefix = "vdi"
2830
sr: SR

tests/storage/ext/conftest.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from lib import config
88
from lib.host import Host
99
from lib.sr import SR
10-
from lib.vdi import VDI, ImageFormat
10+
from lib.vdi import QCOW2_IMAGE_FORMAT, VDI, ImageFormat
1111
from lib.vm import VM
1212

1313
from typing import Any, Generator
@@ -39,3 +39,29 @@ def vm_on_ext_sr(host: Host, ext_sr: SR, vm_ref: str) -> Generator[VM, None, Non
3939
# teardown
4040
logging.info("<< Destroy VM")
4141
vm.destroy(verify=True)
42+
43+
@pytest.fixture(scope='package')
44+
def ext_sr_4k(host: Host,
45+
unused_4k_disks: dict[Host, list[Host.BlockDeviceInfo]]) -> Generator[SR, None, None]:
46+
"""An EXT SR on a native 4KiB block device, using the QCOW2 image format."""
47+
sr_disk = unused_4k_disks[host][0].name
48+
sr = host.sr_create('ext', "EXT-4K-local-SR-test",
49+
{'device': '/dev/' + sr_disk,
50+
'preferred-image-formats': QCOW2_IMAGE_FORMAT})
51+
yield sr
52+
# teardown
53+
sr.destroy()
54+
55+
@pytest.fixture(scope='module')
56+
def vdi_on_ext_sr_4k(ext_sr_4k: SR) -> Generator[VDI, None, None]:
57+
vdi = ext_sr_4k.create_vdi('EXT-4K-local-VDI-test', virtual_size=config.volume_size)
58+
yield vdi
59+
vdi.destroy()
60+
61+
@pytest.fixture(scope='module')
62+
def vm_on_ext_sr_4k(host: Host, ext_sr_4k: SR, vm_ref: str) -> Generator[VM, None, None]:
63+
vm = host.import_vm(vm_ref, sr_uuid=ext_sr_4k.uuid)
64+
yield vm
65+
# teardown
66+
logging.info("<< Destroy VM")
67+
vm.destroy(verify=True)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from lib.commands import SSHCommandFailed
6+
from lib.common import Defer, vm_image, wait_for
7+
from lib.fistpoint import FistPoint
8+
from lib.host import Host
9+
from lib.sr import SR
10+
from lib.vdi import QCOW2_IMAGE_FORMAT, VDI
11+
from lib.vm import VM
12+
from tests.storage import (
13+
MAX_VDI_SIZE,
14+
CoalesceOperation,
15+
XVACompression,
16+
coalesce_integrity,
17+
full_vdi_write,
18+
try_to_create_sr_with_missing_device,
19+
vdi_export_import,
20+
vdi_is_open,
21+
xva_export_import,
22+
)
23+
24+
# Requirements:
25+
# - one XCP-ng host >= 8.3 with an additional unused native 4KiB disk for the SR
26+
27+
class TestEXTSR4KCreateDestroy:
28+
"""
29+
Tests that do not use fixtures that setup the SR or import VMs,
30+
because they precisely need to test SR creation and destruction,
31+
and VM import.
32+
"""
33+
34+
def test_create_sr_with_missing_device(self, host: Host) -> None:
35+
try_to_create_sr_with_missing_device('ext', 'EXT-4K-local-SR-test', host)
36+
37+
def test_create_and_destroy_sr(self, host: Host,
38+
unused_4k_disks: dict[Host, list[Host.BlockDeviceInfo]]) -> None:
39+
# Create and destroy tested in the same test to leave the host as unchanged as possible
40+
sr_disk = unused_4k_disks[host][0].name
41+
sr = host.sr_create('ext', "EXT-4K-local-SR-test",
42+
{'device': '/dev/' + sr_disk,
43+
'preferred-image-formats': QCOW2_IMAGE_FORMAT}, verify=True)
44+
# import a VM in order to detect vm import issues here rather than in the vm_on_xfs_fixture used in
45+
# the next tests, because errors in fixtures break teardown
46+
vm = host.import_vm(vm_image('mini-linux-x86_64-bios'), sr_uuid=sr.uuid)
47+
vm.destroy(verify=True)
48+
sr.destroy(verify=True)
49+
50+
@pytest.mark.usefixtures("ext_sr_4k")
51+
class TestEXTSR4K:
52+
@pytest.mark.quicktest
53+
def test_quicktest(self, ext_sr_4k: SR) -> None:
54+
ext_sr_4k.run_quicktest()
55+
56+
def test_vdi_is_not_open(self, vdi_on_ext_sr_4k: VDI) -> None:
57+
assert not vdi_is_open(vdi_on_ext_sr_4k)
58+
59+
def test_vdi_image_format(self, vdi_on_ext_sr_4k: VDI) -> None:
60+
fmt = vdi_on_ext_sr_4k.get_image_format()
61+
# feature-detect: if the SM doesn't report image-format, skip this check
62+
if not fmt:
63+
pytest.skip("SM does not report sm-config:image-format; skipping format check")
64+
assert fmt == QCOW2_IMAGE_FORMAT
65+
66+
@pytest.mark.small_vm # run with a small VM to test the features
67+
@pytest.mark.big_vm # and ideally with a big VM to test it scales
68+
def test_start_and_shutdown_VM(self, vm_on_ext_sr_4k: VM) -> None:
69+
vm = vm_on_ext_sr_4k
70+
vm.start()
71+
vm.wait_for_os_booted()
72+
vm.shutdown(verify=True)
73+
74+
@pytest.mark.small_vm
75+
@pytest.mark.big_vm
76+
def test_snapshot(self, vm_on_ext_sr_4k: VM) -> None:
77+
vm = vm_on_ext_sr_4k
78+
vm.start()
79+
try:
80+
vm.wait_for_os_booted()
81+
vm.test_snapshot_on_running_vm()
82+
finally:
83+
vm.shutdown(verify=True)
84+
85+
@pytest.mark.small_vm
86+
@pytest.mark.parametrize("vdi_op", ["snapshot", "clone"])
87+
def test_coalesce(self, storage_test_vm: VM, vdi_on_ext_sr_4k: VDI, vdi_op: CoalesceOperation,
88+
defer: Defer) -> None:
89+
coalesce_integrity(storage_test_vm, vdi_on_ext_sr_4k, vdi_op, defer)
90+
91+
@pytest.mark.small_vm
92+
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
93+
def test_xva_export_import(self, vm_on_ext_sr_4k: VM, compression: XVACompression, temp_large_dir: str,
94+
defer: Defer) -> None:
95+
xva_export_import(vm_on_ext_sr_4k, compression, temp_large_dir, defer)
96+
97+
@pytest.mark.small_vm
98+
def test_vdi_export_import(self, storage_test_vm: VM, ext_sr_4k: SR, temp_large_dir: str,
99+
defer: Defer) -> None:
100+
vdi_export_import(storage_test_vm, ext_sr_4k, QCOW2_IMAGE_FORMAT, temp_large_dir, defer)
101+
102+
@pytest.mark.small_vm
103+
@pytest.mark.disk_throughput_intensive
104+
def test_full_vdi_write(self, storage_test_vm: VM, vdi_on_ext_sr_4k: VDI, defer: Defer):
105+
full_vdi_write(storage_test_vm, vdi_on_ext_sr_4k, defer)
106+
107+
@pytest.mark.small_vm
108+
def test_invalid_vdi_size(self, ext_sr_4k: SR):
109+
with pytest.raises(SSHCommandFailed) as excinfo:
110+
ext_sr_4k.create_vdi(virtual_size=MAX_VDI_SIZE[QCOW2_IMAGE_FORMAT] + 1)
111+
assert 'VDI Invalid size' in excinfo.value.stdout
112+
113+
# *** tests with blktap activate failure (longer tests).
114+
115+
@pytest.mark.small_vm
116+
@pytest.mark.big_vm
117+
def test_blktap_activate_failure(self, vm_on_ext_sr_4k: VM) -> None:
118+
vm = vm_on_ext_sr_4k
119+
with FistPoint(vm.host, "blktap_activate_inject_failure"), pytest.raises(SSHCommandFailed):
120+
vm.start()
121+
vm.shutdown(force=True)
122+
123+
@pytest.mark.small_vm
124+
@pytest.mark.big_vm
125+
def test_resize(self, vm_on_ext_sr_4k: VM) -> None:
126+
vm = vm_on_ext_sr_4k
127+
vdi = VDI(vm.vdi_uuids()[0], host=vm.host)
128+
old_size = vdi.get_virtual_size()
129+
new_size = old_size + (1 * 1024 * 1024 * 1024) # Adding a 1GiB to size
130+
131+
vdi.resize(new_size)
132+
133+
assert vdi.get_virtual_size() == new_size
134+
135+
@pytest.mark.reboot
136+
@pytest.mark.small_vm
137+
def test_reboot(self, host: Host, ext_sr_4k: SR, vm_on_ext_sr_4k: VM) -> None:
138+
sr = ext_sr_4k
139+
vm = vm_on_ext_sr_4k
140+
host.reboot(verify=True)
141+
wait_for(sr.all_pbds_attached, "Wait for PBD attached")
142+
# start the VM as a way to check that the underlying SR is operational
143+
vm.start()
144+
vm.wait_for_os_booted()
145+
vm.shutdown(verify=True)
146+
147+
# *** End of tests with reboots
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
from lib.host import Host
4+
from lib.sr import SR
5+
from lib.vm import VM
6+
from tests.storage import cold_migration_then_come_back, live_storage_migration_then_come_back
7+
8+
# Requirements:
9+
# From --hosts parameter:
10+
# - host(A1): first XCP-ng host >= 8.3 with an additional unused native 4KiB disk for the EXT SR.
11+
# - hostB1: Master of a second pool. Any local SR.
12+
# From --vm parameter
13+
# - A VM to import to the EXT SR
14+
15+
@pytest.mark.small_vm
16+
@pytest.mark.big_vm
17+
class Test:
18+
def test_cold_crosspool_migration(
19+
self, host: Host, hostB1: Host, vm_on_ext_sr_4k: VM, local_sr_on_hostB1: SR
20+
) -> None:
21+
cold_migration_then_come_back(vm_on_ext_sr_4k, host, hostB1, local_sr_on_hostB1)
22+
23+
def test_live_crosspool_migration(
24+
self, host: Host, hostB1: Host, vm_on_ext_sr_4k: VM, local_sr_on_hostB1: SR
25+
) -> None:
26+
live_storage_migration_then_come_back(vm_on_ext_sr_4k, host, hostB1, local_sr_on_hostB1)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
from lib.host import Host
4+
from lib.sr import SR
5+
from lib.vm import VM
6+
from tests.storage import cold_migration_then_come_back, live_storage_migration_then_come_back
7+
8+
# Requirements:
9+
# From --hosts parameter:
10+
# - host(A1): first XCP-ng host >= 8.3 with an additional unused native 4KiB disk for the EXT SR.
11+
# - hostA2: Second member of the pool. Can have any local SR. No need to specify it on CLI.
12+
# From --vm parameter
13+
# - A VM to import to the EXT SR
14+
15+
@pytest.mark.small_vm
16+
@pytest.mark.big_vm
17+
class Test:
18+
def test_cold_intrapool_migration(
19+
self, host: Host, hostA2: Host, vm_on_ext_sr_4k: VM, local_sr_on_hostA2: SR
20+
) -> None:
21+
cold_migration_then_come_back(vm_on_ext_sr_4k, host, hostA2, local_sr_on_hostA2)
22+
23+
def test_live_intrapool_migration(
24+
self, host: Host, hostA2: Host, vm_on_ext_sr_4k: VM, local_sr_on_hostA2: SR
25+
) -> None:
26+
live_storage_migration_then_come_back(vm_on_ext_sr_4k, host, hostA2, local_sr_on_hostA2)

tests/storage/lvm/conftest.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from lib import config
88
from lib.host import Host
99
from lib.sr import SR
10-
from lib.vdi import VDI, ImageFormat
10+
from lib.vdi import QCOW2_IMAGE_FORMAT, VDI, ImageFormat
1111
from lib.vm import VM
1212

1313
from typing import Generator
@@ -39,3 +39,29 @@ def vm_on_lvm_sr(host: Host, lvm_sr: SR, vm_ref: str) -> Generator[VM, None, Non
3939
# teardown
4040
logging.info("<< Destroy VM")
4141
vm.destroy(verify=True)
42+
43+
@pytest.fixture(scope='package')
44+
def lvm_sr_4k(host: Host,
45+
unused_4k_disks: dict[Host, list[Host.BlockDeviceInfo]]) -> Generator[SR, None, None]:
46+
"""An LVM SR on a native 4KiB block device, using the QCOW2 image format."""
47+
sr_disk = unused_4k_disks[host][0].name
48+
sr = host.sr_create('lvm', "LVM-4K-local-SR-test",
49+
{'device': '/dev/' + sr_disk,
50+
'preferred-image-formats': QCOW2_IMAGE_FORMAT})
51+
yield sr
52+
# teardown
53+
sr.destroy()
54+
55+
@pytest.fixture(scope='module')
56+
def vdi_on_lvm_sr_4k(lvm_sr_4k: SR) -> Generator[VDI, None, None]:
57+
vdi = lvm_sr_4k.create_vdi('LVM-4K-local-VDI-test', virtual_size=config.volume_size)
58+
yield vdi
59+
vdi.destroy()
60+
61+
@pytest.fixture(scope='module')
62+
def vm_on_lvm_sr_4k(host: Host, lvm_sr_4k: SR, vm_ref: str) -> Generator[VM, None, None]:
63+
vm = host.import_vm(vm_ref, sr_uuid=lvm_sr_4k.uuid)
64+
yield vm
65+
# teardown
66+
logging.info("<< Destroy VM")
67+
vm.destroy(verify=True)

0 commit comments

Comments
 (0)