Skip to content

Commit f398d35

Browse files
committed
feat(coalesce): Add tests for coalesce
Signed-off-by: Damien Thenot <[email protected]>
1 parent cbfcd2e commit f398d35

File tree

4 files changed

+178
-5
lines changed

4 files changed

+178
-5
lines changed

lib/host.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shlex
55
import tempfile
66
import uuid
7+
from typing import Optional
78

89
from packaging import version
910

@@ -606,3 +607,16 @@ def enable_hsts_header(self):
606607
def disable_hsts_header(self):
607608
self.ssh(['rm', '-f', f'{XAPI_CONF_DIR}/00-XCP-ng-tests-enable-hsts-header.conf'])
608609
self.restart_toolstack(verify=True)
610+
611+
def get_dom0_uuid(self):
612+
output = self.ssh(["grep", "-e", "\"CONTROL_DOMAIN_UUID=\"", "/etc/xensource-inventory"])
613+
return output.split("=")[1].replace("'", "")
614+
615+
def get_vdi_sr_uuid(self, vdi_uuid) -> Optional[SR]:
616+
sr_uuid = self.xe("vdi-param-get",
617+
{"param-name": "sr-uuid",
618+
"uuid": vdi_uuid,
619+
})
620+
if sr_uuid is None:
621+
return None
622+
return SR(sr_uuid, self.pool)

lib/vdi.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import Optional
23

34
from lib.common import _param_add, _param_clear, _param_get, _param_remove, _param_set
45

@@ -9,11 +10,7 @@ def __init__(self, uuid, *, host=None, sr=None):
910
self.uuid = uuid
1011
# TODO: use a different approach when migration is possible
1112
if sr is None:
12-
sr_uuid = host.get_vdi_sr_uuid(uuid)
13-
# avoid circular import
14-
# FIXME should get it from Host instead
15-
from lib.sr import SR
16-
self.sr = SR(sr_uuid, host.pool)
13+
self.sr = host.get_vdi_sr_uuid(uuid)
1714
else:
1815
self.sr = sr
1916

@@ -34,6 +31,9 @@ def readonly(self):
3431
def __str__(self):
3532
return f"VDI {self.uuid} on SR {self.sr.uuid}"
3633

34+
def get_parent(self) -> Optional[str]:
35+
return self.param_get("sm-config", key="vhd-parent", accept_unknown_key=True)
36+
3737
def param_get(self, param_name, key=None, accept_unknown_key=False):
3838
return _param_get(self.sr.pool.master, self.xe_prefix, self.uuid,
3939
param_name, key, accept_unknown_key)

