Skip to content

Commit 2019971

Browse files
committed
feat(coalesce): Add tests for coalesce
Add a vdi-type parameter to parametrize the vdi_type fixture, it defaults to vhd at the moment, will be used to run test on others vdi types, e.g. qcow2. The tests create a VDI, connect it to Dom0 then use the tapdev to write random data somewhere in it. It then compares the data of the VDI to the original data we have to see if it has changed. The test creates snapshot/clone before deleting them and waiting for the coalesce to have happened by observing sm-config:vhd-parent before checking the integrity of the data. Signed-off-by: Damien Thenot <[email protected]>
1 parent 66f25f3 commit 2019971

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed

conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ def pytest_addoption(parser):
9494
"4KiB blocksize to be formatted and used in storage tests. "
9595
"Set it to 'auto' to let the fixtures auto-detect available disks."
9696
)
97+
parser.addoption(
98+
"--image-format",
99+
action="append",
100+
default=[],
101+
help="Format of VDI to execute tests on."
102+
"Example: vhd,qcow2"
103+
)
97104

98105
def pytest_configure(config):
99106
global_config.ignore_ssh_banner = config.getoption('--ignore-ssh-banner')
@@ -106,6 +113,12 @@ def pytest_generate_tests(metafunc):
106113
vms = [None] # no --vm parameter does not mean skip the test, for us, it means use the default
107114
metafunc.parametrize("vm_ref", vms, indirect=True, scope="module")
108115

116+
if "image_format" in metafunc.fixturenames:
117+
image_format = metafunc.config.getoption("image_format")
118+
if len(image_format) == 0:
119+
image_format = ["vhd"] # Not giving image-format will default to doing tests on vhd
120+
metafunc.parametrize("image_format", image_format, scope="session")
121+
109122
def pytest_collection_modifyitems(items, config):
110123
# Automatically mark tests based on fixtures they require.
111124
# Check pytest.ini or pytest --markers for marker descriptions.

tests/storage/coalesce/__init__.py

Whitespace-only changes.

tests/storage/coalesce/conftest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
3+
import logging
4+
5+
from lib.vdi import VDI
6+
7+
MAX_LENGTH = 1 * 1024 * 1024 * 1024 # 1GiB
8+
9+
@pytest.fixture(scope="module")
10+
def vdi_on_local_sr(host, local_sr_on_hostA1, image_format):
11+
sr = local_sr_on_hostA1
12+
vdi = sr.create_vdi("testVDI", MAX_LENGTH, image_format=image_format)
13+
logging.info(">> Created VDI {} of type {}".format(vdi.uuid, image_format))
14+
15+
yield vdi
16+
17+
logging.info("<< Destroying VDI {}".format(vdi.uuid))
18+
vdi.destroy()
19+
20+
@pytest.fixture(scope="module")
21+
def vdi_with_vbd_on_dom0(host, vdi_on_local_sr):
22+
dom0 = host.get_dom0_vm()
23+
dom0.connect_vdi(vdi_on_local_sr)
24+
25+
yield vdi_on_local_sr
26+
27+
dom0.disconnect_vdi(vdi_on_local_sr)
28+
29+
@pytest.fixture(scope="function")
30+
def data_file_on_host(host):
31+
filename = "/root/data.bin"
32+
logging.info(f">> Creating data file {filename} on host")
33+
size = 1 * 1024 * 1024 # 1MiB
34+
assert size <= MAX_LENGTH, "Size of the data file bigger than the VDI size"
35+
36+
host.ssh(["dd", "if=/dev/urandom", f"of={filename}", f"bs={size}", "count=1"])
37+
38+
yield filename
39+
40+
logging.info("<< Deleting data file")
41+
host.ssh(["rm", filename])
42+
43+
@pytest.fixture(scope="module")
44+
def tapdev(local_sr_on_hostA1, vdi_with_vbd_on_dom0):
45+
"""
46+
A tapdev is a blockdevice allowing access to a VDI from the Dom0.
47+
48+
It is usually used to give access to the VDI to Qemu for emulating devices
49+
before PV driver are loaded in the guest.
50+
"""
51+
sr_uuid = local_sr_on_hostA1.uuid
52+
vdi_uuid = vdi_with_vbd_on_dom0.uuid
53+
yield f"/dev/sm/backend/{sr_uuid}/{vdi_uuid}"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import pytest
2+
3+
import logging
4+
5+
from .utils import compare_data, copy_data_to_tapdev, operation_on_vdi, wait_for_vdi_coalesce
6+
7+
from typing import TYPE_CHECKING
8+
9+
if TYPE_CHECKING:
10+
from lib.host import Host
11+
from lib.vdi import VDI
12+
13+
class Test:
14+
def test_write_data(self, host: "Host", tapdev: str, data_file_on_host: str):
15+
length = 1 * 1024 * 1024
16+
offset = 0
17+
18+
logging.info("Copying data to tapdev")
19+
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length)
20+
21+
logging.info("Comparing data to tapdev")
22+
assert compare_data(host, tapdev, data_file_on_host, offset, length)
23+
24+
@pytest.mark.parametrize("vdi_op", ["snapshot", "clone"])
25+
def test_coalesce(self, host: "Host", tapdev: str, vdi_with_vbd_on_dom0: "VDI", data_file_on_host: str, vdi_op):
26+
vdi = vdi_with_vbd_on_dom0
27+
vdi_uuid = vdi.uuid
28+
length = 1 * 1024 * 1024
29+
offset = 0
30+
31+
new_vdi = operation_on_vdi(host, vdi_uuid, vdi_op)
32+
33+
logging.info("Copying data to tapdev")
34+
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length)
35+
36+
logging.info(f"Removing VDI {vdi_op}")
37+
host.xe("vdi-destroy", {"uuid": new_vdi})
38+
39+
wait_for_vdi_coalesce(vdi)
40+
41+
logging.info("Comparing data to tapdev")
42+
assert compare_data(host, tapdev, data_file_on_host, offset, length)

