summaryrefslogtreecommitdiffstats
path: root/tests/packaging
diff options
context:
space:
mode:
Diffstat (limited to 'tests/packaging')
-rw-r--r--tests/packaging/README.rst87
-rw-r--r--tests/packaging/conftest.py10
-rw-r--r--tests/packaging/test_packaging.py494
3 files changed, 591 insertions, 0 deletions
diff --git a/tests/packaging/README.rst b/tests/packaging/README.rst
new file mode 100644
index 0000000..997f666
--- /dev/null
+++ b/tests/packaging/README.rst
@@ -0,0 +1,87 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+Packaging tests
+===============
+
+Packaging tests used pytest, docker and each directory with subdirectory *.packaging*
+is called as *component*.
+
+Run tests for all components:
+
+.. code-block::
+
+ pytest -r fEsxX tests/packaging
+
+List all components:
+
+.. code-block::
+
+ pytest tests/packaging --collect-only
+
+Run test for specific component (*doc/.packaging*):
+
+.. code-block::
+
+ pytest -r fEsxX tests/packaging -k test_collect[debian_10-doc/.packaging]
+
+.. note::
+
+ For debug add argument :code:`-s`.
+
+daemon/.packaging component
+---------------------------
+
+This is special component that is used by all others components.
+For each distribution and version are created two docker images with this component.
+One with building dependencies and one for running dependencies.
+*Build docker image* is tagged as :code:`kr-packaging-tests-<distro><version>-build`
+and *Run docker image* is tagged as :code:`kr-packaging-tests-<distro><version>-run`.
+
+Others components
+-----------------
+
+All others components are based on *daemon/.packaging* component (docker image).
+When component needs new building dependencies, new running dependencies
+or some scripts that change build or run phase (see `File structure of each component`_),
+new docker image is created.
+*Build docker image* is tagged as :code:`kr-packaging-tests-<distro><version>-<component>-build`
+and *Run docker image* is tagged as :code:`kr-packaging-tests-<distro><version>-<component>-run`.
+
+File structure of each component
+------------------------------------
+
+* <distro>
+ * <version>
+ * builddeps - list of build dependencies
+ * rundeps - list of runtime dependencies
+ * pre-build.sh - script called before build phase
+ * post-build.sh - script called after build phase
+ * pre-run.sh - script called before run phase
+ * post-run.sh - script called after run phase
+ * install.sh and build.sh - scripts to rewrite standard commands for building and installing knot-resolvers
+ * pre-test.sh - script called immediately before testing
+* test.config or test.sh - kresd config test or shell script (one of them must exists)
+
+Commands order to create docker image
+-------------------------------------
+
+For *build docker image*:
+
+#. run pre-build.sh
+#. install packages specified in the file *builddeps*
+#. run build.sh
+#. run install.sh
+#. run post-build.sh
+
+For *run docker image*:
+
+#. run pre-run.sh
+#. install packages specified in the file *rundeps*
+#. run pre-test.sh
+#. run test (:code:`kresd -c test.config` or :code:`test.sh`)
+#. run post-build.sh
+
+
+.. note::
+
+ knot-resolver builded in *build docker image* is automatically moved to *run docker image*.
diff --git a/tests/packaging/conftest.py b/tests/packaging/conftest.py
new file mode 100644
index 0000000..7279c15
--- /dev/null
+++ b/tests/packaging/conftest.py
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import pytest
+import os
+
+
+def pytest_configure():
+ pytest.KR_PYTESTS_DIR = os.path.dirname(os.path.realpath(__file__))
+ pytest.KR_ROOT_DIR = os.path.join(pytest.KR_PYTESTS_DIR, "..", "..")
+ pytest.KR_PREFIX = "kr-packaging-tests-"
diff --git a/tests/packaging/test_packaging.py b/tests/packaging/test_packaging.py
new file mode 100644
index 0000000..1a9bc41
--- /dev/null
+++ b/tests/packaging/test_packaging.py
@@ -0,0 +1,494 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import os
+import pytest
+import docker
+import logging
+from pathlib import Path
+from abc import ABC, abstractmethod
+
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+client = docker.from_env()
+
+
+class DockerCmdError(Exception):
+ """ Raised when shell command in Docker container failed """
+ pass
+
+
+class ContainerHandler():
+ def __init__(self, image):
+ self.img_id = image
+ self.container = None
+
+ def run(self):
+ self.container = client.containers.run(self.img_id, network_mode='host',
+ tty=True, detach=True)
+ logger.info('Run container ID={}'.format(self.container))
+
+ def stop(self):
+ self.container.kill()
+
+ def exec_cmd(self, cmd, workdir):
+ # workaround: When exec_run is called in GitLab CI/CD workdir argument doesn't work.
+ inter_cmd = ''
+ if workdir is not None:
+ inter_cmd = 'cd {}; '.format(workdir)
+
+ rcode, out = self.container.exec_run('/bin/sh -c \'' + inter_cmd + cmd + '\'')
+ if rcode != 0:
+ raise DockerCmdError(rcode, out)
+
+ def getFiles(self, output, path):
+ strm, stat = self.container.get_archive(path)
+ with open(output, 'wb') as ofile:
+ for data in strm:
+ ofile.write(data)
+
+
+class DockerImages(ABC):
+ def __init__(self, version):
+ self.version = version
+ self.module = None
+ self.distro = None
+ self.build_id = None
+ self.run_id = None
+
+ @abstractmethod
+ def cmd_pkgs_install(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def cmd_kresd_install(self):
+ raise NotImplementedError
+
+ @abstractmethod
+ def cmd_kresd_build(self):
+ raise NotImplementedError
+
+ def readDependencies(self, deps_file):
+ """Read dependencies from file"""
+ listf = None
+ try:
+ with open(deps_file, 'r') as f:
+ listf = f.read().splitlines()
+ except FileNotFoundError:
+ pass
+
+ return listf
+
+ def __genDockerFile(self, path, from_image=None):
+ """Generate Dockerfile for build image"""
+ if self.module is None:
+ raise AttributeError
+
+ if from_image is None:
+ if os.path.isfile(os.path.join(self.module, self.distro, 'docker-image-name')):
+ with open(os.path.join(self.module, self.distro, 'docker-image-name')) as f:
+ from_image = f.read()
+ else:
+ from_image = '{0}:{1}'.format(self.distro, self.version)
+
+ distro_dir = os.path.join(self.module, self.distro, self.version)
+
+ dockerf = open(os.path.join(path, 'Dockerfile-build'), 'w')
+
+ dockerf.write('FROM {}\n'.format(from_image))
+ dockerf.write('WORKDIR /root/kresd\n')
+ if self.module == 'daemon/.packaging':
+ dockerf.write('COPY . /root/kresd\n')
+ # when this file doesn't exists, tzdata needs user interaction
+ dockerf.write('RUN if [ ! -f /etc/localtime ];' +
+ 'then ln -fs /usr/share/zoneinfo/Europe/Prague /etc/localtime; fi\n')
+ if os.path.isfile(os.path.join(distro_dir, 'pre-build.sh')):
+ dockerf.write('RUN {}\n'.format(os.path.join(distro_dir, 'pre-build.sh')))
+ if os.path.isfile(os.path.join(distro_dir, 'builddeps')):
+ dockerf.write('RUN {0} {1}\n'.format(self.cmd_pkgs_install(),
+ ' '.join(self.readDependencies(os.path.join(distro_dir, 'builddeps')))))
+ if os.path.isfile(os.path.join(distro_dir, 'build.sh')):
+ dockerf.write('RUN {}\n'.format(os.path.join(distro_dir, 'build.sh')))
+ else:
+ dockerf.write('RUN {}\n'.format(self.cmd_kresd_build()))
+ if os.path.isfile(os.path.join(distro_dir, 'install.sh')):
+ dockerf.write('RUN {}\n'.format(os.path.join(distro_dir, 'install.sh')))
+ else:
+ dockerf.write('RUN {}\n'.format(self.cmd_kresd_install()))
+ if os.path.isfile(os.path.join(distro_dir, 'post-build.sh')):
+ dockerf.write('RUN {}\n'.format(os.path.join(distro_dir, 'post-build.sh')))
+
+ dockerf.close()
+
+ def __genDockerFile_run(self, path, build_id, from_image=None):
+ """Generate Dockerfile for run image"""
+ if self.module is None:
+ raise AttributeError
+
+ if from_image is None:
+ if os.path.isfile(os.path.join(self.module, self.distro, 'docker-image-name')):
+ with open(os.path.join(self.module, self.distro, 'docker-image-name')) as f:
+ from_image = f.read()
+ else:
+ from_image = '{0}:{1}'.format(self.distro, self.version)
+
+ distro_dir = os.path.join(self.module, self.distro, self.version)
+
+ dockerf = open(os.path.join(path, 'Dockerfile-run'), 'w')
+
+ dockerf.write('FROM {}\n'.format(from_image))
+ dockerf.write('COPY --from={} /root/kresd /root/kresd\n'.format(build_id))
+ dockerf.write('WORKDIR /root/kresd\n')
+ if os.path.isfile(os.path.join(distro_dir, 'pre-run.sh')):
+ dockerf.write('RUN {}\n'.format(os.path.join(distro_dir, 'pre-run.sh')))
+ if os.path.isfile(os.path.join(distro_dir, 'rundeps')):
+ dockerf.write('RUN {0} {1}\n'.format(self.cmd_pkgs_install(),
+ ' '.join(self.readDependencies(os.path.join(distro_dir, 'rundeps')))))
+ if os.path.isfile(os.path.join(distro_dir, 'pre-test.sh')):
+ dockerf.write('RUN {}\n'.format(os.path.join(distro_dir, 'pre-test.sh')))
+
+ dockerf.close()
+
+ def build_printing_errors(self, path, dockerfile, network_mode, tag, rm):
+ try:
+ return client.images.build(path=path, dockerfile=dockerfile,
+ network_mode=network_mode, tag=tag, rm=rm)
+ except docker.errors.BuildError as e:
+ iterable = iter(e.build_log)
+ while True:
+ try:
+ item = next(iterable)
+ if item['stream']:
+ for l in item['stream'].splitlines():
+ stripped = l.strip()
+ if stripped:
+ logging.error(stripped)
+ except StopIteration:
+ break
+ raise e
+
+ def build(self, tmpdir, tag="", from_image=None):
+ self.__genDockerFile(tmpdir, from_image=from_image)
+
+ logger.debug('tmpdir={}'.format(tmpdir))
+ logger.debug('datadir={}'.format(pytest.KR_ROOT_DIR))
+ logger.debug('tag={}'.format(tag))
+ image = self.build_printing_errors(path=str(pytest.KR_ROOT_DIR),
+ dockerfile=os.path.join(tmpdir, 'Dockerfile-build'),
+ network_mode='host', tag=tag, rm=True)
+ logger.info('"Build image" ID={} created'.format(image[0].short_id))
+ self.build_id = image[0].short_id
+ return self.build_id
+
+ def build_run(self, tmpdir, build_id, from_image=None, tag=""):
+ self.__genDockerFile_run(tmpdir, build_id, from_image=from_image)
+
+ logger.debug('tmpdir={}'.format(tmpdir))
+ logger.debug('datadir={}'.format(tmpdir))
+ logger.debug('tag={}'.format(tag))
+ image = self.build_printing_errors(path=str(tmpdir),
+ dockerfile=os.path.join(tmpdir, 'Dockerfile-run'),
+ network_mode='host', tag=tag, rm=True)
+ logger.info('"Run image" ID={} created'.format(image[0].short_id))
+ self.run_id = image[0].short_id
+ return self.run_id
+
+
+class DebianImage(DockerImages):
+ def __init__(self, version):
+ super().__init__(version)
+ self.distro = 'debian'
+
+ def cmd_pkgs_install(self):
+ return 'apt-get install -y '
+
+ def cmd_kresd_install(self):
+ return 'ninja -C build_packaging install >/dev/null'
+
+ def cmd_kresd_build(self):
+ return """\\
+ [ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; \\
+ CFLAGS=\"$CFLAGS -Wall -pedantic -fno-omit-frame-pointer\"; \\
+ LDFLAGS=\"$LDFLAGS -Wl,--as-needed\"; \\
+ meson build_packaging \\
+ --buildtype=plain \\
+ --prefix=/root/kresd/install_packaging \\
+ --libdir=lib \\
+ --default-library=static \\
+ -Dsystemd_files=enabled \\
+ -Dclient=enabled \\
+ -Dkeyfile_default=/usr/share/dns/root.key \\
+ -Droot_hints=/usr/share/dns/root.hints \\
+ -Dinstall_kresd_conf=enabled \\
+ -Dunit_tests=enabled \\
+ -Dc_args=\"${CFLAGS}\" \\
+ -Dc_link_args=\"${LDFLAGS}\"; \\
+ ninja -C build_packaging
+ """
+
+
+class UbuntuImage(DebianImage):
+ def __init__(self, version):
+ super().__init__(version)
+ self.distro = 'ubuntu'
+
+
+class CentosImage(DockerImages):
+ def __init__(self, version):
+ super().__init__(version)
+ self.distro = 'centos'
+
+ def cmd_pkgs_install(self):
+ return "yum install -y "
+
+ def cmd_kresd_install(self):
+ return 'ninja-build -C build_packaging install'
+
+ def cmd_kresd_build(self):
+ return """\\
+ [ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; \\
+ CFLAGS=\"$CFLAGS -Wall -pedantic -fno-omit-frame-pointer\"; \\
+ LDFLAGS=\"$LDFLAGS -Wl,--as-needed\"; \\
+ meson build_packaging \\
+ --buildtype=plain \\
+ --prefix=/root/kresd/install_packaging \\
+ --sbindir=sbin \\
+ --libdir=lib \\
+ --includedir=include \\
+ --sysconfdir=etc \\
+ --default-library=static \\
+ -Dsystemd_files=enabled \\
+ -Dclient=enabled \\
+ -Dunit_tests=enabled \\
+ -Dmanaged_ta=enabled \\
+ -Dkeyfile_default=/root/kresd/install_packaging/var/lib/knot-resolver/root.keys \\
+ -Dinstall_root_keys=enabled \\
+ -Dinstall_kresd_conf=enabled; \\
+ ninja-build -C build_packaging
+ """
+
+
+class FedoraImage(DockerImages):
+ def __init__(self, version):
+ super().__init__(version)
+ self.distro = 'fedora'
+
+ def cmd_pkgs_install(self):
+ return "dnf install -y "
+
+ def cmd_kresd_install(self):
+ return 'ninja -C build_packaging install >/dev/null'
+
+ def cmd_kresd_build(self):
+ return """\\
+ [ -d /root/kresd/build_packaging ] && rm -rf /root/kresd/build_packaging/; \\
+ CFLAGS=\"$CFLAGS -Wall -pedantic -fno-omit-frame-pointer\"; \\
+ LDFLAGS=\"$LDFLAGS -Wl,--as-needed\"; \\
+ meson build_packaging \\
+ --buildtype=plain \\
+ --prefix=/root/kresd/install_packaging \\
+ --sbindir=sbin \\
+ --libdir=lib \\
+ --includedir=include \\
+ --sysconfdir=etc \\
+ --default-library=static \\
+ -Dsystemd_files=enabled \\
+ -Dclient=enabled \\
+ -Dunit_tests=enabled \\
+ -Dmanaged_ta=enabled \\
+ -Dkeyfile_default=/root/kresd/install_packaging/var/lib/knot-resolver/root.keys \\
+ -Dinstall_root_keys=enabled \\
+ -Dinstall_kresd_conf=enabled; \\
+ ninja -C build_packaging
+ """
+
+
+class LeapImage(FedoraImage):
+ def __init__(self, version):
+ super().__init__(version)
+ self.distro = 'leap'
+
+ def cmd_pkgs_install(self):
+ return "zypper install -y "
+
+
+def create_distro_image(name, version):
+ img = None
+
+ if (name == 'debian'):
+ img = DebianImage(version)
+ elif (name == 'ubuntu'):
+ img = UbuntuImage(version)
+ elif (name == 'centos'):
+ img = CentosImage(version)
+ elif (name == 'fedora'):
+ img = FedoraImage(version)
+ elif (name == 'leap'):
+ img = LeapImage(version)
+ else:
+ img = None
+
+ return img
+
+
+def list_dirs(path, exclude=None):
+ """return all 'packaging' directories with full path"""
+ filtered_dirs = []
+
+ for rootpath, dirs, _ in os.walk(path):
+
+ if (os.path.basename(rootpath) == '.packaging'):
+ fdir = os.path.relpath(rootpath, path)
+ if exclude is not None:
+ if fdir not in exclude:
+ filtered_dirs.append(fdir)
+ else:
+ filtered_dirs.append(fdir)
+
+ return filtered_dirs
+
+
+def list_tests_dirs():
+ """return all 'packaging' directories"""
+ return list_dirs(pytest.KR_ROOT_DIR)
+
+
+def list_distro_vers(distro_root):
+ """
+ return list of { 'name': distro_name, 'version': distro_version)
+ pairs found in distro_root
+ """
+ # transform list of paths like TOP/debian/10 into (debian, 10)
+ dist_ver = [{'name': p.parts[-2], 'version': p.parts[-1]} for p
+ in Path(distro_root).glob('*/*') if p.is_dir()]
+
+ return list(dist_ver)
+
+
+MODULES = list_tests_dirs()
+DISTROS = list_distro_vers(os.path.join(pytest.KR_ROOT_DIR, 'daemon/.packaging'))
+DISTROS_NAMES = ['{0}_{1}'.format(distro['name'], distro['version']) for distro in DISTROS]
+
+
+@pytest.fixture(scope='session', params=DISTROS, ids=DISTROS_NAMES)
+def buildenv(request, tmpdir_factory):
+ distro = request.param
+
+ logger.debug('Creating main images for "{0} {1}"'.format(distro['name'], distro['version']))
+ img = create_distro_image(distro['name'], distro['version'])
+ if img is None:
+ logger.warning('Unknown distro {}'.format(distro['name']))
+ else:
+ img.module = 'daemon/.packaging'
+ tmpdir = tmpdir_factory.mktemp(distro['name']+distro['version'])
+ img.build(tmpdir, tag=pytest.KR_PREFIX+distro['name']+distro['version']+'-build')
+ img.build_run(tmpdir, img.build_id,
+ tag=pytest.KR_PREFIX+distro['name']+distro['version']+'-run')
+
+ yield img
+# client.images.remove(img.run_id)
+# client.images.remove(img.build_id)
+
+
+@pytest.mark.parametrize('module', MODULES)
+def test_collect(module, buildenv, tmp_path):
+ logger.info(' ### Run test {} ###'.format(module))
+
+ if buildenv is None:
+ logger.error('Distro "{0} {1}" isn\'t implemented'.format(buildenv.distro,
+ buildenv.version))
+ assert False
+
+ rcode = None
+ buildmod = None
+ module_dir = os.path.join(pytest.KR_ROOT_DIR, module)
+ distro_dir = os.path.join(module_dir, buildenv.distro, buildenv.version)
+
+ if os.path.isfile(os.path.join(distro_dir, 'NOTSUPPORTED')):
+ pytest.skip('Unsupported linux distribution ({0} {1}:{2})'.format(buildenv.distro, buildenv.version, module))
+
+ try:
+ if module == 'daemon/.packaging':
+ # use main "run image" without changes
+ logging.info('Use main "run image"')
+ ch = ContainerHandler(buildenv.run_id)
+ ch.run()
+ elif buildenv is not None:
+ if os.path.isfile(os.path.join(distro_dir, 'pre-build.sh')) \
+ or os.path.isfile(os.path.join(distro_dir, 'builddeps')):
+ # create module specific "build image"
+ logger.info('Create new "build image"')
+ buildmod = create_distro_image(buildenv.distro, buildenv.version)
+ buildmod.module = module
+ buildmod.build(tmp_path, from_image=buildenv.build_id,
+ tag=pytest.KR_PREFIX+buildmod.distro+buildmod.version+'-' +
+ module.replace('/.packaging', '')+'-build')
+
+ if buildmod is not None:
+ # new build image was made, create new module specific "run image"
+ logger.info('Create module specific "run image" from Dockerfile')
+ buildmod.build_run(tmp_path, buildmod.build_id,
+ tag=pytest.KR_PREFIX+buildmod.distro+buildmod.version+'-' +
+ module.replace('/.packaging', '')+'-run', from_image=buildenv.run_id)
+ ch = ContainerHandler(buildmod.run_id)
+ ch.run()
+ elif os.path.isfile(os.path.join(distro_dir, 'pre-run.sh')) \
+ or os.path.isfile(os.path.join(distro_dir, 'rundeps')):
+ # use main "run image" and apply module specific changes
+ logger.info('Apply module specific changes to "run image"')
+ buildmod = buildenv
+ ch = ContainerHandler(buildmod.run_id)
+ ch.run()
+
+ if os.path.isfile(os.path.join(distro_dir, 'pre-run.sh')):
+ ch.exec_cmd(os.path.join(module, buildenv.distro, buildenv.version,
+ 'pre-run.sh'), '/root/kresd/')
+
+ if os.path.isfile(os.path.join(distro_dir, 'rundeps')):
+ logger.debug(buildmod.cmd_pkgs_install() + ' '.join(
+ buildmod.readDependencies(os.path.join(distro_dir, 'rundeps'))))
+ ch.exec_cmd(buildmod.cmd_pkgs_install() + ' '.join(
+ buildmod.readDependencies(os.path.join(distro_dir, 'rundeps'))),
+ '/root/kresd/')
+
+ if os.path.isfile(os.path.join(distro_dir, 'pre-test.sh')):
+ ch.exec_cmd(os.path.join(module, buildenv.distro, buildenv.version,
+ 'pre-test.sh'), '/root/kresd/')
+ else:
+ # use main "run image" without changes
+ logging.info('Use main "run image"')
+ ch = ContainerHandler(buildenv.run_id)
+ ch.run()
+
+ # run test
+ if os.path.isfile(os.path.join(module_dir, 'test.config')):
+ ch.exec_cmd('/root/kresd/install_packaging/sbin/kresd -n -c ' + os.path.join('..',
+ module, 'test.config'), '/root/kresd/install_packaging/')
+ elif os.path.isfile(os.path.join(module_dir, 'test.sh')):
+ ch.exec_cmd(os.path.join('..', module, 'test.sh'),
+ '/root/kresd/install_packaging/')
+ else:
+ ch.stop()
+ ch.container.remove()
+ logger.error('Test file (test.config or test.sh) not found')
+ assert False
+
+ rcode = 0
+
+ if os.path.isfile(os.path.join(distro_dir, 'post-run.sh')):
+ ch.exec_cmd(os.path.join(module, buildenv.distro, buildenv.version, 'post-run.sh'),
+ '/root/kresd/')
+
+ except DockerCmdError as err:
+ rcode, out = err.args
+ logger.debug('rcode: {}'.format(rcode))
+ logger.error(out.decode('utf-8'))
+ finally:
+ ch.stop()
+ ch.container.remove()
+ if buildmod is not None and buildmod is not buildenv:
+ client.images.remove(buildmod.run_id)
+ client.images.remove(buildmod.build_id)
+
+ assert(rcode == 0)