| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Routes for CL exonerator.""" |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| import httplib |
| import sys |
| |
| from chromite.lib import cidb |
| from chromite.lib import parallel |
| from chromite.lib import cros_logging as logging |
| import flask |
| |
| import config |
| import debug_routes |
| import flex_ts_mon.setup |
| from exonerator import cidblib |
| from exonerator import gerrit_cq |
| from exonerator import gerrit_precq |
| from exonerator import innocent_cls_cq |
| from exonerator import innocent_cls_precq |
| from exonerator import weekend |
| |
| |
| app = flask.Flask(__name__) |
| app.register_blueprint(debug_routes.app) |
| config = config.Config() |
| |
| # TODO(phobbs) make this higher at night / weekend |
| _CQ_BUILD_LIMIT = 1 |
| |
| APPENGINE_CRON_HEADER = 'X-Appengine-Cron' |
| PRE_CQ_TYPE = 'pre-cq' |
| CQ_TYPE = 'cq' |
| |
| # This must be done at the top level because gunicorn and nginx does not run us |
| # as __main__ |
| logging.basicConfig(level=logging.DEBUG) |
| |
| |
| @app.route('/') |
| def Hello(): |
| """Placeholder route""" |
| return 'hello world' |
| |
| |
| # Ideally this would be a POST, but Appengine cron only makes GET requests. |
| @app.route('/cron/exonerate/cq') |
| def ExonerateCQ(): |
| """Exonerates innocent CLs which were kicked out.""" |
| if not _RequestIsFromAppEngineCron(): |
| flask.abort(httplib.UNAUTHORIZED) |
| |
| conn = cidb.CIDBConnection( |
| config.cidb_cred_dir, for_service=config.is_service) |
| all_exonerated = [] |
| for batch in innocent_cls_cq.NewInnocentCLs(conn, limit=_CQ_BUILD_LIMIT): |
| all_exonerated.extend(_ExonerateBatch(conn, batch, CQ_TYPE)) |
| |
| return flask.jsonify(exonerated=all_exonerated) |
| |
| |
| @app.route('/cron/exonerate/pre-cq') |
| def ExoneratePreCQ(): |
| """Exonerates innocent CLs which were kicked out.""" |
| if not _RequestIsFromAppEngineCron(): |
| flask.abort(httplib.UNAUTHORIZED) |
| |
| conn = cidb.CIDBConnection( |
| config.cidb_cred_dir, for_service=config.is_service) |
| all_exonerated = [] |
| on_peak = not weekend.HaveUnlimitedResources() |
| for batch in innocent_cls_precq.NewInnocentCLs(conn, on_peak=on_peak): |
| all_exonerated.extend(_ExonerateBatch(conn, batch, PRE_CQ_TYPE)) |
| |
| return flask.jsonify(exonerated=all_exonerated) |
| |
| |
| def _ExonerateBatch(conn, batch, exoneration_type): |
| """Exonerates a batch of changes, then inserts CLActions. |
| |
| Args: |
| conn: The CIDBConnection to use |
| batch: A List[exonerator.gerrit_cq.ChangeWithBuild] of changes to exonerate. |
| exoneration_type: A string in ('cq', 'pre-cq') |
| """ |
| exonerate_f = { |
| 'cq': gerrit_cq.MaybeExonerate, |
| 'pre-cq': gerrit_precq.MaybeExonerate, |
| }[exoneration_type] |
| exonerated_flags = parallel.RunTasksInProcessPool( |
| exonerate_f, |
| [[change, build_id] for change, build_id in batch]) |
| |
| exonerated_changes = [] |
| for change, exonerated in zip(batch, exonerated_flags): |
| if exonerated: |
| exonerated_changes.append(change) |
| |
| if exonerated_changes: |
| cidblib.InsertExoneratedCLActions( |
| conn, exonerated_changes, exoneration_type) |
| |
| return exonerated_changes |
| |
| |
| def _RequestIsFromAppEngineCron(): |
| """Returns whether the request came from Appengine's cron service. |
| |
| See https://cloud.google.com/appengine/docs/flexible/python/ |
| scheduling-jobs-with-cron-yaml#validating_cron_requests |
| for more details. |
| """ |
| return flask.request.headers.get(APPENGINE_CRON_HEADER) == 'true' |
| |
| |
| _METRICS_DEBUG_FILE = '/tmp/metrics-debug.log' if config.debug else None |
| |
| |
| @app.before_first_request |
| def SetupWorkerMetrics(): |
| """Sets up the metrics sending thread. |
| |
| This must happen post-fork, or else the gunicorn workers will all use the |
| same hostname and task_num arguments, causing conflicts when they flush |
| metrics. |
| """ |
| flex_ts_mon.setup.SetupTsMonGlobalState(debug_file=_METRICS_DEBUG_FILE) |
| |
| |
| if __name__ == '__main__': |
| print('Run the app locally with ' |
| '"gunicorn -c gunicorn.conf.py -b :8080 main:app"', file=sys.stderr) |