tests/storage/coalesce/utils.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import logging
2+
3+
from lib.common import wait_for_not
4+
from lib.host import Host
5+
from lib.vdi import VDI
6+
7+
from typing import Literal
8+
9+
def wait_for_vdi_coalesce(vdi: VDI):
10+
wait_for_not(lambda: vdi.get_parent(), msg="Waiting for coalesce")
11+
logging.info("Coalesce done")
12+
13+
def copy_data_to_tapdev(host: Host, data_file: str, tapdev: str, offset: int, length: int):
14+
# if offset == 0:
15+
# off = "0"
16+
# else:
17+
# off = f"{offset}B" # Doesn't work with `dd` version of XCP-ng 8.3
18+
19+
bs = 1
20+
off = int(offset / bs)
21+
count = length / bs
22+
count += length % bs
23+
count = int(count)
24+
cmd = ["dd", f"if={data_file}", f"of={tapdev}", f"bs={bs}", f"seek={off}", f"count={count}"]
25+
host.ssh(cmd)
26+
27+
def get_data(host: Host, file: str, offset: int, length: int, checksum: bool = False) -> str:
28+
cmd = ["xxd", "-p", "-seek", str(offset), "-len", str(length), file]
29+
if checksum:
30+
cmd = cmd + ["|", "sha256sum"]
31+
return host.ssh(cmd)
32+
33+
def get_hashed_data(host: Host, file: str, offset: int, length: int):
34+
return get_data(host, file, offset, length, True).split()[0]
35+
36+
def operation_on_vdi(host: Host, vdi_uuid: str, vdi_op: Literal["snapshot", "clone"]) -> str:
37+
new_vdi = host.xe(f"vdi-{vdi_op}", {"uuid": vdi_uuid})
38+
logging.info(f"{vdi_op.capitalize()} VDI {vdi_uuid}: {new_vdi}")
39+
return new_vdi
40+
41+
def compare_data(host: Host, tapdev: str, data_file: str, offset: int, length: int) -> bool:
42+
logging.info("Getting data from VDI and file")
43+
vdi_checksum = get_hashed_data(host, tapdev, offset, length)
44+
file_checksum = get_hashed_data(host, data_file, 0, length)
45+
logging.info(f"VDI: {vdi_checksum}")
46+
logging.info(f"FILE: {file_checksum}")
47+
48+
return vdi_checksum == file_checksum

0 commit comments

Comments
 (0)