diff options
author | Fathi Boudra <fathi.boudra@linaro.org> | 2019-12-17 17:37:58 +0200 |
---|---|---|
committer | Fathi Boudra <fathi.boudra@linaro.org> | 2019-12-17 17:37:58 +0200 |
commit | 9984a98f85c5006ed9979439ec0ff08fe47631a0 (patch) | |
tree | b49bb64765bf727f9343b00412e47333e5323590 | |
parent | 9e402bf7c9094d47fb58412525796001b5cd0e62 (diff) | |
download | tf-a-job-configs-9984a98f85c5006ed9979439ec0ff08fe47631a0.tar.gz |
post-build-lava: initial helper job to submit lava job
Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
Change-Id: I0e487cf29a5064fffe68069d2c3a74b307c0cd65
-rw-r--r-- | post-build-lava.yaml | 38 | ||||
-rwxr-xr-x | post-build-lava/builders.sh | 14 | ||||
-rw-r--r-- | post-build-lava/post-build-lava.groovy | 88 | ||||
-rw-r--r-- | post-build-lava/post-build-lava.py | 599 | ||||
-rw-r--r-- | post-build-lava/submit_for_testing.py | 282 |
5 files changed, 1021 insertions, 0 deletions
diff --git a/post-build-lava.yaml b/post-build-lava.yaml new file mode 100644 index 00000000..6d9f8ee6 --- /dev/null +++ b/post-build-lava.yaml @@ -0,0 +1,38 @@ +- job: + name: post-build-lava + project-type: freestyle + defaults: global + properties: + - build-discarder: + days-to-keep: 365 + num-to-keep: 700 + parameters: + - file: + name: post_build_lava_parameters + - bool: + name: SKIP_REPORT + default: true + - string: + name: LAVA_SERVER + default: 'tf.validation.linaro.org/RPC2/' + disabled: false + node: master + display-name: 'Post build to LAVA' + wrappers: + - timestamps + - credentials-binding: + - text: + credential-id: LAVA_USER_TF + variable: LAVA_USER + - credentials-binding: + - text: + credential-id: LAVA_TOKEN_TF + variable: LAVA_TOKEN + builders: + - shell: + !include-raw: post-build-lava/builders.sh + publishers: + - groovy-postbuild: + script: + !include-raw: + - post-build-lava/post-build-lava.groovy diff --git a/post-build-lava/builders.sh b/post-build-lava/builders.sh new file mode 100755 index 00000000..2b02a82e --- /dev/null +++ b/post-build-lava/builders.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +[ -z "${DEVICE_TYPE}" ] && exit 0 + +# disable job submission +#export SKIP_LAVA=1 + +# Send to LAVA +rm -rf pbl post_build_reports_parameters +git clone https://git.trustedfirmware.org/ci/tf-a-job-configs.git pbl +echo "Device type: ${DEVICE_TYPE}" +python pbl/post-build-lava/post-build-lava.py diff --git a/post-build-lava/post-build-lava.groovy b/post-build-lava/post-build-lava.groovy new file mode 100644 index 00000000..cbcc15de --- /dev/null +++ b/post-build-lava/post-build-lava.groovy @@ -0,0 +1,88 @@ +import hudson.model.* + +// Add a LAVA job link to the description +def matcher = manager.getLogMatcher(".*LAVA Job Id.*") +if (matcher?.matches()) { + def lavaJobId = matcher.group(0).split(",")[0].substring(13) + if (!lavaJobId.isInteger()) { + lavaJobId = matcher.group(0).tokenize("'")[1] + } + def lavaServer = matcher.group(0).tokenize("/")[1] + def lavaJobUrl = "https://${lavaServer}/scheduler/job/${lavaJobId}" + def lavaDescription = " LAVA Job Id: <a href='${lavaJobUrl}'>${lavaJobId}</a>" + + def cause = manager.build.getAction(hudson.model.CauseAction.class).getCauses() + def upstreamBuild = cause[0].upstreamBuild + def upstreamProject = cause[0].upstreamProject + def jobName = upstreamProject + def jobConfiguration = upstreamProject + def jobUrl = manager.hudson.getRootUrl() + "job/${upstreamProject}/${upstreamBuild}" + def jobDescription = "<br> Build <a href='${jobUrl}'>${upstreamProject} #${upstreamBuild}</a>" + + manager.build.setDescription(lavaDescription + jobDescription) + + // Multi-configuration project + if (upstreamProject.contains("/")) { + jobName = upstreamProject.split("/")[0] + jobConfiguration = upstreamProject.split("/")[1] + } + + def jobs = hudson.model.Hudson.instance.getItem(jobName).getAllJobs() + + for (job in jobs) { + if (job.name == jobConfiguration) { + if (job.getLastBuild().getDescription() != null) { + lavaDescription += "<br>" + job.getLastBuild().getDescription() + } + job.getLastBuild().setDescription(lavaDescription) + } + } + + // Add parameters + def action = manager.build.getAction(hudson.model.ParametersAction.class) + def parameters = [ + new StringParameterValue("LAVA_SERVER", "${lavaServer}/RPC2/"), + new StringParameterValue("LAVA_JOB_ID", "${lavaJobId}"), + new StringParameterValue("BUILD_JOB", "${jobUrl}") + ] + updatedAction = action.createUpdated(parameters) + manager.build.replaceAction(updatedAction) + + // Update the pool of jobs to monitor + job = hudson.model.Hudson.instance.getItem("check-lava-status") + property = job.getProperty(hudson.model.ParametersDefinitionProperty.class) + parameter = property.getParameterDefinition("LAVA_JOB_ID_POOL") + lavaJobIdPool = parameter.getDefaultValue() + lavaJobIdPool += " ${manager.build.number}" + parameter.setDefaultValue(lavaJobIdPool) + job.save() + + // Call post-build-report with parameters file + def skipReport = manager.build.getEnvironment(manager.listener)['SKIP_REPORT'] + if (!skipReport.toBoolean()) { + def project = manager.build.project; + def pbrParamsCache = "" + + def sourceJob = hudson.model.Hudson.instance.getItem(jobName) + def sourceVariables = sourceJob.getBuildByNumber(upstreamBuild.toInteger()).getEnvironment() + for (sourceVariableName in sourceVariables.keySet()) { + if (sourceVariableName.startsWith("GERRIT")) { + sourceVariableValue = sourceVariables.get(sourceVariableName) + pbrParamsCache += "SOURCE_${sourceVariableName}=${sourceVariableValue}\n"; + } + if (sourceVariableName.startsWith("ART_URL")) { + sourceVariableValue = sourceVariables.get(sourceVariableName) + pbrParamsCache += "${sourceVariableName}=${sourceVariableValue}\n"; + } + } + + pbrParamsCache += "SOURCE_PROJECT_NAME=${jobName}\n"; + pbrParamsCache += "SOURCE_BUILD_NUMBER=${upstreamBuild}\n"; + pbrParamsCache += "SOURCE_BUILD_ID=${upstreamBuild}\n"; + pbrParamsCache += "SOURCE_BUILD_URL=${jobUrl}\n"; + pbrParamsCache += "LAVA_JOB_IDS=${lavaJobId}\n"; + + def pbrParams = project.getWorkspace().child("post_build_reports_parameters"); + pbrParams.write(pbrParamsCache, null); + } +} diff --git a/post-build-lava/post-build-lava.py b/post-build-lava/post-build-lava.py new file mode 100644 index 00000000..41f4753c --- /dev/null +++ b/post-build-lava/post-build-lava.py @@ -0,0 +1,599 @@ +#!/usr/bin/python + +import base64 +import collections +import fileinput +import json +import os +import re +import sys +import urllib2 +import xmlrpclib + +tests_timeout = { + 'bluetooth-enablement': 7200, + 'bootchart': 800, + 'busybox': 800, + 'cyclictest': 90000, + 'device-tree': 800, + 'e2eaudiotest': 7200, + 'ethernet': 800, + 'gatortests': 1200, + 'kernel-version': 800, + 'leb-basic-graphics': 7200, + 'ltp': 10800, + 'mysql': 800, + 'network-tests-basic': 1200, + 'perf': 800, + 'phpinfo': 800, + 'phpmysql': 800, + 'pwrmgmt': 1200, + 'sdkhelloc': 800, + 'sdkhellocxx': 800, + 'smoke-tests-basic': 1200, + 'toolchain': 800, + 'wifi-enablement': 7200, +} + +tests_nano = [ + 'device-tree', + 'gatortests', + 'ltp', + 'perf', + 'pwrmgmt', + 'smoke-tests-basic', + 'network-tests-basic', +] + + +# CI base URL +ci_base_url = 'https://ci.linaro.org/jenkins/job/' +# Snapshots base URL +snapshots_url = 'https://snapshots.linaro.org' + +class LAVADeviceBase(object): + """ + Base class for definition of the device type and target in lava job. + """ + + def __init__(self, name=None): + self.name = name + + +class LAVADeviceType(LAVADeviceBase): + """ + Representation the definition of the device type in lava job. + """ + + +class LAVADeviceTarget(LAVADeviceBase): + """ + Representation the definition of the device target in lava job. + """ + + +def obfuscate_credentials(s): + return re.sub(r'([^ ]:).+?(@)', r'\1xxx\2', s) + + +def auth_headers(username, password): + return 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1] + + +def get_hwpack_type(job_name, hwpack_file_name="Undefined"): + hwpack_type = job_name.replace('/', ',') + ret_split = dict( + token.split('=') for token in hwpack_type.split(',') if '=' in token) + try: + return ret_split['hwpack'] + except KeyError, e: + # If hwpack key is not found, fallback to hwpack file name + return hwpack_file_name.split('_')[1].split('-')[1] + + +def get_rootfs_url(distribution, architecture, rootfs_type): + # Rootfs last successful build number + ci_url = '%s%s-%s-%s%s%s%s' % \ + (ci_base_url, + distribution, + architecture, + 'rootfs/rootfs=', + rootfs_type, + ',label=build', + '/lastSuccessfulBuild/buildNumber') + request = urllib2.Request(ci_url) + try: + response = urllib2.urlopen(request) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + print 'Failed to reach %s.' % ci_url + print 'Reason: ', e.reason + elif hasattr(e, 'code'): + print 'ci.linaro.org could not fulfill the request: %s' % ci_url + print 'Error code: ', e.code + sys.exit('Failed to get last successful rootfs build number.') + + rootfs_build_number = '%s' % eval(response.read()) + + # Rootfs last successful build timestamp + ci_url = '%s%s-%s-%s%s%s%s' % \ + (ci_base_url, + distribution, + architecture, + 'rootfs/rootfs=', + rootfs_type, + ',label=build', + '/lastSuccessfulBuild/buildTimestamp?format=yyyyMMdd') + request = urllib2.Request(ci_url) + try: + response = urllib2.urlopen(request) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + print 'Failed to reach %s.' % ci_url + print 'Reason: ', e.reason + elif hasattr(e, 'code'): + print 'ci.linaro.org could not fulfill the request: %s' % ci_url + print 'Error code: ', e.code + sys.exit('Failed to get last successful rootfs build timestamp.') + + rootfs_build_timestamp = '%s' % eval(response.read()) + + rootfs_file_name = 'linaro-utopic-%s-%s-%s.tar.gz' % \ + (rootfs_type, + rootfs_build_timestamp, + rootfs_build_number) + + rootfs_url = '%s/%s/%s/%s/%s/%s' % \ + (snapshots_url, + distribution, + 'images', + rootfs_type, + rootfs_build_number, + rootfs_file_name) + + return rootfs_url, rootfs_build_number + + +def lava_submit(config, lava_server): + print config + + skip_lava = os.environ.get('SKIP_LAVA') + if skip_lava is None: + # LAVA user + lava_user = os.environ.get('LAVA_USER') + if lava_user is None: + f = open('/var/run/lava/lava-user') + lava_user = f.read().strip() + f.close() + + # LAVA token + lava_token = os.environ.get('LAVA_TOKEN') + if lava_token is None: + f = open('/var/run/lava/lava-token') + lava_token = f.read().strip() + f.close() + + # LAVA server base URL + lava_server_root = lava_server.rstrip('/') + if lava_server_root.endswith('/RPC2'): + lava_server_root = lava_server_root[:-len('/RPC2')] + + try: + server_url = \ + 'https://{lava_user:>s}:{lava_token:>s}@{lava_server:>s}' + server = \ + xmlrpclib.ServerProxy(server_url.format( + lava_user=lava_user, + lava_token=lava_token, + lava_server=lava_server)) + lava_job_id = server.scheduler.submit_job(config) + job_is_single_node = isinstance(lava_job_id, int) + if job_is_single_node: + lava_job_details = server.scheduler.job_details(lava_job_id) + lava_id = lava_job_details['id'] + else: + lava_job_details = map(lambda sub_id: server.scheduler.job_details(sub_id), lava_job_id) + lava_id = lava_job_details[0]['id'] + print 'LAVA Job Id: %s, URL: https://%s/scheduler/job/%s' % \ + (lava_job_id, lava_server_root, lava_id) + if not job_is_single_node: + try: + lava_sub_jobs = [] + for details in lava_job_details: + lava_job_role = json.loads(details['definition'])['role'] + lava_sub_jobs.append('%s:%s:%s' % (details['id'], details['sub_id'], lava_job_role)) + print 'LAVA Sub-Jobs: %s' % ', '.join(lava_sub_jobs) + except (TypeError, ValueError): + # ignore ValueError JSON decode errors in case job is YAML based + pass + except xmlrpclib.ProtocolError, e: + print 'Error making a LAVA request:', obfuscate_credentials(str(e)) + sys.exit(1) + + json.dump({'lava_url': 'https://' + lava_server_root, + 'job_id': lava_job_id}, open('lava-job-info', 'w')) + else: + print 'LAVA job submission skipped.' + + sys.exit() + + +def get_job_list(): + job_list = ['CUSTOM_JSON_URL'] + sec_job_prefix = 'CUSTOM_JSON_URL_' + + for var in os.environ.keys(): + if var.startswith(sec_job_prefix): + job_list.append(var) + job_list.sort() + + return job_list + + +def replace(fp, pattern, subst): + print pattern + print subst + for line in fileinput.input(fp, inplace=1): + if pattern in line: + line = line.replace(pattern, subst) + sys.stdout.write(line) + fileinput.close() + + +def submit_job_from_url(): + """This routine updates a predefined job with the parameters specific + to this particular build""" + job_list = get_job_list() + for job in job_list: + lava_job_url = os.environ.get(job) + if lava_job_url is None: + print "Error: No CUSTOM_JSON_URL provided" + return + jobresource = urllib2.urlopen(lava_job_url) + jobjson = open('job.json','wb') + jobjson.write(jobresource.read()) + jobjson.close() + # Job name, defined by android-build, e.g. linaro-android_leb-panda + job_name = os.environ.get("JOB_NAME") + default_frontend_job_name = "~" + job_name.replace("_", "/", 1) + frontend_job_name = os.environ.get("FRONTEND_JOB_NAME", default_frontend_job_name) + + # Build number, defined by android-build, e.g. 61 + build_number = os.environ.get("BUILD_NUMBER") + + # download base URL, this may differ from job URL if we don't host + # downloads in Jenkins any more + download_url = "%s/%s/%s/" % ('%s/android/' % snapshots_url, + frontend_job_name, + build_number) + + # jenkins job name scheme doesn't apply for 96boards jobs so expect + # download_url to be provided by the job. + download_url = os.environ.get("DOWNLOAD_URL", download_url) + + # Set the file extension based on the type of artifacts + artifact_type = os.environ.get("MAKE_TARGETS", "tarball") + if artifact_type == "droidcore": + # Check if File extension is already defined + file_extension = os.environ.get("IMAGE_EXTENSION", "img") + else: + file_extension = "tar.bz2" + + boot_subst = "%s%s%s" % (download_url, "/boot.", file_extension) + system_subst = "%s%s%s" % (download_url, "/system.", file_extension) + userdata_subst = "%s%s%s" % (download_url, "/userdata.", file_extension) + cache_subst = "%s%s%s" % (download_url, "/cache.", file_extension) + + replace("job.json", "%%ANDROID_BOOT%%", boot_subst) + replace("job.json", "%%ANDROID_SYSTEM%%", system_subst) + replace("job.json", "%%ANDROID_DATA%%", userdata_subst) + replace("job.json", "%%ANDROID_CACHE%%", cache_subst) + replace("job.json", "%%ANDROID_META_NAME%%", job_name) + replace("job.json", "%%JOB_NAME%%", job_name) + replace("job.json", "%%ANDROID_META_BUILD%%", build_number) + replace("job.json", "%%ANDROID_META_URL%%", os.environ.get("BUILD_URL")) + replace("job.json", "%%BUNDLE_STREAM%%", os.environ.get('LAVA_STREAM', '/private/team/linaro/android-daily/')) + replace("job.json", "%%WA2_JOB_NAME%%", build_number) + replace("job.json", "%%DOWNLOAD_URL%%", download_url) + replace("job.json", "%%GERRIT_CHANGE_NUMBER%%", os.environ.get("GERRIT_CHANGE_NUMBER", "")) + replace("job.json", "%%GERRIT_PATCHSET_NUMBER%%", os.environ.get("GERRIT_PATCHSET_NUMBER", "")) + replace("job.json", "%%GERRIT_CHANGE_URL%%", os.environ.get("GERRIT_CHANGE_URL", "")) + replace("job.json", "%%GERRIT_CHANGE_ID%%", os.environ.get("GERRIT_CHANGE_ID", "")) + replace("job.json", "%%REFERENCE_BUILD_URL%%", os.environ.get("REFERENCE_BUILD_URL", "")) + replace("job.json", "%%CTS_MODULE_NAME%%", os.environ.get("CTS_MODULE_NAME", "")) + + # LAVA server URL + lava_server = os.environ.get('LAVA_SERVER', + 'validation.linaro.org/RPC2/') + + with open("job.json", 'r') as fin: + print fin.read() + + # Inject credentials after the job dump to avoid to leak + replace("job.json", "%%ART_TOKEN%%", os.environ.get("ART_TOKEN")) + replace("job.json", "%%ARTIFACTORIAL_TOKEN%%", os.environ.get("ARTIFACTORIAL_TOKEN")) + replace("job.json", "%%QA_REPORTS_TOKEN%%", os.environ.get("QA_REPORTS_TOKEN")) + replace("job.json", "%%AP_SSID%%", os.environ.get("AP_SSID")) + replace("job.json", "%%AP_KEY%%", os.environ.get("AP_KEY")) + + with open("job.json") as fd: + config = fd.read().strip() + lava_submit(config=config, lava_server=lava_server) + + sys.exit() + + +def main(): + '''Script entry point: return some JSON based on calling args. + We should be called from Jenkins and expect the following to be defined: + $HWPACK_BUILD_NUMBER $HWPACK_JOB_NAME HWPACK_FILE_NAME $DEVICE_TYPE + or, alternatively, $TARGET_PRODUCT $JOB_NAME $BUILD_NUMBER $BUILD_URL + ''' + + # LAVA server URL + lava_server = os.environ.get('LAVA_SERVER', + 'validation.linaro.org/RPC2/') + + # CI user + ci_user = os.environ.get('CI_USER') + # CI pass + ci_pass = os.environ.get('CI_PASS') + if ci_user is not None and ci_pass is not None: + auth = auth_headers(ci_user, ci_pass) + else: + auth = None + + if os.environ.get('TARGET_PRODUCT') is not None: + submit_job_from_url() + + # Allow to override completely the generated json + # using file provided by the user + custom_url = os.environ.get('CUSTOM_JSON_URL') + if custom_url is None: + custom_url = os.environ.get('CUSTOM_YAML_URL') + if custom_url is not None: + request = urllib2.Request(custom_url) + if auth: + request.add_header('Authorization', auth) + try: + response = urllib2.urlopen(request) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + print 'Failed to reach %s.' % custom_url + print 'Reason: ', e.reason + elif hasattr(e, 'code'): + print 'ci.linaro.org could not fulfill the request: %s' % \ + custom_url + print 'Error code: ', e.code + sys.exit('Failed to get last successful artifact.') + + if os.environ.get('CUSTOM_JSON_URL') is not None: + config = json.dumps(json.load( + response, object_pairs_hook=collections.OrderedDict), + indent=2, separators=(',', ': ')) + else: + config = response.read() + + lava_submit(config, lava_server) + + # Name of the hardware pack project + hwpack_job_name = os.environ.get('HWPACK_JOB_NAME') + # The hardware pack build number + hwpack_build_number = os.environ.get('HWPACK_BUILD_NUMBER') + # Hardware pack file name + hwpack_file_name = os.environ.get('HWPACK_FILE_NAME', 'Undefined') + if hwpack_file_name == 'Undefined': + sys.exit('Hardware pack is not defined.') + + # Device type + device_type = os.environ.get('DEVICE_TYPE', 'Undefined') + if device_type == 'Undefined': + sys.exit('Device type is not defined.') + + # Pre-built image URL + image_url = os.environ.get('IMAGE_URL', 'Undefined') + + # Hardware pack URL + hwpack_url = os.environ.get('HWPACK_URL', 'Undefined') + + # Test definitions repository + git_repo = os.environ.get('GIT_REPO', + 'git://git.linaro.org/qa/test-definitions.git') + + # Distribution, architecture and hardware pack type + distribution = os.environ.get('DISTRIBUTION', 'ubuntu') + architecture = os.environ.get('ARCHITECTURE', 'armhf') + if hwpack_job_name.startswith('package-and-publish'): + ret_split = hwpack_job_name.split('-', 3) + hwpack_type = ret_split[3] + elif hwpack_job_name.startswith('linux'): + hwpack_type = get_hwpack_type(hwpack_job_name) + else: + ret_split = hwpack_job_name.split('-', 2) + (distribution, architecture, hwpack_type) = \ + ret_split[0], ret_split[1], ret_split[2] + hwpack_type = get_hwpack_type(hwpack_job_name, hwpack_file_name) + + # Rootfs type, default is nano-lava + rootfs_type = os.getenv('ROOTFS_TYPE', 'nano-lava') + + # Bundle stream name + bundle_stream_name = os.environ.get( + 'BUNDLE_STREAM_NAME', + '/private/team/linaro/developers-and-community-builds/') + + lava_test_plan = os.environ.get('LAVA_TEST_PLAN') + if lava_test_plan is None: + # tests set specific to an image + tests = tests_nano + else: + lava_test_plan = lava_test_plan.strip("'") + tests = lava_test_plan.split() + + # vexpress doesn't support PM, so disable pwrmgmt + if device_type in ['vexpress-a9']: + try: + tests.remove('pwrmgmt') + except ValueError: + pass + + actions = [{'command': 'deploy_linaro_image'}] + deploy_image_parameters = {} + metadata = {} + + if image_url == 'Undefined': + # Convert CI URLs to snapshots URLs + if hwpack_url == 'Undefined': + if hwpack_job_name.startswith('package-and-publish'): + hwpack_job_name_fixup = hwpack_job_name.replace('.', '_') + hwpack_url = '%s/%s/%s/%s/%s/%s' % \ + (snapshots_url, + 'kernel-hwpack', + hwpack_job_name_fixup, + hwpack_job_name, + hwpack_build_number, + hwpack_file_name) + elif hwpack_job_name.startswith('linux'): + hwpack_url = '%s/%s/%s-%s/%s/%s' % \ + (snapshots_url, + 'kernel-hwpack', + hwpack_job_name.split('/')[0], + hwpack_type, + hwpack_build_number, + hwpack_file_name) + else: + hwpack_url = '%s/%s/%s/%s/%s/%s' % \ + (snapshots_url, + distribution, + 'hwpacks', + hwpack_type, + hwpack_build_number, + hwpack_file_name) + + (rootfs_url, rootfs_build_number) = get_rootfs_url(distribution, + architecture, + rootfs_type) + + deploy_image_parameters['hwpack'] = hwpack_url + deploy_image_parameters['rootfs'] = rootfs_url + metadata['rootfs.type'] = rootfs_type + metadata['rootfs.build'] = rootfs_build_number + else: + deploy_image_parameters['image'] = image_url + + metadata['hwpack.type'] = hwpack_type + metadata['hwpack.build'] = hwpack_build_number + metadata['distribution'] = distribution + + deploy_image_parameters_url = os.environ.get('DEPLOY_IMAGE_PARAMETERS_URL') + if deploy_image_parameters_url is not None: + request = urllib2.Request(deploy_image_parameters_url) + if auth: + request.add_header('Authorization', auth) + try: + response = urllib2.urlopen(request) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + print 'Failed to reach %s.' % deploy_image_parameters_url + print 'Reason: ', e.reason + elif hasattr(e, 'code'): + print 'ci.linaro.org could not fulfill the request: %s' % \ + deploy_image_parameters_url + print 'Error code: ', e.code + sys.exit('Failed to get last successful artifact.') + + deploy_image_parameters.update(json.load(response, object_pairs_hook=collections.OrderedDict)) + + actions[0]['parameters'] = deploy_image_parameters + + metadata_url = os.environ.get('METADATA_URL') + if metadata_url is not None: + request = urllib2.Request(metadata_url) + if auth: + request.add_header('Authorization', auth) + try: + response = urllib2.urlopen(request) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + print 'Failed to reach %s.' % metadata_url + print 'Reason: ', e.reason + elif hasattr(e, 'code'): + print 'ci.linaro.org could not fulfill the request: %s' % \ + metadata_url + print 'Error code: ', e.code + sys.exit('Failed to get last successful artifact.') + + metadata.update(json.load(response, object_pairs_hook=collections.OrderedDict)) + + actions[0]['metadata'] = metadata + + if len(tests) == 0: + actions.append({ + 'command': 'boot_linaro_image' + }) + + boot_image_parameters_url = os.environ.get('BOOT_IMAGE_PARAMETERS_URL') + if boot_image_parameters_url is not None: + request = urllib2.Request(boot_image_parameters_url) + if auth: + request.add_header('Authorization', auth) + try: + response = urllib2.urlopen(request) + except urllib2.URLError, e: + if hasattr(e, 'reason'): + print 'Failed to reach %s.' % boot_image_parameters_url + print 'Reason: ', e.reason + elif hasattr(e, 'code'): + print 'ci.linaro.org could not fulfill the request: %s' % \ + boot_image_parameters_url + print 'Error code: ', e.code + sys.exit('Failed to get last successful artifact.') + + boot_image_parameters = json.load(response, object_pairs_hook=collections.OrderedDict) + if {'command': 'boot_linaro_image'} not in actions: + actions.append({ + 'command': 'boot_linaro_image' + }) + actions[1]['parameters'] = boot_image_parameters + + if len(tests) > 0: + if distribution == 'quantal' or distribution == 'raring': + distribution = 'ubuntu' + for test in tests: + test_list = [({'git-repo': git_repo, + 'testdef': '{distribution:>s}/{test:>s}.yaml'.format( + distribution=distribution, test=test)})] + + actions.append({ + 'command': 'lava_test_shell', + 'parameters': { + 'timeout': tests_timeout.get(test, 18000), + 'testdef_repos': test_list + } + }) + + actions.append({ + 'command': 'submit_results', + 'parameters': { + 'stream': bundle_stream_name, + 'server': '%s%s' % ('https://', lava_server) + } + }) + + # XXX Global timeout in LAVA is hardcoded to 24h (24 * 60 60) + # https://bugs.launchpad.net/bugs/1226017 + # Set to 172800s (48h) to workaround the limitation + # A sane default is 900s (15m) + config = json.dumps({'timeout': 172800, + 'actions': actions, + 'job_name': '%s%s/%s/' % (ci_base_url, + hwpack_job_name, + hwpack_build_number), + 'device_type': device_type, + }, indent=2, separators=(',', ': ')) + + lava_submit(config, lava_server) + + +if __name__ == '__main__': + main() diff --git a/post-build-lava/submit_for_testing.py b/post-build-lava/submit_for_testing.py new file mode 100644 index 00000000..f4236733 --- /dev/null +++ b/post-build-lava/submit_for_testing.py @@ -0,0 +1,282 @@ +import argparse +import os +import re +import requests +import sys +import StringIO +from copy import deepcopy +from string import Template +from jinja2 import Environment, FileSystemLoader, StrictUndefined +from ruamel.yaml import YAML + + +try: + from urllib.parse import urlsplit +except ImportError: + from urlparse import urlsplit + + +# Templates base path +template_base_path = 'configs/openembedded-lkft/lava-job-definitions' +testplan_base_path = 'configs/openembedded-lkft/lava-job-definitions/' +testplan_device_path = 'devices/' +# Snapshots base URL +snapshots_url = 'https://snapshots.linaro.org/openembedded/lkft' + +def parse_template(yaml_string): + ''' + Round trip lava_job through ruamel to test parsing and + improve formatting. Comments are preserved. + + In: yaml-formatted string + Out: validated yaml-formatted string + ''' + yaml = YAML() + # ruamel does not provide a mechanism to dump to string, so use StringIO + # to catch it + output = StringIO.StringIO() + yaml.dump(yaml.load(yaml_string), output) + # strip empty lines from output + return re.sub(r'^\s*$\n', '', output.getvalue(), flags=re.MULTILINE) + +def get_job_name(lava_job_string): + ''' + In: yaml-formatted string + Out: LAVA job's name + ''' + yaml = YAML() + lava_job = yaml.load(lava_job_string) + return lava_job['job_name'] + +def _load_template(template_name, template_path, device_type): + template = '' + template_file_name = '' + + if template_name: + template_file_name = "%s/%s/%s" % (template_path, + device_type, + template_name) + if os.path.exists(template_file_name): + with open(template_file_name, 'r') as f: + template = f.read() + else: + print('template (%s) was specified but not exists' % + template_file_name) + sys.exit(1) + + return template, template_file_name + + +def _submit_to_squad(lava_job, lava_url_base, qa_server_api, qa_server_base, qa_token, quiet): + headers = { + "Auth-Token": qa_token + } + + try: + data = { + "definition": lava_job, + "backend": urlsplit(lava_url_base).netloc # qa-reports backends are named as lava instances + } + print("Submit to: %s" % qa_server_api) + results = requests.post(qa_server_api, data=data, headers=headers, + timeout=31) + if results.status_code < 300: + print("%s/testjob/%s %s" % (qa_server_base, results.text, get_job_name(lava_job))) + else: + print(results.status_code) + print(results.text) + except requests.exceptions.RequestException as err: + print("QA Reports submission failed") + if not quiet: + print("offending job definition:") + print(lava_job) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--device-type", + help="Device type in LAVA", + dest="device_type", + required=True) + parser.add_argument("--environment", + help="User specified the environment name, prefix or suffix won't be used", + dest="environment", + default="") + parser.add_argument("--env-prefix", + help="Prefix for the environment name", + dest="env_prefix", + default="") + parser.add_argument("--env-suffix", + help="Suffix for the environment name", + dest="env_suffix", + default="") + parser.add_argument("--build-number", + help="Build number", + dest="build_number", + required=True) + parser.add_argument("--qa-server-team", + help="Team in QA Reports service", + dest="qa_server_team", + required=True) + parser.add_argument("--qa-server-project", + help="Project in QA Reports service", + dest="qa_server_project", + required=True) + parser.add_argument("--qa-server", + help="QA Reports server", + dest="qa_server", + default="https://qa-reports.linaro.org") + parser.add_argument("--qa-token", + help="QA Reports token", + dest="qa_token", + default=os.environ.get('QA_REPORTS_TOKEN')) + parser.add_argument("--lava-server", + help="LAVA server URL", + dest="lava_server", + required=True) + parser.add_argument("--git-commit", + help="git commit ID", + dest="git_commit", + required=True) + parser.add_argument("--template-path", + help="Path to LAVA job templates", + dest="template_path", + default=template_base_path) + parser.add_argument("--testplan-path", + help="Path to Jinja2 LAVA job templates", + dest="testplan_path", + default=testplan_base_path) + parser.add_argument("--testplan-device-path", + help="Relative path to Jinja2 device deployment fragments", + dest="testplan_device_path", + default=testplan_device_path) + parser.add_argument("--template-base-pre", + help="base template used to construct templates, previous", + dest="template_base_pre") + parser.add_argument("--template-base-post", + help="base template used to construct templates, posterior", + dest="template_base_post") + parser.add_argument("--template-names", + help="list of the templates to submit for testing", + dest="template_names", + nargs="+", + default=[]) + parser.add_argument("--test-plan", + help="""list of the Jinja2 templates to submit for testing. + It is assumed that the templates produce valid LAVA job + definitions. All varaibles are substituted using Jinja2 + engine. This includes environment variables.""", + dest="test_plan", + nargs="+", + default=[]) + parser.add_argument("--dry-run", + help="""Prepare and write templates to tmp/. + Don't submit to actual servers.""", + action='store_true', + dest="dryrun") + parser.add_argument("--quiet", + help="Only output the final qa-reports URL", + action='store_true', + dest="quiet") + + args, _ = parser.parse_known_args() + + output_path = "tmp" + if args.dryrun: + if not os.path.exists(output_path): + os.mkdir(output_path) + if args.qa_token is None and not args.dryrun: + print("QA_REPORTS_TOKEN is missing") + sys.exit(1) + + qa_server_base = args.qa_server + if not (qa_server_base.startswith("http://") or qa_server_base.startswith("https://")): + qa_server_base = "https://" + qa_server_base + qa_server_team = args.qa_server_team + qa_server_project = args.qa_server_project + qa_server_build = args.git_commit + + if not args.environment: + # when user not specify value for the environment option, + # use the device_type as before + qa_server_env = args.env_prefix + args.device_type + args.env_suffix + else: + # when user specified value for the environment option, + # use the user specified value + qa_server_env = args.environment + + qa_server_api = "%s/api/submitjob/%s/%s/%s/%s" % ( + qa_server_base, + qa_server_team, + qa_server_project, + qa_server_build, + qa_server_env) + lava_server = args.lava_server + if not (lava_server.startswith("http://") or lava_server.startswith("https://")): + lava_server = "https://" + lava_server + lava_url_base = "%s://%s/" % (urlsplit(lava_server).scheme, urlsplit(lava_server).netloc) + + template_base_pre, _ = _load_template(args.template_base_pre, + args.template_path, + args.device_type) + template_base_post, _ = _load_template(args.template_base_post, + args.template_path, + args.device_type) + lava_jobs = [] + for test in args.template_names: + test_template, template_file_name = _load_template(test, + args.template_path, + args.device_type) + if template_base_pre: + test_template = "%s\n%s" % (template_base_pre, test_template) + if template_base_post: + test_template = "%s\n%s" % (test_template, template_base_post) + + template = Template(test_template) + print("using template: %s" % template_file_name) + lava_job = template.substitute(os.environ) + lava_job = parse_template(lava_job) + lava_jobs.append(lava_job) + + if not args.quiet: + print(lava_job) + if args.dryrun: + testpath = os.path.join(output_path, args.device_type, test) + if not os.path.exists(os.path.dirname(testpath)): + os.makedirs(os.path.dirname(testpath)) + with open(os.path.join(testpath), 'w') as f: + f.write(lava_job) + + THIS_DIR = os.path.abspath(args.testplan_path) + # prevent creating templates when variables are missing + j2_env = Environment(loader=FileSystemLoader(THIS_DIR, followlinks=True), undefined=StrictUndefined) + context = deepcopy(os.environ) + context.update({"device_type": os.path.join(args.testplan_device_path, args.device_type)}) + for test in args.test_plan: + ''' Prepare lava jobs ''' + lava_job = j2_env.get_template(test).render(context) + lava_job = parse_template(lava_job) + lava_jobs.append(lava_job) + + if not args.quiet: + print(lava_job) + if args.dryrun: + testpath = os.path.join(output_path, args.device_type, test) + if not os.path.exists(os.path.dirname(testpath)): + os.makedirs(os.path.dirname(testpath)) + with open(os.path.join(testpath), 'w') as f: + f.write(lava_job) + + for lava_job in lava_jobs: + ''' Submit lava jobs ''' + if not args.dryrun: + _submit_to_squad(lava_job, + lava_url_base, + qa_server_api, + qa_server_base, + args.qa_token, + args.quiet) + + +if __name__ == "__main__": + main() |