Home | History | Annotate | Download | only in models
      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 base Model for any kind of alert that can be associated with a bug."""
      6 
      7 from google.appengine.ext import ndb
      8 
      9 from dashboard.models import internal_only_model
     10 from dashboard.models import sheriff as sheriff_module
     11 
     12 
     13 class Alert(internal_only_model.InternalOnlyModel):
     14   """General base class for alerts."""
     15 
     16   # Whether the alert should only be viewable by internal users.
     17   internal_only = ndb.BooleanProperty(indexed=True, default=False)
     18 
     19   # The time the alert fired.
     20   timestamp = ndb.DateTimeProperty(indexed=True, auto_now_add=True)
     21 
     22   # Note: -1 denotes an invalid alert and -2 an ignored alert.
     23   # By default, this is None, which denotes a non-triaged alert.
     24   bug_id = ndb.IntegerProperty(indexed=True)
     25 
     26   # The sheriff rotation that should handle this alert.
     27   sheriff = ndb.KeyProperty(kind=sheriff_module.Sheriff, indexed=True)
     28 
     29   # Each Alert is related to one Test.
     30   test = ndb.KeyProperty(indexed=True)
     31 
     32   # Each Alert has a revision range it's associated with; however,
     33   # start_revision and end_revision could be the same.
     34   start_revision = ndb.IntegerProperty(indexed=True)
     35   end_revision = ndb.IntegerProperty(indexed=True)
     36 
     37   # The group this alert belongs to.
     38   # TODO(qyearsley): If the old AnomalyGroup entities can be removed and
     39   # all recent groups have the kind AlertGroup, then the optional argument
     40   # kind=alert_group.AlertGroup can be added.
     41   group = ndb.KeyProperty(indexed=True)
     42 
     43   def _pre_put_hook(self):
     44     """Updates the alert's group."""
     45     # TODO(qyearsley): Extract sub-methods from this method in order
     46     # to make it shorter.
     47 
     48     # The group should not be updated if this is the first time that the
     49     # the Alert is being put. (If the key is auto-assigned, then key.id()
     50     # will be None the first time.)
     51     if not self.key.id():
     52       return
     53 
     54     # The previous state of this alert. (If this is the first time the
     55     # alert is being put, then this will be None.
     56     original_alert = self.key.get(use_cache=False)
     57     if original_alert is None:
     58       return
     59 
     60     # If the alert does not have a group, don't do anything.
     61     if not self.group:
     62       return
     63     # If the group key is "AnomalyGroup" (the previous incarnation of
     64     # AlertGroup), we can just leave it as is. This will only apply to
     65     # already-existing Anomaly entities, not new Anomaly entities.
     66     if self.group.kind() != 'AlertGroup':
     67       self.group = None
     68       return
     69     group = self.group.get()
     70     if not group:
     71       return
     72 
     73     # Each AlertGroup should only be associated with entities of one class;
     74     # i.e. an Anomaly entity shouldn't be grouped with a StoppageAlert entity.
     75     alert_class = self.__class__
     76 
     77     # When the bug ID changes, this alert may be updated to belong
     78     # to the new group.
     79     if self.bug_id != original_alert.bug_id:
     80       grouped_alerts = alert_class.query(
     81           alert_class.group == group.key).fetch()
     82       grouped_alerts.append(self)
     83 
     84       # The alert has been assigned a real bug ID.
     85       # Update the group bug ID if necessary.
     86       if self.bug_id > 0 and group.bug_id != self.bug_id:
     87         group.bug_id = self.bug_id
     88         group.put()
     89 
     90       # The bug has been marked invalid/ignored. Kick it out of the group.
     91       elif self.bug_id < 0 and self.bug_id is not None:
     92         self._RemoveFromGroup(grouped_alerts)
     93         grouped_alerts.remove(self)
     94 
     95       # The bug has been un-triaged. Update the group's bug ID if this is
     96       # the only alert in the group.
     97       elif self.bug_id is None and len(grouped_alerts) == 1:
     98         group.bug_id = None
     99         group.put()
    100 
    101       # Check and update the group's revision range if necessary.
    102       group.UpdateRevisionRange(grouped_alerts)
    103 
    104     elif (self.end_revision != original_alert.end_revision or
    105           self.start_revision != original_alert.start_revision):
    106       grouped_alerts = alert_class.query(alert_class.group == group.key).fetch()
    107       grouped_alerts.append(self)
    108       group.UpdateRevisionRange(grouped_alerts)
    109 
    110   def _RemoveFromGroup(self, grouped_alerts):
    111     """Removes an alert from its group and updates the group's properties.
    112 
    113     Args:
    114       grouped_alerts: The list of alerts in |group| used to calculate
    115           new revision range; none are modified.
    116     """
    117     group = self.group.get()
    118     self.group = None
    119     grouped_alerts.remove(self)
    120     if not grouped_alerts:
    121       group.key.delete()
    122       return
    123     # Update minimum revision range for group.
    124     group.UpdateRevisionRange(grouped_alerts)
    125 
    126 
    127 def _GetTestSuiteFromKey(test_key):
    128   """Gets test suite from |test_key|, None if not found."""
    129   pairs = test_key.pairs()
    130   if len(pairs) < 3:
    131     return None
    132   return pairs[2][1]
    133 
    134 
    135 def GetBotNamesFromAlerts(alerts):
    136   """Gets a set with the names of the bots related to some alerts."""
    137   # a.test is a ndb.Key object, and a.test.flat() should return a list like
    138   # ['Master', master name, 'Bot', bot name, 'Test', test suite name, ...]
    139   return {a.test.flat()[3] for a in alerts}
    140