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