blob: 95797530a3440de645d32dc99946f1288253d2bd [file] [log] [blame]
# 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)