From 40b69afc6362d9bf8452463a0a0f3e0dedaf0bcc Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Thu, 30 Jul 2015 10:55:35 -0400 Subject: [PATCH 1/9] Bug 1149738 - Initial script to re-trigger tasks on TaskCluster --- mozci/scripts/misc/taskcluster_retrigger.py | 116 ++++++++++++++++++++ setup.py | 1 + 2 files changed, 117 insertions(+) create mode 100644 mozci/scripts/misc/taskcluster_retrigger.py diff --git a/mozci/scripts/misc/taskcluster_retrigger.py b/mozci/scripts/misc/taskcluster_retrigger.py new file mode 100644 index 0000000..832cbe2 --- /dev/null +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -0,0 +1,116 @@ +''' +taskcluster_retrigger.py allows you to retrigger a task from TaskCluster +past its deadline. + +The API used is: + * createTask [1] + +[1] http://docs.taskcluster.net/queue/api-docs/#createTask +''' +import datetime +import json +import logging +import sys + +from argparse import ArgumentParser + +import taskcluster + +logging.basicConfig(format='%(asctime)s %(levelname)s:\t %(message)s', + datefmt='%m/%d/%Y %I:%M:%S') +LOG = logging.getLogger() +LOG.setLevel(logging.DEBUG) + + +# http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-of-unicode-ones-from-json-in-python +def byteify(input): + if isinstance(input, dict): + return {byteify(key):byteify(value) for key,value in input.iteritems()} + elif isinstance(input, list): + return [byteify(element) for element in input] + elif isinstance(input, unicode): + return input.encode('utf-8') + else: + return input + + +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('task_ids', + metavar='task_id', + type=str, + nargs='+', + help='Task IDs to work with.') + + options = parser.parse_args() + + if options.debug: + LOG.setLevel(logging.DEBUG) + LOG.info("Setting DEBUG level") + else: + LOG.setLevel(logging.INFO) + + # requests is too noisy and adds no value + logging.getLogger("requests").setLevel(logging.WARNING) + + queue = taskcluster.Queue() + if options.retrigger: + # Since we can't rerun a task past its deadline, we create + # a new task with a new taskGroupId, expiration, creation and + # deadline values + try: + for t_id in options.task_ids: + task = queue.task(t_id) + LOG.debug("Original task:") + LOG.debug(json.dumps(task)) + new_task_id = taskcluster.slugId() + + artifacts = task['payload'].get('artifacts', {}) + for artifact, definition in artifacts.iteritems(): + definition['expires'] = taskcluster.fromNow('365 days') + + # The task group will be identified by the ID of the only + # task in the group + task['taskGroupId'] = new_task_id + task['expires'] = taskcluster.fromNow('48 hours') + task['created'] = taskcluster.stringDate(datetime.datetime.utcnow()) + task['deadline'] = taskcluster.fromNow('24 hours') + + # We need a json object rather than a Python dictionary + # to submit a task. + task = json.dumps(task) + LOG.info("Submitting new task with task_id: {}".format(new_task_id)) + LOG.debug("Contents of new task:") + LOG.debug(task) + result = queue.createTask(new_task_id, task) + LOG.debug(result) + LOG.info("https://tools.taskcluster.net/task-inspector/#{}/".format(new_task_id)) + + except taskcluster.exceptions.TaskclusterAuthFailure as e: + LOG.debug(str(e)) + # Hack until we fix it in the issue + if str(e) == "Authorization Failed": + raise e + LOG.info("Your credentials do not allow you to make this API call.") + LOG.info("Your permanent credentials need queue:rerun-task and " + "assume:scheduler-id:task-graph-scheduler/* as scopes to work") + LOG.info("This is defined under 'scopes' in " + "http://docs.taskcluster.net/queue/api-docs/#rerunTask") + elif str(e) == "Authentication Error": + LOG.info("Make sure that you create permanent credentials and you " + "set these environment variables: TASKCLUSTER_CLIENT_ID, " + "TASKCLUSTER_ACCESS_TOKEN") + sys.exit(1) + +if __name__ == "__main__": + main() 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' ], From 85fc493a63e0ffd57eb9eae6780843cce34bc453 Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Thu, 30 Jul 2015 15:16:14 -0400 Subject: [PATCH 2/9] Small changes for debugging --- mozci/scripts/misc/taskcluster_retrigger.py | 28 ++++++++------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/mozci/scripts/misc/taskcluster_retrigger.py b/mozci/scripts/misc/taskcluster_retrigger.py index 832cbe2..7fc2f1c 100644 --- a/mozci/scripts/misc/taskcluster_retrigger.py +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -22,18 +22,6 @@ LOG.setLevel(logging.DEBUG) -# http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-of-unicode-ones-from-json-in-python -def byteify(input): - if isinstance(input, dict): - return {byteify(key):byteify(value) for key,value in input.iteritems()} - elif isinstance(input, list): - return [byteify(element) for element in input] - elif isinstance(input, unicode): - return input.encode('utf-8') - else: - return input - - def main(): parser = ArgumentParser() parser.add_argument('-r', @@ -68,6 +56,7 @@ def main(): # Since we can't rerun a task past its deadline, we create # a new task with a new taskGroupId, expiration, creation and # deadline values + # XXX: check for credential env variables try: for t_id in options.task_ids: task = queue.task(t_id) @@ -77,18 +66,20 @@ def main(): artifacts = task['payload'].get('artifacts', {}) for artifact, definition in artifacts.iteritems(): + # XXX: We should check with gardnt if we got expiration dates + # lined up properly with the task expiration date definition['expires'] = taskcluster.fromNow('365 days') # The task group will be identified by the ID of the only # task in the group task['taskGroupId'] = new_task_id - task['expires'] = taskcluster.fromNow('48 hours') + task['expires'] = taskcluster.fromNow('366 days') task['created'] = taskcluster.stringDate(datetime.datetime.utcnow()) task['deadline'] = taskcluster.fromNow('24 hours') + # XXX: task['payload']['command'] might need to manipulate to + # extract any of the artifacts that were generated by the build that + # triggered this test job - # We need a json object rather than a Python dictionary - # to submit a task. - task = json.dumps(task) LOG.info("Submitting new task with task_id: {}".format(new_task_id)) LOG.debug("Contents of new task:") LOG.debug(task) @@ -97,10 +88,10 @@ def main(): LOG.info("https://tools.taskcluster.net/task-inspector/#{}/".format(new_task_id)) except taskcluster.exceptions.TaskclusterAuthFailure as e: - LOG.debug(str(e)) + raise e + ''' # Hack until we fix it in the issue if str(e) == "Authorization Failed": - raise e LOG.info("Your credentials do not allow you to make this API call.") LOG.info("Your permanent credentials need queue:rerun-task and " "assume:scheduler-id:task-graph-scheduler/* as scopes to work") @@ -111,6 +102,7 @@ def main(): "set these environment variables: TASKCLUSTER_CLIENT_ID, " "TASKCLUSTER_ACCESS_TOKEN") sys.exit(1) + ''' if __name__ == "__main__": main() From 2c15bdf95e7af2b223628cfbe87805ff156214ca Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Fri, 31 Jul 2015 12:52:32 -0400 Subject: [PATCH 3/9] * Unified a bit more how all scripts set up the logging * Refactored taskcluster_retrigger.py script to split out core logic * Created scheduling base class and taskcluster derived class * Moved taskcluster core logic under mozci/sources --- mozci/mozci.py | 6 +- mozci/scheduling.py | 28 ++++++++ mozci/scripts/alltalos.py | 1 - mozci/scripts/generate_triggercli.py | 18 ++--- mozci/scripts/misc/taskcluster_retrigger.py | 76 ++------------------ mozci/scripts/trigger.py | 1 - mozci/scripts/triggerbyfilters.py | 1 - mozci/sources/buildapi.py | 3 +- mozci/sources/taskcluster_.py | 80 +++++++++++++++++++++ mozci/utils/misc.py | 4 ++ 10 files changed, 131 insertions(+), 87 deletions(-) create mode 100644 mozci/scheduling.py create mode 100644 mozci/sources/taskcluster_.py 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..9b5222d 100755 --- a/mozci/scripts/alltalos.py +++ b/mozci/scripts/alltalos.py @@ -77,7 +77,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/generate_triggercli.py b/mozci/scripts/generate_triggercli.py index d18baf4..a4f0dc0 100644 --- a/mozci/scripts/generate_triggercli.py +++ b/mozci/scripts/generate_triggercli.py @@ -30,17 +30,16 @@ 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) def main(): @@ -50,12 +49,9 @@ def main(): "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") + LOG = setup_logging(logging.DEBUG) else: - LOG.setLevel(logging.INFO) - # requests is too noisy and adds no value + LOG = setup_logging(logging.INFO) 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 index 7fc2f1c..01c3b87 100644 --- a/mozci/scripts/misc/taskcluster_retrigger.py +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -5,21 +5,11 @@ The API used is: * createTask [1] -[1] http://docs.taskcluster.net/queue/api-docs/#createTask ''' -import datetime -import json -import logging -import sys - from argparse import ArgumentParser -import taskcluster - -logging.basicConfig(format='%(asctime)s %(levelname)s:\t %(message)s', - datefmt='%m/%d/%Y %I:%M:%S') -LOG = logging.getLogger() -LOG.setLevel(logging.DEBUG) +from mozci.scheduling import TaskclusterSchedulingClient +from mozci.utils.misc import setup_logging def main(): @@ -43,66 +33,14 @@ def main(): options = parser.parse_args() if options.debug: - LOG.setLevel(logging.DEBUG) - LOG.info("Setting DEBUG level") + LOG = setup_logging(logging.DEBUG) else: - LOG.setLevel(logging.INFO) + LOG = setup_logging(logging.INFO) - # requests is too noisy and adds no value - logging.getLogger("requests").setLevel(logging.WARNING) - - queue = taskcluster.Queue() if options.retrigger: - # Since we can't rerun a task past its deadline, we create - # a new task with a new taskGroupId, expiration, creation and - # deadline values - # XXX: check for credential env variables - try: - for t_id in options.task_ids: - task = queue.task(t_id) - LOG.debug("Original task:") - LOG.debug(json.dumps(task)) - new_task_id = taskcluster.slugId() - - artifacts = task['payload'].get('artifacts', {}) - for artifact, definition in artifacts.iteritems(): - # XXX: We should check with gardnt if we got expiration dates - # lined up properly with the task expiration date - definition['expires'] = taskcluster.fromNow('365 days') - - # The task group will be identified by the ID of the only - # task in the group - task['taskGroupId'] = new_task_id - task['expires'] = taskcluster.fromNow('366 days') - task['created'] = taskcluster.stringDate(datetime.datetime.utcnow()) - task['deadline'] = taskcluster.fromNow('24 hours') - # XXX: task['payload']['command'] might need to manipulate to - # extract any of the artifacts that were generated by the build that - # triggered this test job - - LOG.info("Submitting new task with task_id: {}".format(new_task_id)) - LOG.debug("Contents of new task:") - LOG.debug(task) - result = queue.createTask(new_task_id, task) - LOG.debug(result) - LOG.info("https://tools.taskcluster.net/task-inspector/#{}/".format(new_task_id)) - - except taskcluster.exceptions.TaskclusterAuthFailure as e: - raise e - ''' - # Hack until we fix it in the issue - if str(e) == "Authorization Failed": - LOG.info("Your credentials do not allow you to make this API call.") - LOG.info("Your permanent credentials need queue:rerun-task and " - "assume:scheduler-id:task-graph-scheduler/* as scopes to work") - LOG.info("This is defined under 'scopes' in " - "http://docs.taskcluster.net/queue/api-docs/#rerunTask") - elif str(e) == "Authentication Error": - LOG.info("Make sure that you create permanent credentials and you " - "set these environment variables: TASKCLUSTER_CLIENT_ID, " - "TASKCLUSTER_ACCESS_TOKEN") - sys.exit(1) - ''' + sch = TaskclusterSchedulingClient() + for t_id in options.task_ids: + sch.retrigger(uuid=t_id, dry_run=False) 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..dfcee9f --- /dev/null +++ b/mozci/sources/taskcluster_.py @@ -0,0 +1,80 @@ +""" +This module allow us to interact with taskcluster through the taskcluster +client. +""" +import datetime +import json +import logging +import sys +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. + + http://docs.taskcluster.net/queue/api-docs/#createTask + """ + try: + queue = taskcluster_client.Queue() + task = queue.task(task_id) + + LOG.debug("Original task:") + LOG.debug(json.dumps(task)) + new_task_id = taskcluster_client.slugId() + + artifacts = task['payload'].get('artifacts', {}) + for artifact, definition in artifacts.iteritems(): + # XXX: We should check with gardnt if we got expiration dates + # lined up properly with the task expiration date + definition['expires'] = taskcluster_client.fromNow('364 days') + + # The task group will be identified by the ID of the only + # task in the group + task['taskGroupId'] = new_task_id + # TC workers + task['expires'] = taskcluster_client.fromNow('366 days') + task['created'] = taskcluster_client.stringDate(datetime.datetime.utcnow()) + task['deadline'] = taskcluster_client.fromNow('24 hours') + # XXX: task['payload']['command'] might need to manipulate to + # extract any of the artifacts that were generated by the build that + # triggered this test job + + LOG.debug("Contents of new task:") + LOG.debug(task) + if not dry_run: + LOG.info("Submitting 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() + + except taskcluster_client.exceptions.TaskclusterAuthFailure as e: + # Hack until we fix it in the issue + if str(e) == "Authorization Failed": + LOG.info("The taskclaster client that you specified is lacking " + "the right set of scopes.") + LOG.info("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.info("Make sure that you create permanent credentials and you " + "set these environment variables: TASKCLUSTER_CLIENT_ID, " + "TASKCLUSTER_ACCESS_TOKEN") + sys.exit(1) diff --git a/mozci/utils/misc.py b/mozci/utils/misc.py index f78a2c4..419e44e 100644 --- a/mozci/utils/misc.py +++ b/mozci/utils/misc.py @@ -72,4 +72,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 From 9e8a8098107e8288c8fc69c39437828ae9cc29f8 Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Fri, 31 Jul 2015 15:44:06 -0400 Subject: [PATCH 4/9] Logging improvements --- mozci/scripts/alltalos.py | 4 ++-- mozci/scripts/generate_triggercli.py | 6 ++++-- mozci/scripts/misc/taskcluster_retrigger.py | 10 ++++------ mozci/sources/taskcluster_.py | 2 +- mozci/utils/misc.py | 7 +++++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/mozci/scripts/alltalos.py b/mozci/scripts/alltalos.py index 9b5222d..2f6158c 100755 --- a/mozci/scripts/alltalos.py +++ b/mozci/scripts/alltalos.py @@ -76,9 +76,9 @@ def main(): options = parse_args() if options.debug: - LOG = setup_logging(logging.DEBUG) + 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 a4f0dc0..4553c34 100644 --- a/mozci/scripts/generate_triggercli.py +++ b/mozci/scripts/generate_triggercli.py @@ -30,6 +30,7 @@ 3) Remove the --dry-run parameter and actually trigger intermittents via trigger.py script. """ +import logging import os from argparse import ArgumentParser @@ -40,9 +41,12 @@ from mozci.utils.misc import setup_logging bugzilla = bugsy.Bugsy() +LOG = setup_logging() def main(): + global LOG + options = parse_args() bugs = [] assert options.bug_no or options.test_name, \ @@ -50,8 +54,6 @@ def main(): if options.debug: LOG = setup_logging(logging.DEBUG) - else: - LOG = setup_logging(logging.INFO) 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 index 01c3b87..594e2af 100644 --- a/mozci/scripts/misc/taskcluster_retrigger.py +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -1,11 +1,9 @@ ''' taskcluster_retrigger.py allows you to retrigger a task from TaskCluster past its deadline. - -The API used is: - * createTask [1] - ''' +import logging + from argparse import ArgumentParser from mozci.scheduling import TaskclusterSchedulingClient @@ -33,9 +31,9 @@ def main(): options = parser.parse_args() if options.debug: - LOG = setup_logging(logging.DEBUG) + setup_logging(logging.DEBUG) else: - LOG = setup_logging(logging.INFO) + setup_logging() if options.retrigger: sch = TaskclusterSchedulingClient() diff --git a/mozci/sources/taskcluster_.py b/mozci/sources/taskcluster_.py index dfcee9f..b6ae40f 100644 --- a/mozci/sources/taskcluster_.py +++ b/mozci/sources/taskcluster_.py @@ -52,7 +52,7 @@ def retrigger_task(task_id, dry_run=False): LOG.debug("Contents of new task:") LOG.debug(task) if not dry_run: - LOG.info("Submitting new task with task_id: {}".format(new_task_id)) + 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( diff --git a/mozci/utils/misc.py b/mozci/utils/misc.py index 419e44e..4e6e911 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. @@ -54,17 +54,20 @@ def setup_logging(level): https://docs.python.org/2/howto/logging-cookbook.html#logging-to-multiple-destinations """ LOG = logging.getLogger('mozci') + + # 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) From 7b779c676087dba0a5d6e51b6206873dae721334 Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Fri, 31 Jul 2015 17:05:47 -0400 Subject: [PATCH 5/9] * Fix logging issue where we would only see messages from the 'mozci' logger instead of all loggers * Polish code and output to re-trigger task plus return a value to specify outcome * Improved docstring --- mozci/scripts/misc/taskcluster_retrigger.py | 7 ++- mozci/sources/taskcluster_.py | 58 ++++++++++++--------- mozci/utils/misc.py | 2 +- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/mozci/scripts/misc/taskcluster_retrigger.py b/mozci/scripts/misc/taskcluster_retrigger.py index 594e2af..187b111 100644 --- a/mozci/scripts/misc/taskcluster_retrigger.py +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -22,6 +22,11 @@ def main(): 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, @@ -38,7 +43,7 @@ def main(): if options.retrigger: sch = TaskclusterSchedulingClient() for t_id in options.task_ids: - sch.retrigger(uuid=t_id, dry_run=False) + sch.retrigger(uuid=t_id, dry_run=options.dry_run) if __name__ == "__main__": main() diff --git a/mozci/sources/taskcluster_.py b/mozci/sources/taskcluster_.py index b6ae40f..3150b75 100644 --- a/mozci/sources/taskcluster_.py +++ b/mozci/sources/taskcluster_.py @@ -22,59 +22,67 @@ def retrigger_task(task_id, dry_run=False): 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:") - LOG.debug(json.dumps(task)) + 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(): - # XXX: We should check with gardnt if we got expiration dates - # lined up properly with the task expiration date - definition['expires'] = taskcluster_client.fromNow('364 days') + 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 - task['expires'] = taskcluster_client.fromNow('366 days') + # 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') - # XXX: task['payload']['command'] might need to manipulate to - # extract any of the artifacts that were generated by the build that - # triggered this test job - LOG.debug("Contents of new task:") - LOG.debug(task) + 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) - ) + 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.info("The taskclaster client that you specified is lacking " - "the right set of scopes.") - LOG.info("Run this same command with --debug and you will see " - "the missing scopes (the output comes from the " - "taskcluster python client)") + 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.info("Make sure that you create permanent credentials and you " - "set these environment variables: TASKCLUSTER_CLIENT_ID, " - "TASKCLUSTER_ACCESS_TOKEN") - sys.exit(1) + 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 4e6e911..e2c2ad1 100644 --- a/mozci/utils/misc.py +++ b/mozci/utils/misc.py @@ -53,7 +53,7 @@ def setup_logging(level=logging.INFO): 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, From 315d8a3bb70d6315dcfa86b5fb12b5dba7549779 Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Fri, 31 Jul 2015 17:14:26 -0400 Subject: [PATCH 6/9] Fix tox issues --- mozci/sources/taskcluster_.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mozci/sources/taskcluster_.py b/mozci/sources/taskcluster_.py index 3150b75..2d9eb11 100644 --- a/mozci/sources/taskcluster_.py +++ b/mozci/sources/taskcluster_.py @@ -5,7 +5,6 @@ import datetime import json import logging -import sys import traceback import taskcluster as taskcluster_client @@ -66,7 +65,6 @@ def retrigger_task(task_id, dry_run=False): else: LOG.info("Dry-run mode: Nothing was retriggered.") - except taskcluster_client.exceptions.TaskclusterRestFailure as e: traceback.print_exc() new_task_id = -1 From a8e3a02a32ebf7f7cd805a0dbfa3ec51bb64b9f3 Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Wed, 5 Aug 2015 10:39:33 -0400 Subject: [PATCH 7/9] Rename mozci.sources.taskcluster_.py to mozci.sources.tc to not use _ at the end of a module name and avoid conflicting with the python package called taskcluster --- mozci/scheduling.py | 4 ++-- mozci/sources/{taskcluster_.py => tc.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename mozci/sources/{taskcluster_.py => tc.py} (100%) diff --git a/mozci/scheduling.py b/mozci/scheduling.py index ce68641..64ae87f 100644 --- a/mozci/scheduling.py +++ b/mozci/scheduling.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from abc import ABCMeta, abstractmethod -from mozci.sources import taskcluster_ +import mozci.sources.tc class BaseSchedulingClient: @@ -25,4 +25,4 @@ def retrigger(self, uuid, **kwargs): class TaskclusterSchedulingClient(BaseSchedulingClient): def retrigger(self, uuid, **kwargs): - taskcluster_.retrigger_task(task_id=uuid, **kwargs) + return tc.retrigger_task(task_id=uuid, **kwargs) diff --git a/mozci/sources/taskcluster_.py b/mozci/sources/tc.py similarity index 100% rename from mozci/sources/taskcluster_.py rename to mozci/sources/tc.py From 6074cc9432e194b19e1e89cc07687c9483a03743 Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Wed, 5 Aug 2015 13:55:36 -0400 Subject: [PATCH 8/9] Import tc module explicitely - taskcluster retrigger script changes: Remove retrigger option Log failure to retrigger - retrigger_task function in tc module: Return 0 for the dry_run case instead of None Make reference to bug wrt to expiration deadlines Create expiration of task to be exactly 24 hours from creation time Dump results of creating a task a json dump rather than a python dictionary --- mozci/scheduling.py | 2 +- mozci/scripts/misc/taskcluster_retrigger.py | 18 +++++++----------- mozci/sources/tc.py | 15 +++++++++------ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/mozci/scheduling.py b/mozci/scheduling.py index 64ae87f..dc8351e 100644 --- a/mozci/scheduling.py +++ b/mozci/scheduling.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from abc import ABCMeta, abstractmethod -import mozci.sources.tc +from mozci.sources import tc class BaseSchedulingClient: diff --git a/mozci/scripts/misc/taskcluster_retrigger.py b/mozci/scripts/misc/taskcluster_retrigger.py index 187b111..ef35e88 100644 --- a/mozci/scripts/misc/taskcluster_retrigger.py +++ b/mozci/scripts/misc/taskcluster_retrigger.py @@ -12,11 +12,6 @@ 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", @@ -36,14 +31,15 @@ def main(): options = parser.parse_args() if options.debug: - setup_logging(logging.DEBUG) + LOG = setup_logging(logging.DEBUG) else: - setup_logging() + LOG = setup_logging() - if options.retrigger: - sch = TaskclusterSchedulingClient() - for t_id in options.task_ids: - sch.retrigger(uuid=t_id, dry_run=options.dry_run) + sch = TaskclusterSchedulingClient() + for t_id in options.task_ids: + ret_code = sch.retrigger(uuid=t_id, dry_run=options.dry_run) + if ret_code < 0: + LOG.warning("We could not retrigger task %s" % t_id) if __name__ == "__main__": main() diff --git a/mozci/sources/tc.py b/mozci/sources/tc.py index 2d9eb11..32c64f2 100644 --- a/mozci/sources/tc.py +++ b/mozci/sources/tc.py @@ -25,13 +25,13 @@ def retrigger_task(task_id, dry_run=False): 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. + returns - 0 for dry_run case, -1 for any failure or the task id (int) + of a succesful retriggered task. http://docs.taskcluster.net/queue/api-docs/#createTask """ one_year = 365 - new_task_id = None + new_task_id = 0 try: queue = taskcluster_client.Queue() @@ -48,11 +48,14 @@ def retrigger_task(task_id, dry_run=False): # The task group will be identified by the ID of the only # task in the group task['taskGroupId'] = new_task_id + # https://bugzilla.mozilla.org/show_bug.cgi?id=1190660 # 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') + now = datetime.datetime.utcnow() + tomorrow = now + datetime.timedelta(hours=24) + task['created'] = taskcluster_client.stringDate(now) + task['deadline'] = taskcluster_client.stringDate(tomorrow) LOG.debug("Contents of new task: (Limit 1024 char)") LOG.debug(str(task)[:1024]) @@ -60,7 +63,7 @@ def retrigger_task(task_id, dry_run=False): 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.debug(json.dumps(result)) LOG.info("{}/task-inspector/#{}".format(TASKCLUSTER_TOOLS_HOST, new_task_id)) else: LOG.info("Dry-run mode: Nothing was retriggered.") From 63fbd073c9afb5a317641418909a138ec28558ee Mon Sep 17 00:00:00 2001 From: Armen Zambrano G Date: Wed, 5 Aug 2015 14:12:22 -0400 Subject: [PATCH 9/9] Missing WARNING from previous merge. --- mozci/mozci.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mozci/mozci.py b/mozci/mozci.py index 619bc2d..d94db9e 100644 --- a/mozci/mozci.py +++ b/mozci/mozci.py @@ -21,6 +21,7 @@ PENDING, RUNNING, SUCCESS, + WARNING, UNKNOWN, COALESCED, FAILURE,