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