From 2fa8d0a172b97529768413c0f774536a21d9d57c Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 26 Nov 2014 13:25:22 +0000 Subject: Add initial test plugins --- mason/__init__.py | 2 + mason/tests/__init__.py | 8 +++ mason/tests/artifact_upload.py | 118 ++++++++++++++++++++++++++++++++ mason/tests/build.py | 115 ++++++++++++++++++++++++++++++++ mason/tests/build_test.py | 148 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 391 insertions(+) create mode 100644 mason/__init__.py create mode 100644 mason/tests/__init__.py create mode 100644 mason/tests/artifact_upload.py create mode 100644 mason/tests/build.py create mode 100644 mason/tests/build_test.py diff --git a/mason/__init__.py b/mason/__init__.py new file mode 100644 index 0000000..237344b --- /dev/null +++ b/mason/__init__.py @@ -0,0 +1,2 @@ +import tests +import util diff --git a/mason/tests/__init__.py b/mason/tests/__init__.py new file mode 100644 index 0000000..b5b98a6 --- /dev/null +++ b/mason/tests/__init__.py @@ -0,0 +1,8 @@ +# builds the systems in `cluster-morphology` +import build + +# uploads build-artifacts to the cache on upstream-trove +import artifact_upload + +# test plugins +import build_test diff --git a/mason/tests/artifact_upload.py b/mason/tests/artifact_upload.py new file mode 100644 index 0000000..21d1093 --- /dev/null +++ b/mason/tests/artifact_upload.py @@ -0,0 +1,118 @@ +# Copyright 2014 Codethink Ltd + +import cliapp +import json +import logging +import os +import urlparse + +from turbo_hipster.lib import common +from turbo_hipster.lib import models + + +#TODO: Less different instances of this would be nice +class MorphologyHelper(object): + + def __init__(self, path): + self.defs_repo = morphlib.gitdir.GitDirectory(path) + self.loader = morphlib.morphloader.MorphologyLoader() + self.finder = morphlib.morphologyfinder.MorphologyFinder(self.defs_repo) + + def load_morphology(self, path): + text = self.finder.read_morphology(path) + return self.loader.load_from_string(text) + + @classmethod + def iterate_systems(cls, systems_list): + for system in systems_list: + yield morphlib.util.sanitise_morphology_path(system['morph']) + if 'subsystems' in system: + for subsystem in cls.iterate_systems(system['subsystems']): + yield subsystem + + def iterate_cluster_deployments(cls, cluster_morph): + for system in cluster_morph['systems']: + path = morphlib.util.sanitise_morphology_path(system['morph']) + defaults = system.get('deploy-defaults', {}) + for name, options in system['deploy'].iteritems(): + config = dict(defaults) + config.update(options) + yield path, name, config + + def load_cluster_systems(self, cluster_morph): + for system_path in set(self.iterate_systems(cluster_morph['systems'])): + system_morph = self.load_morphology(system_path) + yield system_path, system_morph + + +#TODO: Deployment + + +class Runner(models.Task): + + """This thread handles running the build-deploy-build test, + which is used to ensure that Baserock can build Baserock.""" + + log = logging.getLogger("task_plugins.build_deploy_test.task.Runner") + + def __init__(self, worker_server, plugin_config, job_name): + super(Runner, self).__init__(worker_server, plugin_config, job_name) + + self.total_steps = 5 + print self.job_arguments + + def do_job_steps(self): + self.log.info('Step 1: Creating a workspace') + self._create_workspace() + + self.log.info('Step 2: Deploy and test the systems') + self._deploy_and_test_systems() + + self.log.info('Step 3: Clean up') + self._clean_up() + + def _do_git_config(self): + cliapp.runcmd(['git', 'config', 'user.name', 'Mason Test Runner']) + cliapp.runcmd(['git', 'config', 'user.email', 'mason@test.runner']) + + @common.task_step + def _create_workspace(self): + self.commit = self.job_arguments['ZUUL_COMMIT'] + self.project = self.job_arguments['ZUUL_PROJECT'] + self.ref = self.job_arguments['ZUUL_REF'] + self.workspace = '/root/mason-workspace' + self.zuul_url = self.job_arguments['ZUUL_URL'] + + url = urlparse.urlparse(self.zuul_url) + self.defs_checkout = os.path.join(self.workspace, + self.commit, + url.hostname, + '8080', + self.project) + + self._do_git_config() + cliapp.runcmd(['morph', 'init', self.workspace]) + + repo = 'http://%s:8080/%s' % (url.hostname, self.project) + cliapp.runcmd(['morph', 'checkout', repo, self.commit], cwd=self.workspace) + + @common.task_step + def _test_systems(self): + infrastructure = \ + self.plugin_config['config']['test-infrastructure-type'] + cmd = ['scripts/release-test'] + args = ['--deployment-host', + self.plugin_config['config']['deployment-host'], + '--trove-host', self.plugin_config['config']['trove-host'], + '--trove-id', self.plugin_config['config']['trove-id'], + '--test-ref', self.commit + ] + if infrastructure == 'openstack': + cmd = ['/usr/lib/mason/mason-test-os'] + args += ['--net-id', + self.plugin_config['config']['openstack-network-id']] + args += [self.plugin_config['config']['cluster-morphology']] + + @common.task_step + def _clean_up(self): + cliapp.runcmd(['rm', '-rf', self.workspace]) diff --git a/mason/tests/build.py b/mason/tests/build.py new file mode 100644 index 0000000..d347eb9 --- /dev/null +++ b/mason/tests/build.py @@ -0,0 +1,115 @@ +# Copyright 2014 Codethink Ltd + +import cliapp +import json +import logging +import morphlib +import os +import urlparse + +import mason + + +class Build(object): + + """A single build instance.""" + + def __init__(self, name, controller): + self.system_name = name + self.controller = controller + self.command = [ + 'morph', 'build', self.system_name] + + def start(self): + self.process = subprocess.Popen(self.command) + + def completed(self): + return (self.process.poll() is not None) + + +class Runner(mason.util.JobRunner): + + """This thread handles running the build-deploy-build test, + which is used to ensure that Baserock can build Baserock.""" + + log = logging.getLogger("mason.tests.build.Runner") + + def __init__(self, worker_server, plugin_config, job_name): + super(Runner, self).__init__(worker_server, plugin_config, job_name) + + self.total_steps = 3 + + def do_job_steps(self): + self.log.info('Step 1: Creating a workspace') + self._create_workspace() + + self.log.info('Step 2: Building the systems') + self._build_systems() + + #TODO: provide logs + self.log.info('Step 3: Clean up') + self._clean_up() + + def _do_git_config(self): + cliapp.runcmd(['git', 'config', 'user.name', 'Mason Test Runner']) + cliapp.runcmd(['git', 'config', 'user.email', 'mason@test.runner']) + + def _parse_controllers(self, conf): + controllers = {} + for arch, addr in (item.split(':') for item in conf['controllers']): + controllers[arch] = addr + return controllers + + def _prepare_builds(self, conf): + cluster = self.morph_helper.load_morphology(conf['cluster-morphology']) + systems = set(self.morph_helper.iterate_systems(cluster['systems'])) + controllers = self._parse_controllers(conf) + builds = [] + for system_name in systems: + system = self.morph_helper.load_morphology(system_name) + if system['arch'] in controllers: + builds.append(Build(system_name, controllers['arch'])) + return builds + + def _create_workspace(self): + self.commit = self.job_arguments['ZUUL_COMMIT'] + self.project = self.job_arguments['ZUUL_PROJECT'] + self.ref = self.job_arguments['ZUUL_REF'] + self.workspace = '/root/mason-workspace' + self.zuul_url = self.job_arguments['ZUUL_URL'] + + url = urlparse.urlparse(self.zuul_url) + self.defs_checkout = os.path.join(self.workspace, + self.commit, + url.hostname, + '8080', + self.project) + self.morph_helper = mason.util.MorphologyHelper(self.defs_checkout) + + self._do_git_config() + cliapp.runcmd(['morph', 'init', self.workspace]) + + repo = 'http://%s:8080/%s' % (url.hostname, self.project) + cliapp.runcmd(['morph', 'checkout', repo, self.commit], + cwd=self.workspace) + + def _build_systems(self): + builds = self._prepare_builds(self.plugin_config['config']) + os.chdir(self.defs_checkout) + for build in builds: + build.start() + + while not all(build.completed() for build in builds): + time.sleep(1) + + fail = False + for build in builds: + if build.process.returncode != 0: + fail = True + sys.stderr.write( + 'Building failed for %s\n' % build.system_name) + if fail: + raise cliapp.AppException('Building of systems failed.') + + def _clean_up(self): + cliapp.runcmd(['rm', '-rf', self.workspace]) diff --git a/mason/tests/build_test.py b/mason/tests/build_test.py new file mode 100644 index 0000000..11cc30e --- /dev/null +++ b/mason/tests/build_test.py @@ -0,0 +1,148 @@ +# Copyright 2014 Codethink Ltd + +import cliapp +import json +import logging +import os +import urlparse + +import mason + + +class Runner(mason.util.JobRunner): + + """This thread handles running the build test, which is used to ensure + that Baserock can build Baserock.""" + + log = logging.getLogger("mason.tests.build_test.Runner") + + def __init__(self, worker_server, plugin_config, job_name): + super(Runner, self).__init__(worker_server, plugin_config, job_name) + + self.total_steps = 3 + + def do_job_steps(self): + self.log.info('Step 1: Creating a workspace') + self._create_workspace() + + self.log.info('Step 2: Deploy and test the systems') + self._deploy_and_test_systems() + + self.log.info('Step 3: Clean up') + self._clean_up() + + def _do_git_config(self): + cliapp.runcmd(['git', 'config', 'user.name', 'Mason Test Runner']) + cliapp.runcmd(['git', 'config', 'user.email', 'mason@test.runner']) + + @staticmethod + def _run_tests(instance, system_path, system_morph, + (trove_host, trove_id, build_ref_prefix), + systems): + instance.wait_until_online() + + tests = [] + def baserock_build_test(instance): + instance.runcmd(['git', 'config', '--global', 'user.name', + 'Test Instance of %s' % instance.deployment.name]) + instance.runcmd(['git', 'config', '--global', 'user.email', + 'ci-test@%s' % instance.config['HOSTNAME']]) + instance.runcmd(['mkdir', '-p', '/src/ws', '/src/cache', + '/src/tmp']) + def morph_cmd(*args, **kwargs): + # TODO: decide whether to use cached artifacts or not by + # adding --artifact-cache-server= --cache-server= + argv = ['morph', '--log=/src/morph.log', '--cachedir=/src/cache', + '--tempdir=/src/tmp', '--log-max=100M', + '--trove-host', trove_host, '--trove-id', trove_id, + '--build-ref-prefix', build_ref_prefix] + argv.extend(args) + instance.runcmd(argv, **kwargs) + + repo = self.morph_helper.sb.root_repository_url + ref = self.morph_helper.defs_repo.HEAD + sha1 = self.morph_helper.defs_repo.resolve_ref_to_commit(ref) + morph_cmd('init', '/src/ws') + chdir = '/src/ws' + + morph_cmd('checkout', repo, ref, chdir=chdir) + # TODO: Add a morph subcommand that gives the path to the root repository. + repo_path = os.path.relpath( + self.morph_helper.sb.get_git_directory_name(repo), + self.morph_helper.sb.root_directory) + chdir = os.path.join(chdir, ref, repo_path) + + instance.runcmd(['git', 'reset', '--hard', sha1], chdir=chdir) + print 'Building test systems for {sys}'.format(sys=system_path) + for to_build_path, to_build_morph in systems.iteritems(): + if to_build_morph['arch'] == system_morph['arch']: + print 'Test building {path}'.format(path=to_build_path) + morph_cmd('build', to_build_path, chdir=chdir, + stdin=None, stdout=None, stderr=None) + print 'Finished Building test systems' + + # TODO: Match the systems with a regex in config? + if 'devel' in system_path: + tests.append(baserock_build_test) + + for test in tests: + test(instance) + + def _create_workspace(self): + self.commit = self.job_arguments['ZUUL_COMMIT'] + self.project = self.job_arguments['ZUUL_PROJECT'] + self.ref = self.job_arguments['ZUUL_REF'] + self.workspace = '/root/mason-workspace' + self.zuul_url = self.job_arguments['ZUUL_URL'] + + url = urlparse.urlparse(self.zuul_url) + self.defs_checkout = os.path.join(self.workspace, + self.commit, + url.hostname, + '8080', + self.project) + self.morph_helper = baserock_tests.MorphologyHelper( + self.defs_checkout) + + self._do_git_config() + cliapp.runcmd(['morph', 'init', self.workspace]) + + repo = 'http://%s:8080/%s' % (url.hostname, self.project) + cliapp.runcmd(['morph', 'checkout', repo, self.commit], + cwd=self.workspace) + + def _deploy_and_test_systems(self): + config = self.plugin_config['config'] + infrastructure = config['test-infrastructure-type'] + build_test_config = (config['trove-host'], + config['trove-id'], + config['build-ref-prefix']) + cluster = self.morph_helper.load_morphology( + config['cluster-morphology']) + systems = dict(morph_helper.load_cluster_systems(cluster)) + + for system_path, deployment_name, deployment_config in \ + self.morph_helper.iterate_cluster_deployments(cluster): + + system = systems[system_path] + # We can only test systems in KVM that have a BSP + if not any('bsp' in si['morph'] for si in system['strata']): + continue + + # We can only test systems in KVM that we have a host for + if system['arch'] not in deployment_hosts: + continue + + host = deployment_hosts[system['arch']] + deployment = mason.util.Deployment(cluster, deployment_name, + deployment_config, host) + + instance = deployment.deploy() + try: + self._run_tests(instance, system_path, system, + build_test_config, systems) + finally: + instance.delete() + + def _clean_up(self): + cliapp.runcmd(['rm', '-rf', self.workspace]) -- cgit v1.2.1