tests/storage/coalesce/conftest.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import pytest
2+
import logging
3+
4+
from lib.vdi import VDI
5+
6+
MAX_LENGTH = 1 * 1024 * 1024 * 1024 # 1GiB
7+
8+
@pytest.fixture(scope="package", params=["qcow2", "vhd"])
9+
def vdi_type(request):
10+
yield request.param
11+
12+
@pytest.fixture(scope="module")
13+
def vdi_on_local_sr(host, local_sr_on_hostA1, vdi_type):
14+
sr_uuid = local_sr_on_hostA1.uuid
15+
vdi_uuid = host.xe("vdi-create",
16+
{"sr-uuid": sr_uuid,
17+
"name-label": "testVDI",
18+
"virtual-size": str(MAX_LENGTH),
19+
"sm-config:type": vdi_type,
20+
})
21+
logging.info(">> Created VDI {} of type {}".format(vdi_uuid, vdi_type))
22+
23+
vdi = VDI(vdi_uuid, host=host)
24+
25+
yield vdi
26+
27+
logging.info("<< Destroying VDI {}".format(vdi_uuid))
28+
host.xe("vdi-destroy", {"uuid": vdi_uuid})
29+
30+
@pytest.fixture(scope="module")
31+
def vdi_with_vbd_on_dom0(host, vdi_on_local_sr):
32+
vdi_uuid = vdi_on_local_sr.uuid
33+
dom0_uuid = host.get_dom0_uuid()
34+
logging.info(f">> Plugging VDI {vdi_uuid} on Dom0")
35+
vbd_uuid = host.xe("vbd-create",
36+
{"vdi-uuid": vdi_uuid,
37+
"vm-uuid": dom0_uuid,
38+
"device": "autodetect",
39+
})
40+
host.xe("vbd-plug", {"uuid": vbd_uuid})
41+
42+
yield vdi_on_local_sr
43+
44+
logging.info(f"<< Unplugging VDI {vdi_uuid} from Dom0")
45+
host.xe("vbd-unplug", {"uuid": vbd_uuid})
46+
host.xe("vbd-destroy", {"uuid": vbd_uuid})
47+
48+
@pytest.fixture(scope="class")
49+
def data_file_on_host(host):
50+
filename = "/root/data.bin"
51+
logging.info(f">> Creating data file {filename} on host")
52+
size = 1 * 1024 * 1024 # 1MiB
53+
assert size <= MAX_LENGTH, "Size of the data file bigger than the VDI size"
54+
55+
host.ssh(["dd", "if=/dev/urandom", f"of={filename}", f"bs={size}", "count=1"])
56+
57+
yield filename
58+
59+
logging.info("<< Deleting data file")
60+
host.ssh(["rm", filename])
61+
62+
@pytest.fixture(scope="module")
63+
def tapdev(local_sr_on_hostA1, vdi_with_vbd_on_dom0):
64+
sr_uuid = local_sr_on_hostA1.uuid
65+
vdi_uuid = vdi_with_vbd_on_dom0.uuid
66+
yield f"/dev/sm/backend/{sr_uuid}/{vdi_uuid}"
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import logging
2+
from typing import Optional
3+
import hashlib
4+
5+
import time
6+
7+
from lib.host import Host
8+
9+
def copy_data_to_tapdev(host: Host, data_file: str, tapdev: str, offset: int, length: int):
10+
"""
11+
if offset == 0:
12+
off = "0"
13+
else:
14+
off = f"{offset}B" # Doesn't work with `dd` version of XCP-ng 8.3
15+
"""
16+
bs = 1
17+
off = int(offset / bs)
18+
count = length / bs
19+
count += length % bs
20+
count = int(count)
21+
cmd = ["dd", f"if={data_file}", f"of={tapdev}", f"bs={bs}", f"seek={off}", f"count={count}"]
22+
host.ssh(cmd)
23+
24+
def get_data(host: Host, file: str, offset: int, length: int):
25+
return host.ssh(["xxd", "-p", "-seek", str(offset), "-len", str(length), file])
26+
27+
def get_hashed_data(host: Host, file: str, offset: int, length: int):
28+
return hashlib.md5(get_data(host, file, offset, length).encode()).hexdigest()
29+
30+
def snapshot_vdi(host: Host, vdi_uuid: str):
31+
vdi_snap = host.xe("vdi-snapshot", {"uuid": vdi_uuid})
32+
logging.info(f"Snapshot VDI {vdi_uuid}: {vdi_snap}")
33+
return vdi_snap
34+
35+
def compare_data(host: Host, tapdev: str, data_file: str, offset: int, length: int) -> bool:
36+
logging.info("Getting data from VDI and file")
37+
vdi_checksum = get_hashed_data(host, tapdev, offset, length)
38+
file_checksum = get_hashed_data(host, data_file, 0, length)
39+
logging.info(f"VDI: {vdi_checksum}")
40+
logging.info(f"FILE: {file_checksum}")
41+
42+
return vdi_checksum == file_checksum
43+
44+
def test_write_data(host, tapdev, data_file_on_host):
45+
length = 1 * 1024 * 1024
46+
offset = 0
47+
48+
logging.info("Copying data to tapdev")
49+
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length)
50+
51+
assert compare_data(host, tapdev, data_file_on_host, offset, length)
52+
53+
def test_coalesce(host, tapdev, vdi_with_vbd_on_dom0, data_file_on_host):
54+
vdi = vdi_with_vbd_on_dom0
55+
vdi_uuid = vdi.uuid
56+
length = 1 * 1024 * 1024
57+
offset = 0
58+
59+
vdi_snap = snapshot_vdi(host, vdi_uuid)
60+
61+
logging.info("Copying data to tapdev")
62+
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length)
63+
64+
logging.info("Removing VDI snapshot")
65+
host.xe("vdi-destroy", {"uuid": vdi_snap})
66+
67+
logging.info("Waiting for coalesce")
68+
while vdi.get_parent() is not None:
69+
time.sleep(1)
70+
logging.info("Coalesce done")
71+
72+
assert compare_data(host, tapdev, data_file_on_host, offset, length)
73+
74+
def test_clone_coalesce(host, tapdev, vdi_with_vbd_on_dom0, data_file_on_host):
75+
vdi = vdi_with_vbd_on_dom0
76+
vdi_uuid = vdi.uuid
77+
length = 1 * 1024 * 1024
78+
offset = 0
79+
80+
clone_uuid = host.xe("vdi-clone", {"uuid": vdi_uuid})
81+
logging.info(f"Clone VDI {vdi_uuid}: {clone_uuid}")
82+
83+
logging.info("Copying data to tapdev")
84+
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length)
85+
86+
host.xe("vdi-destroy", {"uuid": clone_uuid})
87+
88+
logging.info("Waiting for coalesce")
89+
while vdi.get_parent() is not None:
90+
time.sleep(1)
91+
logging.info("Coalesce done")
92+
93+
assert compare_data(host, tapdev, data_file_on_host, offset, length)

0 commit comments

Comments
 (0)