diff --git a/mozci/mozci.py b/mozci/mozci.py index f211046..7a47e82 100644 --- a/mozci/mozci.py +++ b/mozci/mozci.py @@ -14,10 +14,10 @@ import logging -from mozci.platforms import determine_upstream_builder, is_downstream, filter_buildernames,\ - build_talos_buildernames_for_repo +from mozci.platforms import determine_upstream_builder, is_downstream, \ + filter_buildernames, build_talos_buildernames_for_repo from mozci.sources import allthethings, buildapi, buildjson, pushlog -from mozci.query_jobs import PENDING, RUNNING, SUCCESS, UNKNOWN,\ +from mozci.query_jobs import PENDING, RUNNING, SUCCESS, UNKNOWN, \ COALESCED, BuildApi, TreeherderApi from mozci.utils.misc import _all_urls_reachable from mozci.utils.transfer import path_to_file, clean_directory diff --git a/mozci/scheduling.py b/mozci/scheduling.py new file mode 100644 index 0000000..ce68641 --- /dev/null +++ b/mozci/scheduling.py @@ -0,0 +1,28 @@ +""" +This module allow us to interact with the various scheduling systems +in a very generic manner. + +Defined in here: + * BaseSchedulingClient + * TaskclusterSchedulingClient +""" +from __future__ import absolute_import +from abc import ABCMeta, abstractmethod + +from mozci.sources import taskcluster_ + + +class BaseSchedulingClient: + """ Base class for common scheduling methods. """ + + __metaclass__ = ABCMeta + + @abstractmethod + def retrigger(self, uuid, **kwargs): + pass + + +class TaskclusterSchedulingClient(BaseSchedulingClient): + + def retrigger(self, uuid, **kwargs): + taskcluster_.retrigger_task(task_id=uuid, **kwargs) diff --git a/mozci/scripts/alltalos.py b/mozci/scripts/alltalos.py index 8713e77..2f6158c 100755 --- a/mozci/scripts/alltalos.py +++ b/mozci/scripts/alltalos.py @@ -76,10 +76,9 @@ def main(): options = parse_args() if options.debug: - LOG = setup_logging(logging.DEBUG) - LOG.info("Setting DEBUG level") + setup_logging(logging.DEBUG) else: - LOG = setup_logging(logging.INFO) + setup_logging() pgo = False if options.repo_name in PGO_ONLY_BRANCHES or options.pgo: diff --git a/mozci/scripts/generate_triggercli.py b/mozci/scripts/generate_triggercli.py index d18baf4..4553c34 100644 --- a/mozci/scripts/generate_triggercli.py +++ b/mozci/scripts/generate_triggercli.py @@ -30,32 +30,30 @@ 3) Remove the --dry-run parameter and actually trigger intermittents via trigger.py script. """ -import bugsy import logging import os + from argparse import ArgumentParser + +import bugsy + from mozci.mozci import query_repo_name_from_buildername +from mozci.utils.misc import setup_logging bugzilla = bugsy.Bugsy() -logging.basicConfig(format='%(asctime)s %(levelname)s:\t %(message)s', - datefmt='%m/%d/%Y %I:%M:%S') -LOG = logging.getLogger() -LOG.setLevel(logging.INFO) +LOG = setup_logging() def main(): + global LOG + options = parse_args() bugs = [] assert options.bug_no or options.test_name, \ "Either call this with --bug-no or with --test-name" if options.debug: - LOG.setLevel(logging.DEBUG) - logging.getLogger("requests").setLevel(logging.DEBUG) - LOG.info("Setting DEBUG level") - else: - LOG.setLevel(logging.INFO) - # requests is too noisy and adds no value + LOG = setup_logging(logging.DEBUG) if options.bug_no: bugs.append(options.bug_no) diff --git a/mozci/scripts/misc/taskcluster_retrigger.py b/mozci/scripts/misc/taskcluster_retrigger.py new file mode 100644 index 0000000..187b111 --- /dev/null +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -0,0 +1,49 @@ +''' +taskcluster_retrigger.py allows you to retrigger a task from TaskCluster +past its deadline. +''' +import logging + +from argparse import ArgumentParser + +from mozci.scheduling import TaskclusterSchedulingClient +from mozci.utils.misc import setup_logging + + +def main(): + parser = ArgumentParser() + parser.add_argument('-r', + action="store_true", + dest="retrigger", + help="It retriggers a TaskCluster task.") + + parser.add_argument("--debug", + action="store_true", + dest="debug", + help="set debug for logging.") + + parser.add_argument("--dry-run", + action="store_true", + dest="dry_run", + help="Dry run. No real actions are taken.") + + parser.add_argument('task_ids', + metavar='task_id', + type=str, + nargs='+', + help='Task IDs to work with.') + + options = parser.parse_args() + + if options.debug: + setup_logging(logging.DEBUG) + else: + setup_logging() + + if options.retrigger: + sch = TaskclusterSchedulingClient() + for t_id in options.task_ids: + sch.retrigger(uuid=t_id, dry_run=options.dry_run) + +if __name__ == "__main__": + main() diff --git a/mozci/scripts/trigger.py b/mozci/scripts/trigger.py index 025a54d..dedc2d4 100755 --- a/mozci/scripts/trigger.py +++ b/mozci/scripts/trigger.py @@ -198,7 +198,6 @@ def main(): if options.debug: LOG = setup_logging(logging.DEBUG) - LOG.info("Setting DEBUG level") else: LOG = setup_logging(logging.INFO) diff --git a/mozci/scripts/triggerbyfilters.py b/mozci/scripts/triggerbyfilters.py index 4dc8908..db7c5db 100644 --- a/mozci/scripts/triggerbyfilters.py +++ b/mozci/scripts/triggerbyfilters.py @@ -71,7 +71,6 @@ def main(): if options.debug: LOG = setup_logging(logging.DEBUG) - LOG.info("Setting DEBUG level") else: LOG = setup_logging(logging.INFO) diff --git a/mozci/sources/buildapi.py b/mozci/sources/buildapi.py index b5599c4..bc0817a 100755 --- a/mozci/sources/buildapi.py +++ b/mozci/sources/buildapi.py @@ -16,7 +16,8 @@ import requests -from mozci.utils.authentication import get_credentials, remove_credentials, AuthenticationError +from mozci.utils.authentication import get_credentials, remove_credentials, \ + AuthenticationError from mozci.utils.transfer import path_to_file LOG = logging.getLogger('mozci') diff --git a/mozci/sources/taskcluster_.py b/mozci/sources/taskcluster_.py new file mode 100644 index 0000000..2d9eb11 --- /dev/null +++ b/mozci/sources/taskcluster_.py @@ -0,0 +1,86 @@ +""" +This module allow us to interact with taskcluster through the taskcluster +client. +""" +import datetime +import json +import logging +import traceback + +import taskcluster as taskcluster_client + +LOG = logging.getLogger('mozci') +TASKCLUSTER_TOOLS_HOST = 'https://tools.taskcluster.net' + + +def retrigger_task(task_id, dry_run=False): + """ Given a task id (our uuid) we query it and build + a new task based on the old one which we schedule on TaskCluster. + + We don't call the rerun API since we can't rerun a task past + its deadline, instead we create a new task with a new taskGroupId, + expiration, creation and deadline values. + + task_id (int) - ID that identifies a task on Taskcluster + dry_run (bool) - Default to False. If True, it won't trigger + a task. + + returns - None for dry_run case, -1 for any failure a new task id (int) + in a succesful retrigger. + + http://docs.taskcluster.net/queue/api-docs/#createTask + """ + one_year = 365 + new_task_id = None + + try: + queue = taskcluster_client.Queue() + task = queue.task(task_id) + + LOG.debug("Original task: (Limit 1024 char)") + LOG.debug(str(json.dumps(task))[:1024]) + new_task_id = taskcluster_client.slugId() + + artifacts = task['payload'].get('artifacts', {}) + for artifact, definition in artifacts.iteritems(): + definition['expires'] = taskcluster_client.fromNow('%s days' % one_year) + + # The task group will be identified by the ID of the only + # task in the group + task['taskGroupId'] = new_task_id + # TC workers create public logs which are 365 days; if the task expiration + # date is the same or less than that we won't have logs for the task + task['expires'] = taskcluster_client.fromNow('%s days' % (one_year + 1)) + task['created'] = taskcluster_client.stringDate(datetime.datetime.utcnow()) + task['deadline'] = taskcluster_client.fromNow('24 hours') + + LOG.debug("Contents of new task: (Limit 1024 char)") + LOG.debug(str(task)[:1024]) + + if not dry_run: + LOG.info("Attempting to schedule new task with task_id: {}".format(new_task_id)) + result = queue.createTask(new_task_id, task) + LOG.debug(result) + LOG.info("{}/task-inspector/#{}".format(TASKCLUSTER_TOOLS_HOST, new_task_id)) + else: + LOG.info("Dry-run mode: Nothing was retriggered.") + + except taskcluster_client.exceptions.TaskclusterRestFailure as e: + traceback.print_exc() + new_task_id = -1 + + except taskcluster_client.exceptions.TaskclusterAuthFailure as e: + # Hack until we fix it in the issue + if str(e) == "Authorization Failed": + LOG.error("The taskclaster client that you specified is lacking " + "the right set of scopes.") + LOG.error("Run this same command with --debug and you will see " + "the missing scopes (the output comes from the " + "taskcluster python client)") + elif str(e) == "Authentication Error": + LOG.error("Make sure that you create permanent credentials and you " + "set these environment variables: TASKCLUSTER_CLIENT_ID & " + "TASKCLUSTER_ACCESS_TOKEN") + new_task_id = -1 + + return new_task_id diff --git a/mozci/utils/misc.py b/mozci/utils/misc.py index f78a2c4..e2c2ad1 100644 --- a/mozci/utils/misc.py +++ b/mozci/utils/misc.py @@ -44,7 +44,7 @@ def _all_urls_reachable(urls): return True -def setup_logging(level): +def setup_logging(level=logging.INFO): """ Save every message (including debug ones) to ~/.mozilla/mozci/mozci-debug.log. @@ -53,18 +53,21 @@ def setup_logging(level): As seen in: https://docs.python.org/2/howto/logging-cookbook.html#logging-to-multiple-destinations """ - LOG = logging.getLogger('mozci') + LOG = logging.getLogger() + + # Handler 1 - Store all debug messages in a specific file logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s:\t %(message)s', datefmt='%m/%d/%Y %I:%M:%S', filename=path_to_file('mozci-debug.log'), filemode='w') + # Handler 2 - Console output console = logging.StreamHandler() console.setLevel(level) # console does not use the same formatter specified in basicConfig # we have to set it again - formatter = logging.Formatter('%(asctime)s %(levelname)s:\t %(message)s', + formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s:\t %(message)s', datefmt='%m/%d/%Y %I:%M:%S') console.setFormatter(formatter) LOG.addHandler(console) @@ -72,4 +75,8 @@ def setup_logging(level): if level != logging.DEBUG: # requests is too noisy and adds no value logging.getLogger("requests").setLevel(logging.WARNING) + + if level == logging.DEBUG: + LOG.info("Setting DEBUG level") + return LOG diff --git a/setup.py b/setup.py index a5e6d60..ef25cf5 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ 'progressbar>=2.3', 'requests>=2.5.1', 'keyring>=5.3', + 'taskcluster>=0.0.22', 'treeherder-client>=1.4' ],