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 database model Sheriff, for sheriff rotations."""
6
7import re
8import urlparse
9
10from google.appengine.ext import ndb
11
12from dashboard.models import internal_only_model
13
14# Acceptable suffixes for internal-only sheriff rotation email addresses.
15_ALLOWED_INTERNAL_ONLY_EMAIL_DOMAINS = (
16    '@google.com',
17    '@chromium.org',
18    '@grotations.appspotmail.com',
19)
20
21
22class ValidationError(Exception):
23  """Represents a failed validation."""
24  pass
25
26
27def _UrlValidator(prop, val):  # pylint: disable=unused-argument
28  """Validates an URL property.
29
30  The first argument is required, since this function is used as a validator
31  function for a property. For more about validator functions in ndb, see:
32  https://developers.google.com/appengine/docs/python/ndb/properties#options
33
34  Args:
35    prop: The property object.
36    val: The string value to be validated as a URL.
37
38  Raises:
39    ValidationError: The input string is invalid.
40  """
41  parsed = urlparse.urlparse(val)
42  if not (parsed.scheme and parsed.netloc and parsed.path):
43    raise ValidationError('Invalid URL %s' % val)
44
45
46def _EmailValidator(prop, val):  # pylint: disable=unused-argument
47  """Validates an email address property."""
48  # Note, this doesn't check the domain of the email address;
49  # that is done in the pre-put hook in Sheriff.
50  if not re.match(r'[\w.+-]+@[\w.-]', val):
51    raise ValidationError('Invalid email %s' % val)
52
53
54class Sheriff(internal_only_model.InternalOnlyModel):
55  """Configuration options for sheriffing alerts. One per sheriff."""
56
57  # Many perf sheriff email addresses are stored at a url. If there is a url
58  # specified here, it will be used as the perf sheriff address. But the email
59  # below will also be cc-ed, so that alerts can be archived to groups.
60  url = ndb.StringProperty(validator=_UrlValidator, indexed=False)
61  email = ndb.StringProperty(validator=_EmailValidator, indexed=False)
62
63  internal_only = ndb.BooleanProperty(indexed=True)
64  summarize = ndb.BooleanProperty(indexed=True, default=False)
65
66  # A list of patterns. Each pattern is a string which can match parts of the
67  # test path either exactly, or use '*' as a wildcard. Test paths matching
68  # these patterns will use this sheriff configuration for alerting.
69  patterns = ndb.StringProperty(repeated=True, indexed=False)
70
71  # Number of days before the Sheriff should get an alert about not having
72  # received any data for a particular Test entity; -1 means no alerts.
73  # If a sheriff wants to receive these alerts, the number should be greater
74  # than zero but less than two weeks (the amount of time before tests become
75  # marked as deprecated).
76  stoppage_alert_delay = ndb.IntegerProperty(default=-1, indexed=True)
77
78  def _pre_put_hook(self):
79    """Checks that the Sheriff properties are OK before putting."""
80    if (self.internal_only and self.email
81        and not _IsAllowedInternalOnlyEmail(self.email)):
82      raise ValidationError('Invalid internal sheriff email %s' % self.email)
83
84
85def _IsAllowedInternalOnlyEmail(email):
86  """Checks whether an email address is OK for an internal-only sheriff."""
87  return email.endswith(_ALLOWED_INTERNAL_ONLY_EMAIL_DOMAINS)
88