#!/usr/bin/env python import sys import os import re import json import copy import xmlrpclib def obfuscate_credentials(s): return re.sub(r"([^ ]:).+?(@)", r"\1xxx\2", s) # Map a TARGET_PRODUCT to LAVA parameters. PRODUCT_MAP = { "pandaboard": { "test_device_type": "panda" }, "full_panda": { "test_device_type": "panda" }, "beagleboard": { "test_device_type": "beaglexm" }, "snowball": { "test_device_type": "snowball_sd" }, "iMX53": { "test_device_type": "mx53loco" }, "origen": { "test_device_type": "origen", }, "vexpress": { "test_device_type": "vexpress-a9", }, "vexpress_rtsm": { "test_device_type": "rtsm_ve-a15x4-a7x4", }, "full_maguro": { "test_device_type": "nexus", }, "full_arndale": { "test_device_type": "arndale", }, "hi4511": { "test_device_type": "k3v2", }, "fujitsu": { "test_device_type": "aa9", "test_stream": "/private/team/fujitsu/ci-LT-Fujitsu-working-tree/" }, } OPTION_SUFFIX = "_OPTION" TIMEOUT_SUFFIX = "_TIMEOUT" # Special token for LAVA_TEST_PLAN that allows the build owner to encode a # reboot in between test actions. REBOOT_TOKEN = '[system-reboot]' # CONSTANTS DEFINITION DEFAULT_LAVA_SCHEMA = 'https' DEFAULT_LAVA_URL = 'validation.linaro.org/RPC2/' DEFAULT_LAVA_STREAM = '/private/team/linaro/android-daily/' DEFAULT_LAVA_DIR = '/var/run/lava/' DEFAULT_LAVA_USER_FILE = '%s/lava-user' % DEFAULT_LAVA_DIR DEFAULT_LAVA_TOKEN_FILE = '%s/lava-token' % DEFAULT_LAVA_DIR DEFAULT_DOWNLOAD_BASE_URL = 'http://snapshots.linaro.org/android/' DEFAULT_TIMEOUT_VALUE = 18000 # Environment variables name ENV_LAVA_STREAM = 'LAVA_STREAM' ENV_LAVA_SERVER = 'LAVA_SERVER' ENV_LAVA_DEVICE = 'LAVA_DEVICE' ENV_LAVA_DEVICE_TYPE = 'LAVA_DEVICE_TYPE' ENV_LAVA_TEST_PLAN = 'LAVA_TEST_PLAN' ENV_LAVA_TEST_PLAN_SEC_PRE = 'LAVA_TEST_PLAN_SECONDARY_' ENV_LAVA_TOKEN_FILE = 'LAVA_TOKEN_FILE' ENV_LAVA_USER = 'LAVA_USER' # Build url, defined by android-build, e.g. # https://android-build.linaro.org/jenkins/job/linaro-android_leb-panda/61/ ENV_BUILD_URL = 'BUILD_URL' ENV_DEFAULT_TIMEOUT = 'DEFAULT_TIMEOUT' # Target product name, user defined, e.g. pandaboard ENV_TARGET_PRODUCT = 'TARGET_PRODUCT' ENV_BOOTLOADER_TYPE = 'BOOTLOADER_TYPE' 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 get_env_var(name, default=None): return os.environ.get(name, default) def get_lava_device_type_or_target(): ''' Here we support the specification by both, LAVA_DEVICE or LAVA_DEVICE_TYPE, and we can specify both of them at the same time. ''' devices = [] # allow to submit to a specific device test_device = get_env_var(ENV_LAVA_DEVICE) if test_device: targets = test_device.split(',') for dev_target in targets: dev_target = dev_target.strip() if dev_target: devices.append(LAVADeviceTarget(name=dev_target)) # allow overload lava device_type by build config test_device_type = get_env_var(ENV_LAVA_DEVICE_TYPE) target_product = get_env_var(ENV_TARGET_PRODUCT) # only use the default device type when neither of # LAVA_DEVICE or LAVA_DEVICE_TYPE is specified. # or say we will not use the value when any of # LAVA_DEVICE or LAVA_DEVICE_TYPE is specified. if (not test_device_type) and (not test_device): test_device_type = PRODUCT_MAP[target_product]["test_device_type"] if test_device_type: types = test_device_type.split(',') for dev_type in types: dev_type = dev_type.strip() if dev_type: devices.append(LAVADeviceType(name=dev_type)) return devices def get_schema_and_url(): lava_server = get_env_var(ENV_LAVA_SERVER) if lava_server is None: schema = DEFAULT_LAVA_SCHEMA url_no_schema = DEFAULT_LAVA_URL elif lava_server.find('://') >= 0: schema = lava_server[:lava_server.find('://')] url_no_schema = lava_server[lava_server.find('://') + len('://'):] else: ## the case that no https and http specified in the url ## like: validation.linaro.org/RPC2/ schema = DEFAULT_LAVA_SCHEMA url_no_schema = lava_server return (schema, url_no_schema) def get_plan_list(): plan_list = [ENV_LAVA_TEST_PLAN] sec_plan_prefix = ENV_LAVA_TEST_PLAN_SEC_PRE for var in os.environ.keys(): if var.startswith(sec_plan_prefix): plan_list.append(var) plan_list.sort() return plan_list def check_target_product(): # Board-specific parameters target_product = get_env_var(ENV_TARGET_PRODUCT) if target_product not in PRODUCT_MAP: # We don't know how to test this job, so skip testing. print "Don't know how to test this board. Skip testing." sys.exit(1) def gen_lava_test_shell_action(test_name=None, test_para=None): if not test_para or not test_name: return None key_value_ary = test_para.split() key_value_hash = {} for pair_line in key_value_ary: pair = pair_line.split('=', 1) if len(pair) != 2: continue if pair[0]: key_value_hash[pair[0]] = pair[1] parameters = {} if key_value_hash.get('timeout'): parameters['timeout'] = int(key_value_hash.get('timeout')) if test_name == 'lava-test-shell-url': if key_value_hash.get('url'): parameters["testdef_urls"] = [key_value_hash['url']] else: return None action = { "command": "lava_test_shell", "parameters": parameters } return action else: if not key_value_hash.get('repo'): return None repo = {} if test_name == 'lava-test-shell-git': repo = {"git-repo": key_value_hash.get('repo')} elif test_name == 'lava-test-shell-bzr': repo = {"bzr-repo": key_value_hash.get('repo')} else: return None if key_value_hash.get('revision'): repo["revision"] = key_value_hash.get('revision') if key_value_hash.get('testdef'): repo["testdef"] = key_value_hash.get('testdef') parameters["testdef_repos"] = [repo] action = { "command": "lava_test_shell", "parameters": parameters } return action def gen_reboot_action(test_para=None): action = {"command": "boot_linaro_android_image"} if not test_para: return action key_value_ary = test_para.split() key_value_hash = {} for pair_line in key_value_ary: pair = pair_line.split('=', 1) if len(pair) != 2: continue if pair[0]: key_value_hash[pair[0]] = pair[1] parameters = {} adb_check_val = key_value_hash.get('adb_check') if adb_check_val and adb_check_val.lower() == 'true': parameters['adb_check'] = True action['parameters'] = parameters return action def gen_lava_android_test_actions(tests=[]): actions = [] if len(tests) == 0: return actions test_actions = [] for test in tests: #support the special action for android test, #like android_install_cts_medias if test.startswith("android_"): continue #skip lava-test-shell test if test.startswith("lava-test-shell"): continue if test.startswith(REBOOT_TOKEN): continue ## support for test that specified with option like methanol,methanol(DEFAULT) if test.find('(') >= 0\ and test.rfind(')') > test.find('('): test = test[:test.find('(')].strip() if not test: continue test_actions.append(test) ## make the next test installation be able to execute ## when one test installation failed for test_action in list(set(test_actions)): inst_action = { "command": "lava_android_test_install", "parameters": { # ensure only unique test names "tests": [test_action] } } actions.append(inst_action) for test in tests: ## support for test that specified with option like methanol,methanol(DEFAULT) ## for this case, get the option from the test test_option = '' if test.find('(') >= 0 and test.rfind(')') > test.find('(') \ and test[:test.find('(')].strip(): test_option = test[test.find('(') + 1: test.rfind(')')] test = test[:test.find('(')].strip() if test.startswith('lava-test-shell'): run_action = gen_lava_test_shell_action(test_name=test, test_para=test_option) if run_action: actions.append(run_action) continue if test.startswith(REBOOT_TOKEN): run_action = gen_reboot_action(test_para=test_option) if run_action: actions.append(run_action) continue parameters = {} if not test_option: test_option = os.environ.get('%s%s' % (test.upper(), OPTION_SUFFIX)) if test_option: parameters['option'] = test_option timeout_option = os.environ.get('%s%s' % (test.upper(), TIMEOUT_SUFFIX)) if timeout_option: parameters['timeout'] = int(timeout_option) if test.startswith('android_'): run_action = { "command": test, "parameters": parameters } actions.append(run_action) elif test == 'hostshell-workload': config = "config.csv" workload_url = "ssh://linaro-lava@mombin.canonical.com/srv/linaro-private.git.linaro.org/people/bhoj/workload-automation.git" config_list = [] config_prefix = "WORKLOAD_CONFIG_" url_prefix = "WORKLOAD_URL" for var in os.environ.keys(): if var.startswith(config_prefix): config_list.append(var) elif var == url_prefix: workload_url = os.environ.get(var) config_list.sort() for var in config_list: config = os.environ.get(var) run_action = { "command": "lava_android_test_run", "parameters": { "option": "-c %s -g %s " % (config, workload_url), "test_name": test } } actions.append(run_action) actions.append({"command": "boot_linaro_android_image"}) else: parameters['test_name'] = test run_action = { "command": "lava_android_test_run", "parameters": parameters } actions.append(run_action) return actions def gen_test_plan_actions(test_plan=None): if test_plan is None: test_plan = os.environ.get("LAVA_TEST_PLAN") if test_plan is None: test_plan = '0xbench, glmark2, monkey' test_plans = test_plan.split(',') for index in range(len(test_plans)): test_plans[index] = test_plans[index].strip() if test_plans[index] == "test_android_0xbench": test_plans[index] = "0xbench" return gen_lava_android_test_actions(test_plans) def gen_command_action(commands=[], cmd_file=None, parser=None, timeout=None): parameters = None if commands: parameters = {'commands': commands} elif cmd_file: parameters = {'command_file': cmd_file} if parameters and parser: parameters['parser'] = parser if timeout: parameters['timeout'] = int(timeout) action = {"command": "lava_android_test_run_custom", "parameters": parameters} return action def gen_custom_actions(): test_actions = [] prefix = 'LAVA_TEST_' pat_suffix = '_PATTERN' sec_job_prefix = 'LAVA_TEST_PLAN_SECONDARY_' test_list = [] for var in os.environ.keys(): if var.startswith(prefix) and (not var.endswith(pat_suffix)) \ and (var != 'LAVA_TEST_PLAN') \ and (not var.startswith(sec_job_prefix)) \ and (not var.endswith(TIMEOUT_SUFFIX)): test_list.append(var) test_list.sort() for var in test_list: cmd = os.environ.get(var) pattern = os.environ.get('%s%s' % (var, pat_suffix)) timeout = os.environ.get('%s%s' % (var, TIMEOUT_SUFFIX)) test_actions.append(gen_command_action(commands=[cmd], parser=pattern, timeout=timeout)) return test_actions def gen_monkeyrunner_actions(): test_actions = [] prefix = 'MONKEY_RUNNER_URL_' test_list = [] for var in os.environ.keys(): if var.startswith(prefix) and (not var.endswith(TIMEOUT_SUFFIX)): test_list.append(var) test_list.sort() for var in test_list: url = os.environ.get(var) parameters = {"url": url} timeout = os.environ.get('%s%s' % (var, TIMEOUT_SUFFIX)) if timeout: parameters['timeout'] = int(timeout) action = { "command": "lava_android_test_run_monkeyrunner", "parameters": parameters } test_actions.append(action) return test_actions def gen_test_actions(): test_actions = [] test_actions.extend(gen_custom_actions()) test_actions.extend(gen_test_plan_actions()) test_actions.extend(gen_monkeyrunner_actions()) return test_actions def gen_deploy_action(): # Job name, defined by android-build, e.g. linaro-android_leb-panda job_name = os.environ.get("JOB_NAME") frontend_job_name = "~" + job_name.replace("_", "/", 1) # 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/" % (DEFAULT_DOWNLOAD_BASE_URL, frontend_job_name, build_number) # Set the file extension based on the type of artifacts artifact_type = os.environ.get("MAKE_TARGETS", "tarball") if artifact_type == "droidcore": file_extension = "img" else: file_extension = "tar.bz2" action = {"command": "deploy_linaro_android_image", "parameters": { "boot": "%s%s%s" % (download_url, "/boot.", file_extension), "system": "%s%s%s" % (download_url, "/system.", file_extension), "data": "%s%s%s" % (download_url, "/userdata.", file_extension) }, "metadata": { "android.name": job_name, "android.build": '%s' % build_number, "android.url": get_env_var(ENV_BUILD_URL) } } bootloadertype = get_env_var(ENV_BOOTLOADER_TYPE) if bootloadertype == "uefi": action["parameters"]["bootloadertype"] = "uefi" return action def gen_boot_action(): # Some devices need not boot to GUI like the Tiny Android builds and builds # which need a proprietary binary overlay to be installed before expecting # GUI. boot_action = {"command": "boot_linaro_android_image"} wait_for_homescreen = os.environ.get("LAVA_WAIT_FOR_HOMESCREEN", 'true') if wait_for_homescreen.lower() in ['0', 'false', 'no']: boot_action["parameters"] = {"wait_for_home_screen": False} return boot_action def gen_install_binaries_action(): # User can disable the installation of android binaries (doing this will # disable hardware acceleration) ENV_LAVA_ANDROID_BINARIES = 'LAVA_ANDROID_BINARIES' enable_binaries = get_env_var(ENV_LAVA_ANDROID_BINARIES, 'false') # Not set, default to False, because this is relevant only for panda # from Vishal if enable_binaries.lower() in ['1', 'true', 'yes']: return {"command": "android_install_binaries"} def gen_submit_action(): (schema, url_no_schema) = get_schema_and_url() schema_url = '%s://%s' % (schema, url_no_schema) lava_stream = get_env_var(ENV_LAVA_STREAM, DEFAULT_LAVA_STREAM) target_product = get_env_var(ENV_TARGET_PRODUCT) submit_action = {"command": "submit_results_on_host", "parameters": { "server": schema_url, "stream": PRODUCT_MAP[target_product].get( "test_stream", lava_stream) } } return submit_action def gen_config(actions=[], device=None): if not isinstance(device, LAVADeviceBase): print "Not supported type of device %s" % type(device) return None # Set the default timeout for all test, also used as the timeout value of # the total job when it's value bigger than 24 * 60 * 60 # if this value is not set, then use the 18000 seconds as the default value default_timeout = get_env_var(ENV_DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_VALUE) config_json = {"job_name": get_env_var(ENV_BUILD_URL), "image_type": 'android', "timeout": int(default_timeout), "actions": actions } # test_device set will win over test_device_type # LAVA parameter naming could use more consistency if isinstance(device, LAVADeviceType): config_json["device_type"] = device.name else: config_json["target"] = device.name config = json.dumps(config_json, indent=4) print config return config def submit_job(config=None, plan=None): if (not config) or (not plan): print "Config or plan is None" return # get the lava token used for submit job lava_token_f = get_env_var(ENV_LAVA_TOKEN_FILE) if lava_token_f is None: lava_token_f = DEFAULT_LAVA_TOKEN_FILE else: lava_token_f = '%s/%s' % (DEFAULT_LAVA_DIR, lava_token_f) with open(lava_token_f) as fd: lava_token = fd.read().strip() # get the lava user used for submit job lava_user = get_env_var(ENV_LAVA_USER) if lava_user is None: f = open(DEFAULT_LAVA_USER_FILE) lava_user = f.read().strip() f.close() (schema, lava_server_no_schema) = get_schema_and_url() try: report_url = ("%(schema)s://%(lava_user)s:%(lava_token)s@" "%(lava_server_no_schema)s") % dict( schema=schema, lava_user=lava_user, lava_token=lava_token, lava_server_no_schema=lava_server_no_schema) server = xmlrpclib.ServerProxy(report_url) lava_job_id = server.scheduler.submit_job(config) lava_server_root = lava_server_no_schema.rstrip("/") if lava_server_root.endswith("/RPC2"): lava_server_root = lava_server_root[:-len("/RPC2")] except xmlrpclib.ProtocolError, e: print "Error making a LAVA request:", obfuscate_credentials(str(e)) return print "LAVA Job Id: %s, URL: %s://%s/scheduler/job/%s" % \ (lava_job_id, schema, lava_server_root, lava_job_id) if plan == "LAVA_TEST_PLAN": json.dump({ 'lava_url': "%s://%s" % (schema, lava_server_root), 'job_id': lava_job_id, }, open('out/lava-job-info', 'w')) else: json.dump({ 'lava_url': "%s://%s" % (schema, lava_server_root), 'job_id': lava_job_id, }, open('out/lava-job-info-' + plan, 'w')) 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: $TARGET_PRODUCT $JOB_NAME $BUILD_NUMBER $BUILD_URL""" check_target_product() common_actions = [gen_deploy_action()] install_binaries_action = gen_install_binaries_action() if install_binaries_action: common_actions.append(install_binaries_action) common_actions.append(gen_boot_action()) plan_list = get_plan_list() # Create a copy of common actions for plan in plan_list: actions = copy.deepcopy(common_actions) if plan == "LAVA_TEST_PLAN": actions.extend(gen_test_actions()) else: actions.extend(gen_test_plan_actions(get_env_var(plan))) actions.append(gen_submit_action()) devices = get_lava_device_type_or_target() for dev in devices: config = gen_config(actions=actions, device=dev) submit_job(config=config, plan=plan) if __name__ == "__main__": main()