Home | History | Annotate | Download | only in dashboard
      1 # Copyright 2015 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """A request handler to send alert summary emails to sheriffs on duty."""
      6 
      7 from google.appengine.api import mail
      8 from google.appengine.ext import ndb
      9 
     10 from dashboard import datastore_hooks
     11 from dashboard import email_template
     12 from dashboard import request_handler
     13 from dashboard import utils
     14 from dashboard.models import sheriff
     15 from dashboard.models import stoppage_alert
     16 
     17 _HTML_BODY_TEMPLATE = """
     18 <p>Tests that have not received data in some time:</p>
     19 <table>
     20   <tr><th>Last rev</th><th>Test</th><th>Stdio</th></tr>
     21   %(alert_rows)s
     22 </table>
     23 <p>It is possible that the test has been failing, or that the test was
     24 disabled on purpose and we should remove monitoring. It is also possible
     25 that the test has been renamed and the monitoring should be updated.
     26 <a href="%(bug_template_link)s">File a bug.</a></p>
     27 """
     28 _HTML_ALERT_ROW_TEMPLATE = """<tr>
     29     <td>%(rev)d</td>
     30     <td><a href="%(graph_link)s">%(test_path)s</a></td>
     31     <td><a href="%(stdio_link)s">stdio</a></td>
     32   </tr>
     33 """
     34 _TEXT_BODY_TEMPLATE = """
     35 Tests that have not received data in some time:
     36 %(alert_rows)s
     37 
     38 It is possible that the test has been failing, or that the test was
     39 disabled on purpose and we should remove monitoring. It is also possible
     40 that the test has been renamed and the monitoring should be updated.
     41 
     42 File a bug: %(bug_template_link)s
     43 """
     44 _TEXT_ALERT_ROW_TEMPLATE = """
     45   Last rev: %(rev)d
     46   Test: %(test_path)s
     47   Graph:%(graph_link)s
     48   Stdio: %(stdio_link)s
     49 """
     50 _BUG_TEMPLATE_URL = (
     51     'https://code.google.com/p/chromium/issues/entry'
     52     '?labels=Pri-1,Performance-Waterfall,'
     53     'Type-Bug-Regression,OS-?&comment=Tests affected:'
     54     '&summary=No+data+received+for+<tests>+since+<rev>'
     55     'cc=%s')
     56 
     57 
     58 class SendStoppageAlertEmailsHandler(request_handler.RequestHandler):
     59   """Sends emails to sheriffs about stoppage alerts in the past day.
     60 
     61   This request handler takes no parameters and is intended to be called by cron.
     62   """
     63 
     64   def get(self):
     65     """Emails sheriffs about new stoppage alerts."""
     66     datastore_hooks.SetPrivilegedRequest()
     67     sheriffs_to_email_query = sheriff.Sheriff.query(
     68         sheriff.Sheriff.stoppage_alert_delay > 0)
     69     for sheriff_entity in sheriffs_to_email_query:
     70       _SendStoppageAlertEmail(sheriff_entity)
     71 
     72 
     73 def _SendStoppageAlertEmail(sheriff_entity):
     74   """Sends a summary email for the given sheriff rotation.
     75 
     76   Args:
     77     sheriff_entity: A Sheriff key.
     78   """
     79   stoppage_alerts = _RecentStoppageAlerts(sheriff_entity)
     80   if not stoppage_alerts:
     81     return
     82   alert_dicts = [_AlertRowDict(a) for a in stoppage_alerts]
     83   mail.send_mail(
     84       sender='gasper-alerts (at] google.com',
     85       to=email_template.GetSheriffEmails(sheriff_entity),
     86       subject=_Subject(sheriff_entity, stoppage_alerts),
     87       body=_TextBody(alert_dicts),
     88       html=_HtmlBody(alert_dicts))
     89   for alert in stoppage_alerts:
     90     alert.mail_sent = True
     91   ndb.put_multi(stoppage_alerts)
     92 
     93 
     94 def _RecentStoppageAlerts(sheriff_entity):
     95   """Returns new StoppageAlert entities that have not had a mail sent yet."""
     96   return stoppage_alert.StoppageAlert.query(
     97       stoppage_alert.StoppageAlert.sheriff == sheriff_entity.key,
     98       stoppage_alert.StoppageAlert.mail_sent == False).fetch()
     99 
    100 
    101 def _Subject(sheriff_entity, stoppage_alerts):
    102   """Returns the subject line for an email about stoppage alerts.
    103 
    104   Args:
    105     sheriff_entity: The Sheriff who will receive the alerts.
    106     stoppage_alerts: A list of StoppageAlert entities.
    107 
    108   Returns:
    109     A string email subject line.
    110   """
    111   template = 'No data received in at least %d days for %d series.'
    112   return template % (sheriff_entity.stoppage_alert_delay, len(stoppage_alerts))
    113 
    114 
    115 def _HtmlBody(alert_dicts):
    116   """Returns the HTML body for an email about stoppage alerts."""
    117   html_alerts = '\n'.join(_HTML_ALERT_ROW_TEMPLATE % a for a in alert_dicts)
    118   return _HTML_BODY_TEMPLATE % {
    119       'alert_rows': html_alerts,
    120       'bug_template_link': _BUG_TEMPLATE_URL,
    121   }
    122 
    123 
    124 def _TextBody(alert_dicts):
    125   """Returns the text body for an email about stoppage alerts."""
    126   text_alerts = '\n'.join(_TEXT_ALERT_ROW_TEMPLATE % a for a in alert_dicts)
    127   return _TEXT_BODY_TEMPLATE % {
    128       'alert_rows': text_alerts,
    129       'bug_template_link': _BUG_TEMPLATE_URL,
    130   }
    131 
    132 
    133 def _AlertRowDict(alert):
    134   """Returns a dict with information to print about one stoppage alert."""
    135   test_path = utils.TestPath(alert.GetTestMetadataKey())
    136   return {
    137       'rev': alert.revision,
    138       'test_path': test_path,
    139       'graph_link': email_template.GetReportPageLink(test_path),
    140       'stdio_link': _StdioLink(alert),
    141   }
    142 
    143 
    144 def _StdioLink(alert):
    145   """Returns a list of stdio log links for the given stoppage alerts."""
    146   row = alert.row.get()
    147   return getattr(row, 'a_stdio_uri', None)
    148