-
Notifications
You must be signed in to change notification settings - Fork 5
feat(coalesce): Add tests for coalesce #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,13 @@ def pytest_addoption(parser): | |
"4KiB blocksize to be formatted and used in storage tests. " | ||
"Set it to 'auto' to let the fixtures auto-detect available disks." | ||
) | ||
parser.addoption( | ||
"--image-format", | ||
action="append", | ||
default=[], | ||
help="Format of VDI to execute tests on." | ||
"Example: vhd,qcow2" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Making this a CLI parameter means you're deporting the test job definition outside pytest. When rishi wanted to do the same for thin vs thick in the context of XOSTOR tests, we asked him to rework the tests so they actually test both formats. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's to allow to test only one type by hand, the objectives for tests is to keep the default which at the moment is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pytest already has test selection mechanisms:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As long as the parameter is not necessary in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The parameter default to VHD when not given meaning it's not needed to change jobs.py. But I will need to add the new coalesce tests in jobs.py either way and it's the only test at the moment that will use |
||
) | ||
|
||
def pytest_configure(config): | ||
global_config.ignore_ssh_banner = config.getoption('--ignore-ssh-banner') | ||
|
@@ -87,6 +94,12 @@ def pytest_generate_tests(metafunc): | |
vms = [None] # no --vm parameter does not mean skip the test, for us, it means use the default | ||
metafunc.parametrize("vm_ref", vms, indirect=True, scope="module") | ||
|
||
if "image_format" in metafunc.fixturenames: | ||
image_format = metafunc.config.getoption("image_format") | ||
if len(image_format) == 0: | ||
image_format = ["vhd"] # Not giving image-format will default to doing tests on vhd | ||
metafunc.parametrize("image_format", image_format, scope="session") | ||
|
||
def pytest_collection_modifyitems(items, config): | ||
# Automatically mark tests based on fixtures they require. | ||
# Check pytest.ini or pytest --markers for marker descriptions. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import pytest | ||
import logging | ||
|
||
from lib.vdi import VDI | ||
|
||
MAX_LENGTH = 1 * 1024 * 1024 * 1024 # 1GiB | ||
|
||
@pytest.fixture(scope="module") | ||
def vdi_on_local_sr(host, local_sr_on_hostA1, image_format): | ||
sr_uuid = local_sr_on_hostA1.uuid | ||
vdi_uuid = host.xe("vdi-create", | ||
{"sr-uuid": sr_uuid, | ||
"name-label": "testVDI", | ||
"virtual-size": str(MAX_LENGTH), | ||
"sm-config:type": image_format, | ||
}) | ||
logging.info(">> Created VDI {} of type {}".format(vdi_uuid, image_format)) | ||
|
||
vdi = VDI(vdi_uuid, host=host) | ||
|
||
yield vdi | ||
|
||
logging.info("<< Destroying VDI {}".format(vdi_uuid)) | ||
host.xe("vdi-destroy", {"uuid": vdi_uuid}) | ||
|
||
@pytest.fixture(scope="module") | ||
def vdi_with_vbd_on_dom0(host, vdi_on_local_sr): | ||
vdi_uuid = vdi_on_local_sr.uuid | ||
dom0_uuid = host.get_dom0_uuid() | ||
logging.info(f">> Plugging VDI {vdi_uuid} on Dom0") | ||
vbd_uuid = host.xe("vbd-create", | ||
{"vdi-uuid": vdi_uuid, | ||
"vm-uuid": dom0_uuid, | ||
"device": "autodetect", | ||
}) | ||
host.xe("vbd-plug", {"uuid": vbd_uuid}) | ||
|
||
yield vdi_on_local_sr | ||
|
||
logging.info(f"<< Unplugging VDI {vdi_uuid} from Dom0") | ||
host.xe("vbd-unplug", {"uuid": vbd_uuid}) | ||
host.xe("vbd-destroy", {"uuid": vbd_uuid}) | ||
|
||
@pytest.fixture(scope="class") | ||
def data_file_on_host(host): | ||
filename = "/root/data.bin" | ||
logging.info(f">> Creating data file {filename} on host") | ||
size = 1 * 1024 * 1024 # 1MiB | ||
assert size <= MAX_LENGTH, "Size of the data file bigger than the VDI size" | ||
|
||
host.ssh(["dd", "if=/dev/urandom", f"of={filename}", f"bs={size}", "count=1"]) | ||
|
||
yield filename | ||
|
||
logging.info("<< Deleting data file") | ||
host.ssh(["rm", filename]) | ||
|
||
@pytest.fixture(scope="module") | ||
def tapdev(local_sr_on_hostA1, vdi_with_vbd_on_dom0): | ||
sr_uuid = local_sr_on_hostA1.uuid | ||
vdi_uuid = vdi_with_vbd_on_dom0.uuid | ||
yield f"/dev/sm/backend/{sr_uuid}/{vdi_uuid}" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import logging | ||
import time | ||
|
||
from lib.host import Host | ||
|
||
def copy_data_to_tapdev(host: Host, data_file: str, tapdev: str, offset: int, length: int): | ||
""" | ||
if offset == 0: | ||
off = "0" | ||
else: | ||
off = f"{offset}B" # Doesn't work with `dd` version of XCP-ng 8.3 | ||
""" | ||
bs = 1 | ||
off = int(offset / bs) | ||
count = length / bs | ||
count += length % bs | ||
count = int(count) | ||
cmd = ["dd", f"if={data_file}", f"of={tapdev}", f"bs={bs}", f"seek={off}", f"count={count}"] | ||
host.ssh(cmd) | ||
|
||
def get_data(host: Host, file: str, offset: int, length: int, checksum: bool = False) -> str: | ||
cmd = ["xxd", "-p", "-seek", str(offset), "-len", str(length), file] | ||
if checksum: | ||
cmd = cmd + ["|", "sha256sum"] | ||
return host.ssh(cmd) | ||
|
||
def get_hashed_data(host: Host, file: str, offset: int, length: int): | ||
return get_data(host, file, offset, length, True).split()[0] | ||
|
||
def snapshot_vdi(host: Host, vdi_uuid: str): | ||
vdi_snap = host.xe("vdi-snapshot", {"uuid": vdi_uuid}) | ||
logging.info(f"Snapshot VDI {vdi_uuid}: {vdi_snap}") | ||
return vdi_snap | ||
|
||
def compare_data(host: Host, tapdev: str, data_file: str, offset: int, length: int) -> bool: | ||
logging.info("Getting data from VDI and file") | ||
vdi_checksum = get_hashed_data(host, tapdev, offset, length) | ||
file_checksum = get_hashed_data(host, data_file, 0, length) | ||
logging.info(f"VDI: {vdi_checksum}") | ||
logging.info(f"FILE: {file_checksum}") | ||
|
||
return vdi_checksum == file_checksum | ||
|
||
def test_write_data(host, tapdev, data_file_on_host): | ||
length = 1 * 1024 * 1024 | ||
offset = 0 | ||
|
||
logging.info("Copying data to tapdev") | ||
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length) | ||
|
||
assert compare_data(host, tapdev, data_file_on_host, offset, length) | ||
|
||
def test_coalesce(host, tapdev, vdi_with_vbd_on_dom0, data_file_on_host): | ||
vdi = vdi_with_vbd_on_dom0 | ||
vdi_uuid = vdi.uuid | ||
length = 1 * 1024 * 1024 | ||
offset = 0 | ||
|
||
vdi_snap = snapshot_vdi(host, vdi_uuid) | ||
|
||
logging.info("Copying data to tapdev") | ||
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length) | ||
|
||
logging.info("Removing VDI snapshot") | ||
host.xe("vdi-destroy", {"uuid": vdi_snap}) | ||
|
||
logging.info("Waiting for coalesce") | ||
while vdi.get_parent() is not None: | ||
time.sleep(1) | ||
logging.info("Coalesce done") | ||
|
||
assert compare_data(host, tapdev, data_file_on_host, offset, length) | ||
|
||
def test_clone_coalesce(host, tapdev, vdi_with_vbd_on_dom0, data_file_on_host): | ||
vdi = vdi_with_vbd_on_dom0 | ||
vdi_uuid = vdi.uuid | ||
length = 1 * 1024 * 1024 | ||
offset = 0 | ||
|
||
clone_uuid = host.xe("vdi-clone", {"uuid": vdi_uuid}) | ||
logging.info(f"Clone VDI {vdi_uuid}: {clone_uuid}") | ||
|
||
logging.info("Copying data to tapdev") | ||
copy_data_to_tapdev(host, data_file_on_host, tapdev, offset, length) | ||
|
||
host.xe("vdi-destroy", {"uuid": clone_uuid}) | ||
|
||
logging.info("Waiting for coalesce") | ||
while vdi.get_parent() is not None: | ||
time.sleep(1) | ||
logging.info("Coalesce done") | ||
|
||
assert compare_data(host, tapdev, data_file_on_host, offset, length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you work in the context of storage everyday, maybe it's a clear name, but otherwise it might be too generic a name. Both
image
andformat
can be many things.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's why I added
vdi-image-format
as suggestion.But I wonder if we should not use the naming
volume-image-format
instead.No preference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And
vdi-type
looked consistent withsm
's terminology 🤔There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really consistent. In this example here, type is completly different.
vdi_type
stored in the sm-config attribute. Here you can have the VHD/AIO value. However there are weird situations like:sm-config (MRO) : type: raw; vdi_type: aio
.type
is completely ambiguous depending on what you are talking about and the context in which it is used.volume
instead). Andimage-format
is used.