def run_test(args): if not TESTDROID: logger.error( "The environment variabels TESTDROID_URL, TESTDROID_APIKEY both need to be set." ) sys.exit(1) if args.bitbar_config is None: bitbar_configpath = os.path.join(modulepath, 'config', 'config.yml') else: bitbar_configpath = args.bitbar_config configuration.configure(bitbar_configpath, filespath=args.files) run_test_for_project(args.project_name)
def test_run_manager(args): if not TESTDROID: logger.error( "The environment variabels TESTDROID_URL, TESTDROID_APIKEY both need to be set." ) sys.exit(1) if args.bitbar_config is None: bitbar_configpath = os.path.join(modulepath, 'config', 'config.yml') else: bitbar_configpath = args.bitbar_config configuration.configure(bitbar_configpath, filespath=args.files) manager = TestRunManager(wait=args.wait, delete_bitbar_tests=args.delete_bitbar_tests) manager.run()
def run_test(args): if not TESTDROID: logger.error( "The environment variabels TESTDROID_URL, TESTDROID_APIKEY both need to be set." ) sys.exit(1) if args.bitbar_config is None: bitbar_configpath = os.path.join(modulepath, "config", "config.yml") else: bitbar_configpath = args.bitbar_config configuration.configure(bitbar_configpath, filespath=args.files, update_bitbar=args.update_bitbar) run_test_for_project(args.project_name) logger.info("run started for project '%s'" % args.project_name)
def process_active_runs(self): bitbar_projects = CACHE["projects"] bitbar_test_runs = CACHE["test_runs"] # init the temporary dict accumulation_dict = {} for project_name in bitbar_projects: accumulation_dict[project_name] = [] try: # gather all runs per project result = get_active_test_runs() except RequestResponseError as e: logger.error("process_active_runs: RequestResponseError received") logger.error(e) return for item in result: # remove user id from this (see configuration.py:configure_projects) project_name = item["projectName"].replace( "%s-" % (configuration.get_me_id()), "") # only accumulate for projects in our config if project_name in bitbar_projects: accumulation_dict[project_name].append(item) # replace current values with what we got above for project_name in bitbar_projects: stats = CACHE["projects"][project_name]["stats"] lock = CACHE["projects"][project_name]["lock"] with lock: bitbar_test_runs[project_name] = accumulation_dict[ project_name] stats["RUNNING"] = 0 stats["WAITING"] = 0 for test_run in bitbar_test_runs[project_name]: test_run_state = test_run["state"] stats[test_run_state] += 1 stats["IDLE"] = (stats["COUNT"] - stats["DISABLED"] - stats["OFFLINE"] - stats["RUNNING"]) if stats["IDLE"] < 0: stats["IDLE"] = 0
def test_run_manager(args): if not TESTDROID: logger.error( "The environment variabels TESTDROID_URL, TESTDROID_APIKEY both need to be set." ) sys.exit(1) if args.bitbar_config is None: bitbar_configpath = os.path.join(modulepath, "config", "config.yml") else: bitbar_configpath = args.bitbar_config try: configuration.configure(bitbar_configpath, filespath=args.files, update_bitbar=args.update_bitbar) except configuration.DuplicateProjectException as e: logger.error( "Duplicate project found! Please archive all but one and restart. Exiting..." ) logger.error(e) sys.exit(1) manager = TestRunManager(wait=args.wait) manager.run()
def configure(bitbar_configpath, filespath=None, update_bitbar=False): """Parse and load the configuration yaml file defining the Mozilla Bitbar test setup. :param bitbar_configpath: string path to the config.yml containing the Mozilla Bitbar configuration. :param filespath: string path to the files directory where application and test files are kept. """ global CONFIG, FILESPATH FILESPATH = filespath logger.info("configure: starting configuration") start = time.time() with open(bitbar_configpath) as bitbar_configfile: CONFIG = yaml.load(bitbar_configfile.read(), Loader=yaml.SafeLoader) logger.info("configure: performing checks") try: ensure_filenames_are_unique(CONFIG) except (ConfigurationFileException) as e: logger.error(e.message) sys.exit(1) expand_configuration() try: configuration_preflight() except ConfigurationFileException as e: logger.error(e) logger.error( "Configuration files seem to be missing! Please place and restart. Exiting..." ) sys.exit(1) configure_device_groups(update_bitbar=update_bitbar) configure_projects(update_bitbar=update_bitbar) end = time.time() diff = end - start logger.info("configure: configuration took {} seconds".format(diff))
def configure_projects(update_bitbar=False): """Configure projects from configuration. :param config: parsed yaml configuration containing a projects attribute which contains and object for each project. CONFIG['projects']['defaults'] contains values which will be set on the other projects if they are not already explicitly set. """ projects_config = CONFIG["projects"] project_total = len(projects_config) counter = 0 for project_name in projects_config: counter += 1 log_header = "configure_projects: {} ({}/{})".format( project_name, counter, project_total ) if project_name == "defaults": logger.info("{}: skipping".format(log_header)) continue logger.info("{}: configuring...".format(log_header)) project_config = projects_config[project_name] # for the project name at bitbar, add user id to the project_name # - prevents collision with other users' projects and allows us to # avoid having to share projects api_user_id = get_me_id() user_project_name = "%s-%s" % (api_user_id, project_name) bitbar_projects = get_projects(name=user_project_name) if len(bitbar_projects) > 1: raise DuplicateProjectException( "project {} ({}) has {} duplicates".format( project_name, user_project_name, len(bitbar_projects) - 1 ) ) elif len(bitbar_projects) == 1: bitbar_project = bitbar_projects[0] logger.debug( "configure_projects: using project {} ({})".format( bitbar_project, user_project_name ) ) else: if update_bitbar: bitbar_project = create_project( user_project_name, project_type=project_config["project_type"] ) logger.debug( "configure_projects: created project {} ({})".format( bitbar_project, user_project_name ) ) else: raise Exception( "Project {} ({}) does not exist, but not creating as not configured to update bitbar!".format( project_name, user_project_name ) ) framework_name = project_config["framework_name"] BITBAR_CACHE["frameworks"][framework_name] = get_frameworks( name=framework_name )[0] logger.info("{}: configuring test file".format(log_header)) file_name = project_config.get("test_file") if file_name: bitbar_files = get_files(name=file_name) if len(bitbar_files) > 0: bitbar_file = bitbar_files[-1] else: if update_bitbar: TESTDROID.upload_file(os.path.join(FILESPATH, file_name)) bitbar_file = get_files(name=file_name)[-1] else: raise Exception( "Test file {} not found and not configured to update bitbar configuration!".format( file_name ) ) BITBAR_CACHE["files"][file_name] = bitbar_file logger.info("{}: configuring application file".format(log_header)) file_name = project_config.get("application_file") if file_name: bitbar_files = get_files(name=file_name) if len(bitbar_files) > 0: bitbar_file = bitbar_files[-1] else: if update_bitbar: TESTDROID.upload_file(os.path.join(FILESPATH, file_name)) bitbar_file = get_files(name=file_name)[-1] else: raise Exception( "Application file {} not found and not configured to update bitbar configuration!".format( file_name ) ) BITBAR_CACHE["files"][file_name] = bitbar_file # Sync the base project properties if they have changed. if ( project_config["archivingStrategy"] != bitbar_project["archivingStrategy"] or project_config["archivingItemCount"] != bitbar_project["archivingItemCount"] or project_config["description"] != bitbar_project["description"] ): # project basic attributes changed in config, update bitbar version. if update_bitbar: bitbar_project = update_project( bitbar_project["id"], user_project_name, archiving_item_count=project_config["archivingItemCount"], archiving_strategy=project_config["archivingStrategy"], description=project_config["description"], ) else: logger.error( 'archivingStrategy: pc: "{}" bb: "{}"'.format( project_config["archivingStrategy"], bitbar_project["archivingStrategy"], ) ) logger.error( 'archivingItemCount: pc: "{}" bb: "{}"'.format( project_config["archivingItemCount"], bitbar_project["archivingItemCount"], ) ) logger.error( 'description: pc: "{}" bb: "{}"'.format( project_config["description"], bitbar_project["description"] ) ) raise Exception( "The remote configuration for {} ({}) differs from the local configuration, but not configured to update bitbar!".format( project_name, user_project_name ) ) additional_parameters = project_config["additional_parameters"] if "TC_WORKER_TYPE" in additional_parameters: # Add the TASKCLUSTER_ACCESS_TOKEN from the environment to # the additional_parameters in order that the bitbar # projects may be configured to use it. Non-taskcluster # projects such as mozilla-docker-build are not invoke by # Taskcluster currently. taskcluster_access_token_name = additional_parameters[ "TC_WORKER_TYPE" ].replace("-", "_") additional_parameters["TASKCLUSTER_ACCESS_TOKEN"] = os.environ[ taskcluster_access_token_name ] BITBAR_CACHE["projects"][project_name] = bitbar_project BITBAR_CACHE["projects"][project_name]["lock"] = threading.Lock() device_group_name = project_config["device_group_name"] device_group = BITBAR_CACHE["device_groups"][device_group_name] BITBAR_CACHE["projects"][project_name]["stats"] = { "COUNT": device_group["deviceCount"], "IDLE": 0, "OFFLINE_DEVICES": 0, "OFFLINE": 0, "DISABLED": 0, "RUNNING": 0, "WAITING": 0, }
def handle_queue(self, project_name, projects_config): logger.info("thread starting") stats = CACHE["projects"][project_name]["stats"] lock = CACHE["projects"][project_name]["lock"] while self.state == "RUNNING": project_config = projects_config[project_name] device_group_name = project_config["device_group_name"] additional_parameters = project_config["additional_parameters"] worker_type = additional_parameters.get("TC_WORKER_TYPE") with lock: if stats["OFFLINE"] or stats["DISABLED"]: logger.warning("{:10s} DISABLED {} OFFLINE {} {}".format( device_group_name, stats["DISABLED"], stats["OFFLINE"], ", ".join(stats["OFFLINE_DEVICES"]), )) taskcluster_provisioner_id = projects_config["defaults"][ "taskcluster_provisioner_id"] # create enough tests to service either the pending tasks or the number of idle # devices which do not already have a waiting test + a small logarithmic fudge # term based on the number of pending tasks (whichever is smaller). try: pending_tasks = get_taskcluster_pending_tasks( taskcluster_provisioner_id, worker_type) except requests.ConnectionError as e: logger.warning( "exception raised when calling get_taskcluster_pending_tasks." ) logger.warning(e) pending_tasks = 0 # warning: only take the log of positive non-zero numbers, or a # "ValueError: math domain error" will be raised jobs_to_start = min( pending_tasks, stats["IDLE"] - stats["WAITING"] + 1 + int(math.log10(1 + pending_tasks)), ) if jobs_to_start < 0: jobs_to_start = 0 if stats["RUNNING"] or stats["WAITING"]: logger.info( "COUNT {} IDLE {} OFFLINE {} DISABLED {} RUNNING {} WAITING {} PENDING {} STARTING {}" .format( stats["COUNT"], stats["IDLE"], stats["OFFLINE"], stats["DISABLED"], stats["RUNNING"], stats["WAITING"], pending_tasks, jobs_to_start, )) for _task in range(jobs_to_start): if self.state != "RUNNING": break try: if TESTING: logger.info( "TESTING MODE: Would be starting test run.") else: # if there are no devices assigned, the API will throw an exception # when we try to start, so detect and warn here. if stats["COUNT"] == 0: logger.warning( "Didn't try to start a job because there are no devices assigned." ) else: test_run = run_test_for_project(project_name) # increment so we don't start too many jobs before main thread updates stats with lock: stats["WAITING"] += 1 logger.info("test run {} started".format( test_run["id"])) except RequestResponseError as e: if e.status_code == 404 and re.search( ARCHIVED_FILE_REGEX, str(e)): logger.error( "Test files have been archived. Exiting so configuration is rerun..." ) logger.error("%s: %s" % (e.__class__.__name__, e)) self.state = "STOP" elif e.status_code == 404 and re.search( PROJECT_DOES_NOT_EXIST_REGEX, str(e)): logger.error( "Project does not exist!. Exiting so configuration is rerun..." ) logger.error("%s: %s" % (e.__class__.__name__, e)) self.state = "STOP" else: logger.error("%s: %s" % (e.__class__.__name__, e)) except Exception as e: logger.error( "Failed to create test run for group %s (%s: %s)." % (device_group_name, e.__class__.__name__, e), exc_info=True, ) if self.state == "RUNNING": time.sleep(self.wait) logger.info("thread exiting")