From b05c43056d2f76c435eb10295e2af1d2d896f766 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Wed, 24 Aug 2022 09:56:50 +0200 Subject: [PATCH] ovirt-img: command testing Add the required testing infrastructure to test ovirt-img command with a local setup. Engine configuration needs to be set in a configuration file, and then the tests will read this configuration and run normally through tox. The test are run through a new tox environment, namely client (as it tests commands contained in the client folder): tox -e client It will read the configuration in the default location run the tests in the test/client folder. Configuration location can be set through an env variable: CLIENT_TEST_CONF=/path/to/test.conf tox -e client Currently, it only tests upload and download commands, with different formats and comparing resulting images. Example: $ CLIENT_TEST_CONF=~/.config/ovirt-img.conf tox -e client -- --log-cli-level=debug collected 2 items test/client/upload_download_test.py::test_upload_download[raw] Formatting '/var/tmp/imageio-tests/test_upload_download_raw_0/image.raw', fmt=raw size=10737418240 12:26:37,374 INFO (MainThread) [test] Upload raw image to SD nfs-01 [ 100% ] 10.00 GiB, 19.08 s, 536.59 MiB/s | upload completed 12:26:56,595 INFO (MainThread) [test] Download image d27c0e2f-35d1-4106-96e6-215022e022bf [ 100% ] 10.00 GiB, 2.47 s, 4.05 GiB/s | download completed 12:26:59,202 INFO (MainThread) [test] Comparing images 12:26:59,539 INFO (MainThread) [test] Upload raw image to SD iscsi-01 [ 100% ] 10.00 GiB, 24.73 s, 414.15 MiB/s | upload completed 12:27:24,405 INFO (MainThread) [test] Download image a3b48896-33bc-4f62-b3af-159b56686a29 [ 100% ] 10.00 GiB, 4.73 s, 2.11 GiB/s | download completed 12:27:29,277 INFO (MainThread) [test] Comparing images PASSED test/client/upload_download_test.py::test_upload_download[qcow2] Formatting '/var/tmp/imageio-tests/test_upload_download_qcow2_0/image.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=10737418240 lazy_refcounts=off refcount_bits=16 12:27:30,103 INFO (MainThread) [test] Upload qcow2 image to SD nfs-01 [ 100% ] 10.00 GiB, 11.92 s, 858.81 MiB/s | upload completed 12:27:42,175 INFO (MainThread) [test] Download image 9cb8da7a-6169-4020-8233-9f20aeeef56e [ 100% ] 10.00 GiB, 2.44 s, 4.09 GiB/s | download completed 12:27:44,765 INFO (MainThread) [test] Comparing images 12:27:45,956 INFO (MainThread) [test] Upload qcow2 image to SD iscsi-01 [ 100% ] 10.00 GiB, 29.13 s, 351.49 MiB/s | upload completed 12:28:15,236 INFO (MainThread) [test] Download image ae41f54d-0345-40ac-bf40-197430c671f5 [ 100% ] 10.00 GiB, 2.92 s, 3.43 GiB/s | download completed 12:28:18,290 INFO (MainThread) [test] Comparing images PASSED Fixes: #124 Signed-off-by: Albert Esteve --- test/client/client_tests.conf | 22 ++++++ test/client/conftest.py | 35 ++++++++++ test/client/upload_download_test.py | 103 ++++++++++++++++++++++++++++ tox.ini | 17 ++++- 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 test/client/client_tests.conf create mode 100644 test/client/conftest.py create mode 100644 test/client/upload_download_test.py diff --git a/test/client/client_tests.conf b/test/client/client_tests.conf new file mode 100644 index 00000000..b0a4ede9 --- /dev/null +++ b/test/client/client_tests.conf @@ -0,0 +1,22 @@ +# Example configuration for client tests. +# Adapt these settings to your local engine setup and then run: +# tox -e client +# This configuration can be copied to a different location and used as +# CLIENT_TEST_CONF=/path/to/test.conf tox -e client + +# Common parameters must be all kept, and are used by all tests +common: + engine_url: https://engine.com + username: admin@internal + password: password + cafile: /path/to/cert.pem + +tests: + # Image upload and download test. Different formats are tested. + # Image and disk contents are compared. + upload-download: + # Set local storage domain names to test. + # At least one block and one file storage domain shall be tested. + storage-domains: + - nfs1 + - scsi1 diff --git a/test/client/conftest.py b/test/client/conftest.py new file mode 100644 index 00000000..4fc57b14 --- /dev/null +++ b/test/client/conftest.py @@ -0,0 +1,35 @@ +# ovirt-imageio +# Copyright (C) 2022 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +import logging +import os +import yaml + +import pytest + + +log = logging.getLogger("test") + + +@pytest.fixture +def config(tmpdir): + with open(os.environ['CLIENT_TEST_CONF'], encoding='utf-8') as fstream: + try: + conf = yaml.safe_load(fstream) + except yaml.YAMLError as exc: + log.error("Invalid YAML format: %s", exc) + raise + + conf_file = os.path.join(tmpdir, 'ovirt-img.conf') + os.environ['XDG_CONFIG_HOME'] = str(tmpdir) + with open(conf_file, "w+", encoding="utf-8") as fstream: + fstream.write("[test]\n") + for k, v in conf["common"].items(): + fstream.write(f"{k} = {v}\n") + + yield conf diff --git a/test/client/upload_download_test.py b/test/client/upload_download_test.py new file mode 100644 index 00000000..e1bfbf5d --- /dev/null +++ b/test/client/upload_download_test.py @@ -0,0 +1,103 @@ +# ovirt-imageio +# Copyright (C) 2022 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +import contextlib +import logging +import os +import uuid +import subprocess +import sys + +import pytest +import ovirtsdk4 as sdk + +from ovirt_imageio._internal import qemu_img + + +OS_VERSION = 'fedora-36' + +log = logging.getLogger("test") + + +class ClientError(Exception): + pass + + +def create_disk(path, fmt, os_version=OS_VERSION): + env = os.environ.copy() + env['LIBGUESTFS_BACKEND'] = 'direct' + cmd = ['virt-builder', os_version, '--format', fmt, '-o', path] + subprocess.check_call(cmd, env=env) + + +def run_upload_disk(storage_domain, image, disk_id=None, log_level=None): + # Make sure it runs with the same tox environment executable + cmd = [sys.executable, './ovirt-img', 'upload-disk', '-c', 'test'] + cmd.extend(['-s', storage_domain]) + if log_level: + cmd.extend(['--log-level', log_level]) + if disk_id: + cmd.extend(['--disk-id', disk_id]) + cmd.append(image) + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as exc: + raise ClientError(f'Client Error: {exc}') from exc + + +def run_download_disk(disk_id, image, log_level=None): + # Make sure it runs with the same tox environment executable + cmd = [sys.executable, './ovirt-img', 'download-disk', '-c', 'test'] + if log_level: + cmd.extend(['--log-level', log_level]) + cmd.extend([disk_id, image]) + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as exc: + raise ClientError(f'Client Error: {exc}') from exc + + +def remove_disk(conf, sd_name, disk_id): + connection = sdk.Connection( + url=f'{conf["engine_url"]}/ovirt-engine/api', + username=conf["username"], + password=conf["password"], + ca_file=conf["cafile"] + ) + with contextlib.closing(connection): + sd_service = connection.system_service().storage_domains_service() + found_sd = sd_service.list(search=f'name={sd_name}') + if not found_sd: + raise RuntimeError(f"Couldn't find storage domain {sd_name}") + + sd = found_sd[0] + sd_service = sd_service.storage_domain_service(sd.id) + sd_service.disks_service().disk_service(disk_id).remove() + + +@pytest.mark.parametrize("fmt", ["raw", "qcow2"]) +def test_upload_download(config, tmpdir, fmt): + image = os.path.join(tmpdir, f'image.{fmt}') + create_disk(image, fmt) + test_config = config["tests"]["upload-download"] + for sd_name in test_config.get("storage-domains", []): + disk_id = str(uuid.uuid4()) + try: + log.info("Upload %s image to SD %s", fmt, sd_name) + run_upload_disk(sd_name, image, disk_id) + down_img = os.path.join(tmpdir, f'downloaded.{fmt}') + log.info("Download image %s", disk_id) + run_download_disk(disk_id, down_img) + log.info("Comparing images") + qemu_img.compare(image, down_img) + except ClientError as exc: + log.error("%s", exc) + # Skip disk cleanup if client failed + return + finally: + remove_disk(config["common"], sd_name, disk_id) diff --git a/tox.ini b/tox.ini index ece71b38..e20098aa 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = flake8,test-{py36,py38,py39,py310},bench-{py36,py38,py39,py310} +envlist = flake8,test-{py36,py38,py39,py310},bench-{py36,py38,py39,py310},client skip_missing_interpreters = True whitelist_externals = ip @@ -25,8 +25,19 @@ commands_pre = /usr/bin/python3 setup.py build_ext --build-lib . python -c 'from test import testutil; print("ipv6 supported: %s" % testutil.ipv6_enabled())' commands = - test: pytest -m 'not benchmark' --cov=ovirt_imageio --durations=10 {posargs} - bench: pytest -m 'benchmark' -vs {posargs} + test: pytest -m 'not benchmark' --ignore=test/client --cov=ovirt_imageio --durations=10 {posargs} + bench: pytest -m 'benchmark' -vs --ignore=test/client {posargs} + +[testenv:client] +setenv = + CLIENT_TEST_CONF = {env:CLIENT_TEST_CONF:{toxinidir}/test/client/client_tests.conf} +deps = + pytest + pytest-timeout + pyyaml + ovirt-engine-sdk-python +commands = + pytest -vs --timeout 200 {posargs} test/client [testenv:flake8] sitepackages = False