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"""The datastore model for alerts when data is no longer received for a test."""
6
7import logging
8
9from google.appengine.ext import ndb
10
11from dashboard import utils
12from dashboard.models import alert
13from dashboard.models import alert_group
14
15_MAX_GROUP_SIZE = 20
16
17
18class StoppageAlert(alert.Alert):
19  """A stoppage alert is an alert for a Test no longer receiving new points.
20
21  Each StoppageAlert is associated with one Test, so if a test suite gets
22  deprecated or renamed, there may be a set of related StoppageAlerts created.
23
24  The key for a StoppageAlert is of the form:
25    [("StoppageAlertParent", <test_path>), ("StoppageAlert", <revision>)].
26
27  Thus, two StoppageAlert entities can not be created for the same stoppage
28  event.
29  """
30  # Whether a mail notification has been sent for this alert.
31  mail_sent = ndb.BooleanProperty(indexed=True, default=False)
32
33  # Whether new points have been received for the test after this alert.
34  recovered = ndb.BooleanProperty(indexed=True, default=False)
35
36  # Computed properties are treated like member variables, so they have
37  # lowercase names, even though they look like methods to pylint.
38  # pylint: disable=invalid-name
39
40  @ndb.ComputedProperty
41  def revision(self):
42    return self.key.id()
43
44  @ndb.ComputedProperty
45  def test(self):
46    return utils.TestKey(self.key.parent().string_id())
47
48  @ndb.ComputedProperty
49  def row(self):
50    test_container = utils.GetTestContainerKey(self.test)
51    return ndb.Key('Row', self.revision, parent=test_container)
52
53  @ndb.ComputedProperty
54  def start_revision(self):
55    return self.revision
56
57  @ndb.ComputedProperty
58  def end_revision(self):
59    return self.revision
60
61  @ndb.ComputedProperty
62  def last_row_date(self):
63    row = self.row.get()
64    if not row:
65      logging.warning('No Row with key %s', self.row)
66      return None
67    return row.timestamp
68
69
70def GetStoppageAlert(test_path, revision):
71  """Gets a StoppageAlert entity if it already exists.
72
73  Args:
74    test_path: The test path string of the Test associated with the alert.
75    revision: The ID of the last point before the stoppage.
76
77  Returns:
78    A StoppageAlert entity or None.
79  """
80  return ndb.Key(
81      'StoppageAlertParent', test_path, 'StoppageAlert', revision).get()
82
83
84def CreateStoppageAlert(test, row):
85  """Creates a new StoppageAlert entity.
86
87  Args:
88    test: A Test entity.
89    row: A Row entity; the last Row that was put before the stoppage.
90
91  Returns:
92    A new StoppageAlert entity which has not been put, or None,
93    if we don't want to create a new StoppageAlert.
94  """
95  new_alert = StoppageAlert(
96      parent=ndb.Key('StoppageAlertParent', test.test_path),
97      id=row.revision,
98      internal_only=test.internal_only,
99      sheriff=test.sheriff)
100  alert_group.GroupAlerts([new_alert], test.suite_name, 'StoppageAlert')
101  grouped_alert_keys = StoppageAlert.query(
102      StoppageAlert.group == new_alert.group).fetch(keys_only=True)
103  if len(grouped_alert_keys) >= _MAX_GROUP_SIZE:
104    # Too many stoppage alerts in this group; we don't want to put any more.
105    return None
106  test.stoppage_alert = new_alert.key
107  test.put()
108  return new_alert
109