cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #01507
[Merge] ~wesley-wiedenmeier/cloud-init:integration-testing-invocation-cleanup into cloud-init:master
Wesley Wiedenmeier has proposed merging ~wesley-wiedenmeier/cloud-init:integration-testing-invocation-cleanup into cloud-init:master.
Requested reviews:
cloud init development team (cloud-init-dev)
For more details, see:
https://code.launchpad.net/~wesley-wiedenmeier/cloud-init/+git/cloud-init/+merge/314496
Enable running cloud_tests on current working tree, add citest tox envs
- Add 'bddeb' command to build a deb from the current working tree
cleanly in a container, so deps don't have to be installed on host
- Add 'tree_collect' and 'tree_run' commands, to build deb from
current working tree and call collect or run with deb
- Add 'citest' tox env that uses correct pylxd to call cloud_tests
- Add 'citest_run' tox env that runs cloud_tests on current tree
--
Your team cloud init development team is requested to review the proposed merge of ~wesley-wiedenmeier/cloud-init:integration-testing-invocation-cleanup into cloud-init:master.
diff --git a/tests/cloud_tests/__init__.py b/tests/cloud_tests/__init__.py
index 099c357..7959bd9 100644
--- a/tests/cloud_tests/__init__.py
+++ b/tests/cloud_tests/__init__.py
@@ -6,6 +6,7 @@ import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TESTCASES_DIR = os.path.join(BASE_DIR, 'testcases')
TEST_CONF_DIR = os.path.join(BASE_DIR, 'configs')
+TREE_BASE = os.sep.join(BASE_DIR.split(os.sep)[:-2])
def _initialize_logging():
diff --git a/tests/cloud_tests/__main__.py b/tests/cloud_tests/__main__.py
index ef7d187..74f29f6 100644
--- a/tests/cloud_tests/__main__.py
+++ b/tests/cloud_tests/__main__.py
@@ -2,11 +2,9 @@
import argparse
import logging
-import shutil
import sys
-import tempfile
-from tests.cloud_tests import (args, collect, manage, verify)
+from tests.cloud_tests import args, bddeb, collect, manage, run_funcs, verify
from tests.cloud_tests import LOG
@@ -22,28 +20,6 @@ def configure_log(args):
LOG.setLevel(level)
-def run(args):
- """
- run full test suite
- """
- failed = 0
- args.data_dir = tempfile.mkdtemp(prefix='cloud_test_data_')
- LOG.debug('using tmpdir %s', args.data_dir)
- try:
- failed += collect.collect(args)
- failed += verify.verify(args)
- except Exception:
- failed += 1
- raise
- finally:
- # TODO: make this configurable via environ or cmdline
- if failed:
- LOG.warn('some tests failed, leaving data in %s', args.data_dir)
- else:
- shutil.rmtree(args.data_dir)
- return failed
-
-
def main():
"""
entry point for cloud test suite
@@ -80,9 +56,12 @@ def main():
# run handler
LOG.debug('running with args: %s\n', parsed)
return {
+ 'bddeb': bddeb.bddeb,
'collect': collect.collect,
'create': manage.create,
- 'run': run,
+ 'run': run_funcs.run,
+ 'tree_collect': run_funcs.tree_collect,
+ 'tree_run': run_funcs.tree_run,
'verify': verify.verify,
}[parsed.subcmd](parsed)
diff --git a/tests/cloud_tests/args.py b/tests/cloud_tests/args.py
index b68cc98..d051737 100644
--- a/tests/cloud_tests/args.py
+++ b/tests/cloud_tests/args.py
@@ -3,9 +3,24 @@
import os
from tests.cloud_tests import config, util
-from tests.cloud_tests import LOG
+from tests.cloud_tests import LOG, TREE_BASE
ARG_SETS = {
+ 'BDDEB': (
+ (('--bddeb-args',),
+ {'help': 'args to pass through to bddeb',
+ 'action': 'store', 'default': None, 'required': False}),
+ (('--build-os',),
+ {'help': 'OS to use as build system (default is xenial)',
+ 'action': 'store', 'choices': config.list_enabled_distros(),
+ 'default': 'xenial', 'required': False}),
+ (('--build-platform',),
+ {'help': 'platform to use for build system (default is lxd)',
+ 'action': 'store', 'choices': config.list_enabled_platforms(),
+ 'default': 'lxd', 'required': False}),
+ (('--cloud-init',),
+ {'help': 'path to base of cloud-init tree', 'metavar': 'DIR',
+ 'action': 'store', 'required': False, 'default': TREE_BASE}),),
'COLLECT': (
(('-p', '--platform'),
{'help': 'platform(s) to run tests on', 'metavar': 'PLATFORM',
@@ -42,6 +57,10 @@ ARG_SETS = {
(('-d', '--data-dir'),
{'help': 'directory to store test data in',
'action': 'store', 'metavar': 'DIR', 'required': True}),),
+ 'OUTPUT_DEB': (
+ (('--deb',),
+ {'help': 'path to write output deb to', 'metavar': 'FILE',
+ 'action': 'store', 'required': True}),),
'RESULT': (
(('-r', '--result'),
{'help': 'file to write results to',
@@ -66,10 +85,16 @@ ARG_SETS = {
}
SUBCMDS = {
+ 'bddeb': ('build cloud-init deb from tree',
+ ('BDDEB', 'OUTPUT_DEB', 'INTERFACE')),
'collect': ('collect test data',
('COLLECT', 'INTERFACE', 'OUTPUT', 'RESULT', 'SETUP')),
'create': ('create new test case', ('CREATE', 'INTERFACE')),
'run': ('run test suite', ('COLLECT', 'INTERFACE', 'RESULT', 'SETUP')),
+ 'tree_collect': ('collect using current working tree',
+ ('BDDEB', 'COLLECT', 'INTERFACE', 'OUTPUT', 'RESULT')),
+ 'tree_run': ('run using current working tree',
+ ('BDDEB', 'COLLECT', 'INTERFACE', 'RESULT')),
'verify': ('verify test data', ('INTERFACE', 'OUTPUT', 'RESULT')),
}
@@ -81,6 +106,20 @@ def _empty_normalizer(args):
return args
+def normalize_bddeb_args(args):
+ """
+ normalize BDDEB arguments
+ args: parsed args
+ return_value: updated args, or None if errors encountered
+ """
+ # make sure cloud-init dir is accessible
+ if not (args.cloud_init and os.path.isdir(args.cloud_init)):
+ LOG.error('invalid cloud-init tree path')
+ return None
+
+ return args
+
+
def normalize_create_args(args):
"""
normalize CREATE arguments
@@ -185,6 +224,17 @@ def normalize_output_args(args):
return args
+def normalize_output_deb_args(args):
+ """
+ normalize OUTPUT_DEB arguments
+ args: parsed args
+ return_value: updated args, or None if erros occurred
+ """
+ # make sure to use abspath for deb
+ args.deb = os.path.abspath(args.deb)
+ return args
+
+
def normalize_setup_args(args):
"""
normalize SETUP arguments
@@ -210,10 +260,12 @@ def normalize_setup_args(args):
NORMALIZERS = {
+ 'BDDEB': normalize_bddeb_args,
'COLLECT': normalize_collect_args,
'CREATE': normalize_create_args,
'INTERFACE': _empty_normalizer,
'OUTPUT': normalize_output_args,
+ 'OUTPUT_DEB': normalize_output_deb_args,
'RESULT': _empty_normalizer,
'SETUP': normalize_setup_args,
}
diff --git a/tests/cloud_tests/bddeb.py b/tests/cloud_tests/bddeb.py
new file mode 100644
index 0000000..0ed9b82
--- /dev/null
+++ b/tests/cloud_tests/bddeb.py
@@ -0,0 +1,134 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from tests.cloud_tests import (config, LOG)
+from tests.cloud_tests import (platforms, images, snapshots, instances)
+from tests.cloud_tests.stage import (PlatformComponent, run_stage, run_single)
+
+from cloudinit import util as c_util
+
+from functools import partial
+import os
+import textwrap
+
+# cloud-config to boot build instance with, installs cloud-init build deps
+_cloud_config = textwrap.dedent(
+ """
+ #cloud-config
+ packages:
+ - devscripts
+ - equivs
+ - git
+ - python3
+ - python
+ - tar
+ package_update: true
+ runcmd:
+ - [ mk-build-deps, --install, cloud-init, -t,
+ apt-get --no-install-recommends --yes ]
+ """
+).lstrip()
+
+
+def _out(cmd_res):
+ """
+ get clean output from cmd result
+ """
+ return cmd_res[0].strip()
+
+
+def build_deb(args, instance):
+ """
+ build deb on system and copy out to location at args.deb
+ args: cmdline arguments
+ return_value: tuple of results and fail count
+ """
+ # NOTE: it might be cleaner to do this using stages
+
+ # local tmpfile that must be deleted
+ local_tarball = _out(c_util.subp(['mktemp'], capture=True))
+
+ try:
+ # paths to use in remote system
+ remote_tarball = _out(instance.execute(['mktemp']))
+ extract_dir = _out(instance.execute(['mktemp', '--directory']))
+ bddeb_path = os.path.join(extract_dir, 'packages', 'bddeb')
+ output_link = '/cloud-init_all.deb'
+ git_env = {'GIT_DIR': os.path.join(extract_dir, '.git'),
+ 'GIT_WORK_TREE': extract_dir}
+
+ # create a tarball of cloud init tree and copy to remote system
+ LOG.debug('creating tarball of cloud-init at: %s', local_tarball)
+ c_util.subp(['tar', 'cf', local_tarball, '--owner', 'root',
+ '--group', 'root', '-C', args.cloud_init, '.'])
+ LOG.debug('copying to remote system at: %s', remote_tarball)
+ instance.push_file(local_tarball, remote_tarball)
+
+ # extract tarball in remote system and commit anything uncommitted
+ LOG.debug('extracting tarball in remote system at: %s', extract_dir)
+ instance.execute(['tar', 'xf', remote_tarball, '-C', extract_dir])
+ instance.execute(['git', 'commit', '-a', '-m', 'tmp', '--allow-empty'],
+ env=git_env)
+
+ # build the deb, ignoring missing deps (flake8)
+ LOG.debug('building deb in remote system at: %s', output_link)
+ bddeb_args = args.bddeb_args.split() if args.bddeb_args else []
+ instance.execute([bddeb_path, '-d'] + bddeb_args, env=git_env)
+
+ # copy the deb back to the host system
+ LOG.debug('copying built deb to host at: %s', args.deb)
+ instance.pull_file(output_link, args.deb)
+
+ finally:
+ os.remove(local_tarball)
+
+
+def setup_build(args):
+ """
+ set build system up then run build
+ args: cmdline arguments
+ return_value: tuple of results and fail count
+ """
+ res = ({}, 1)
+
+ # set up platform
+ LOG.info('setting up platform: %s', args.build_platform)
+ platform_config = config.load_platform_config(args.build_platform)
+ platform_call = partial(platforms.get_platform, args.build_platform,
+ platform_config)
+ with PlatformComponent(platform_call) as platform:
+
+ # set up image
+ LOG.info('acquiring image for os: %s', args.build_os)
+ img_conf = config.load_os_config(args.build_os)
+ image_call = partial(images.get_image, platform, img_conf)
+ with PlatformComponent(image_call) as image:
+
+ # set up snapshot
+ snapshot_call = partial(snapshots.get_snapshot, image)
+ with PlatformComponent(snapshot_call) as snapshot:
+
+ # create instance with cloud-config to set it up
+ LOG.info('creating instance to build deb in')
+ instance_call = partial(
+ instances.get_instance, snapshot,
+ _cloud_config, use_desc='build cloud-init deb')
+ with PlatformComponent(instance_call) as instance:
+
+ # build the deb
+ res = run_single('build deb on system',
+ partial(build_deb, args, instance))
+
+ return res
+
+
+def bddeb(args):
+ """
+ entry point for build deb
+ args: cmdline arguments
+ return_value: fail count
+ """
+ LOG.info('preparing to build cloud-init deb')
+ (res, failed) = run_stage('build deb', [partial(setup_build, args)])
+ return failed
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/instances/base.py b/tests/cloud_tests/instances/base.py
index 9559d28..1782c1d 100644
--- a/tests/cloud_tests/instances/base.py
+++ b/tests/cloud_tests/instances/base.py
@@ -51,7 +51,7 @@ class Instance(object):
copy file at 'remote_path', from instance to 'local_path'
"""
with open(local_path, 'wb') as fp:
- fp.write(self.read_data(remote_path), encode=True)
+ fp.write(self.read_data(remote_path))
def push_file(self, local_path, remote_path):
"""
diff --git a/tests/cloud_tests/run_funcs.py b/tests/cloud_tests/run_funcs.py
new file mode 100644
index 0000000..683a3f6
--- /dev/null
+++ b/tests/cloud_tests/run_funcs.py
@@ -0,0 +1,65 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from tests.cloud_tests import bddeb, collect, util, verify
+
+import os
+
+
+def tree_collect(args):
+ """
+ collect data using deb build from current tree
+ args: cmdline args
+ return_value: fail count
+ """
+ failed = 0
+
+ with util.TempDir(args) as tmpdir:
+ args.deb = os.path.join(tmpdir, 'cloud-init.deb')
+ try:
+ failed += bddeb.bddeb(args)
+ failed += collect.collect(args)
+ except Exception:
+ failed += 1
+ raise
+
+ return failed
+
+
+def tree_run(args):
+ """
+ run test suite using deb build from current tree
+ args: cmdline args
+ return_value: fail count
+ """
+ failed = 0
+
+ with util.TempDir(args) as tmpdir:
+ args.deb = os.path.join(tmpdir, 'cloud-init.deb')
+ try:
+ failed += bddeb.bddeb(args)
+ failed += run(args)
+ except Exception:
+ failed += 1
+ raise
+
+ return failed
+
+
+def run(args):
+ """
+ run test suite
+ """
+ failed = 0
+
+ with util.TempDir(args) as tmpdir:
+ args.data_dir = tmpdir
+ try:
+ failed += collect.collect(args)
+ failed += verify.verify(args)
+ except Exception:
+ failed += 1
+ raise
+
+ return failed
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/util.py b/tests/cloud_tests/util.py
index 64a8667..b9ad98b 100644
--- a/tests/cloud_tests/util.py
+++ b/tests/cloud_tests/util.py
@@ -3,6 +3,7 @@
import glob
import os
import random
+import shutil
import string
import tempfile
import yaml
@@ -160,4 +161,37 @@ def write_file(*args, **kwargs):
"""
c_util.write_file(*args, **kwargs)
+
+class TempDir(object):
+ """
+ temporary directory like tempfile.TemporaryDirectory, but configurable
+ args:
+ """
+
+ def __init__(self, args):
+ """
+ setup and store args
+ args: cmdline arguments
+ """
+ self.args = args
+ self.tmpdir = None
+
+ def __enter__(self):
+ """
+ create tempdir
+ return_value: tempdir path
+ """
+ self.tmpdir = tempfile.mkdtemp(prefix='cloud_test_')
+ LOG.debug('using tmpdir: %s', self.tmpdir)
+ return self.tmpdir
+
+ def __exit__(self, etype, value, trace):
+ """
+ destroy tempdir if no errors occurred
+ """
+ if etype:
+ LOG.warn('erros occurred, leaving data in %s', self.tmpdir)
+ else:
+ shutil.rmtree(self.tmpdir)
+
# vi: ts=4 expandtab
diff --git a/tox.ini b/tox.ini
index e79ea6a..fda7d85 100644
--- a/tox.ini
+++ b/tox.ini
@@ -64,6 +64,18 @@ deps =
flake8==2.5.4
hacking==0.10.2
+[testenv:citest_run]
+basepython = python3
+commands = {envpython} -m tests.cloud_tests tree_run -v {posargs:-n xenial}
+deps =
+ pylxd==2.1.3
+
+[testenv:citest]
+basepython = python3
+commands = {envpython} -m tests.cloud_tests {posargs}
+deps =
+ pylxd==2.1.3
+
[testenv:centos6]
basepython = python2.6
commands = nosetests {posargs:tests}
Follow ups