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)
예제 #4
0
    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()
예제 #6
0
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))
예제 #7
0
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,
        }
예제 #8
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")