Skip to content

Commit 43ae71a

Browse files
committed
Added support for alert plugins
1 parent aca9dda commit 43ae71a

25 files changed

+1255
-439
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ celerybeat-schedule
1515
*.orig
1616
.vagrant
1717
conf/*.env
18+
dist/

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ install:
1818
- sudo pip install virtualenv
1919
- sudo virtualenv venv
2020
- sudo ./venv/bin/pip install --upgrade setuptools
21+
- CABOT_PLUGINS_ENABLED=cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_email
2122
- sudo ./venv/bin/pip install --timeout=30 --exists-action=w -e . --no-use-wheel
2223

2324
# setup databases

cabot/cabot_config.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
21
import os
32

4-
CABOT_FROM_EMAIL = os.environ.get('CABOT_FROM_EMAIL')
53
GRAPHITE_API = os.environ.get('GRAPHITE_API')
64
GRAPHITE_USER = os.environ.get('GRAPHITE_USER')
75
GRAPHITE_PASS = os.environ.get('GRAPHITE_PASS')
86
GRAPHITE_FROM = os.getenv('GRAPHITE_FROM', '-10minute')
97
JENKINS_API = os.environ.get('JENKINS_API')
108
JENKINS_USER = os.environ.get('JENKINS_USER')
119
JENKINS_PASS = os.environ.get('JENKINS_PASS')
12-
HIPCHAT_ALERT_ROOM = os.environ.get('HIPCHAT_ALERT_ROOM')
13-
HIPCHAT_API_KEY = os.environ.get('HIPCHAT_API_KEY')
14-
HIPCHAT_URL = os.environ.get('HIPCHAT_URL')
15-
TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID')
16-
TWILIO_AUTH_TOKEN = os.environ.get('TWILIO_AUTH_TOKEN')
17-
TWILIO_OUTGOING_NUMBER = os.environ.get('TWILIO_OUTGOING_NUMBER')
1810
CALENDAR_ICAL_URL = os.environ.get('CALENDAR_ICAL_URL')
1911
WWW_HTTP_HOST = os.environ.get('WWW_HTTP_HOST')
2012
WWW_SCHEME = os.environ.get('WWW_SCHEME', "https")
2113
ALERT_INTERVAL = os.environ.get('ALERT_INTERVAL', 10)
2214
NOTIFICATION_INTERVAL = os.environ.get('NOTIFICATION_INTERVAL', 120)
15+
16+
# Default plugins are used if the user has not specified.
17+
CABOT_PLUGINS_ENABLED = os.environ.get('CABOT_PLUGINS_ENABLED', 'cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_email')

cabot/cabotapp/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.contrib import admin
22
from .models import UserProfile, Service, Shift, ServiceStatusSnapshot, StatusCheck, StatusCheckResult, Instance
3+
from .alert import AlertPluginUserData, AlertPlugin
34

45
admin.site.register(UserProfile)
56
admin.site.register(Shift)
@@ -8,3 +9,5 @@
89
admin.site.register(StatusCheck)
910
admin.site.register(StatusCheckResult)
1011
admin.site.register(Instance)
12+
admin.site.register(AlertPlugin)
13+
admin.site.register(AlertPluginUserData)

cabot/cabotapp/alert.py

Lines changed: 32 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -4,165 +4,51 @@
44
from django.core.mail import send_mail
55
from django.core.urlresolvers import reverse
66
from django.template import Context, Template
7+
from django.db import models
8+
from django.db.models import get_models
79

810
from twilio.rest import TwilioRestClient
911
from twilio import twiml
12+
1013
import requests
1114
import logging
15+
import re
16+
17+
from polymorphic import PolymorphicModel
1218

1319
logger = logging.getLogger(__name__)
1420

15-
email_template = """Service {{ service.name }} {{ scheme }}://{{ host }}{% url 'service' pk=service.id %} {% if service.overall_status != service.PASSING_STATUS %}alerting with status: {{ service.overall_status }}{% else %}is back to normal{% endif %}.
16-
{% if service.overall_status != service.PASSING_STATUS %}
17-
CHECKS FAILING:{% for check in service.all_failing_checks %}
18-
FAILING - {{ check.name }} - Type: {{ check.check_category }} - Importance: {{ check.get_importance_display }}{% endfor %}
19-
{% if service.all_passing_checks %}
20-
Passing checks:{% for check in service.all_passing_checks %}
21-
PASSING - {{ check.name }} - Type: {{ check.check_category }} - Importance: {{ check.get_importance_display }}{% endfor %}
22-
{% endif %}
23-
{% endif %}
24-
"""
21+
class AlertPlugin(PolymorphicModel):
22+
title = models.CharField(max_length=30, unique=True, editable=False)
23+
enabled = models.BooleanField(default=True)
2524

26-
hipchat_template = "Service {{ service.name }} {% if service.overall_status == service.PASSING_STATUS %}is back to normal{% else %}reporting {{ service.overall_status }} status{% endif %}: {{ scheme }}://{{ host }}{% url 'service' pk=service.id %}. {% if service.overall_status != service.PASSING_STATUS %}Checks failing:{% for check in service.all_failing_checks %}{% if check.check_category == 'Jenkins check' %}{% if check.last_result.error %} {{ check.name }} ({{ check.last_result.error|safe }}) {{jenkins_api}}job/{{ check.name }}/{{ check.last_result.job_number }}/console{% else %} {{ check.name }} {{jenkins_api}}/job/{{ check.name }}/{{check.last_result.job_number}}/console {% endif %}{% else %} {{ check.name }} {% if check.last_result.error %} ({{ check.last_result.error|safe }}){% endif %}{% endif %}{% endfor %}{% endif %}{% if alert %}{% for alias in users %} @{{ alias }}{% endfor %}{% endif %}"
25+
author = None
2726

28-
sms_template = "Service {{ service.name }} {% if service.overall_status == service.PASSING_STATUS %}is back to normal{% else %}reporting {{ service.overall_status }} status{% endif %}: {{ scheme }}://{{ host }}{% url 'service' pk=service.id %}"
27+
def __unicode__(self):
28+
return u'%s' % (self.title)
2929

30-
telephone_template = "This is an urgent message from Arachnys monitoring. Service \"{{ service.name }}\" is erroring. Please check Cabot urgently."
30+
def send_alert(service, users, duty_officers):
31+
"""
32+
Implement a send_alert function here that shall be called.
33+
"""
34+
return True
3135

36+
class AlertPluginUserData(PolymorphicModel):
37+
title = models.CharField(max_length=30, editable=False)
38+
user = models.ForeignKey('UserProfile', editable=False)
39+
40+
class Meta:
41+
unique_together = ('title', 'user',)
42+
43+
def __unicode__(self):
44+
return u'%s' % (self.title)
3245

3346
def send_alert(service, duty_officers=None):
3447
users = service.users_to_notify.filter(is_active=True)
35-
if service.email_alert:
36-
send_email_alert(service, users, duty_officers)
37-
if service.hipchat_alert:
38-
send_hipchat_alert(service, users, duty_officers)
39-
if service.sms_alert:
40-
send_sms_alert(service, users, duty_officers)
41-
if service.telephone_alert:
42-
send_telephone_alert(service, users, duty_officers)
43-
44-
45-
def send_email_alert(service, users, duty_officers):
46-
emails = [u.email for u in users if u.email]
47-
if not emails:
48-
return
49-
c = Context({
50-
'service': service,
51-
'host': settings.WWW_HTTP_HOST,
52-
'scheme': settings.WWW_SCHEME
53-
})
54-
if service.overall_status != service.PASSING_STATUS:
55-
if service.overall_status == service.CRITICAL_STATUS:
56-
emails += [u.email for u in duty_officers]
57-
subject = '%s status for service: %s' % (
58-
service.overall_status, service.name)
59-
else:
60-
subject = 'Service back to normal: %s' % (service.name,)
61-
t = Template(email_template)
62-
send_mail(
63-
subject=subject,
64-
message=t.render(c),
65-
from_email='Cabot <%s>' % settings.CABOT_FROM_EMAIL,
66-
recipient_list=emails,
67-
)
68-
69-
70-
def send_hipchat_alert(service, users, duty_officers):
71-
alert = True
72-
hipchat_aliases = [u.profile.hipchat_alias for u in users if hasattr(
73-
u, 'profile') and u.profile.hipchat_alias]
74-
if service.overall_status == service.WARNING_STATUS:
75-
alert = False # Don't alert at all for WARNING
76-
if service.overall_status == service.ERROR_STATUS:
77-
if service.old_overall_status in (service.ERROR_STATUS, service.ERROR_STATUS):
78-
alert = False # Don't alert repeatedly for ERROR
79-
if service.overall_status == service.PASSING_STATUS:
80-
color = 'green'
81-
if service.old_overall_status == service.WARNING_STATUS:
82-
alert = False # Don't alert for recovery from WARNING status
83-
else:
84-
color = 'red'
85-
if service.overall_status == service.CRITICAL_STATUS:
86-
hipchat_aliases += [u.profile.hipchat_alias for u in duty_officers if hasattr(
87-
u, 'profile') and u.profile.hipchat_alias]
88-
c = Context({
89-
'service': service,
90-
'users': hipchat_aliases,
91-
'host': settings.WWW_HTTP_HOST,
92-
'scheme': settings.WWW_SCHEME,
93-
'alert': alert,
94-
'jenkins_api': settings.JENKINS_API,
95-
})
96-
message = Template(hipchat_template).render(c)
97-
_send_hipchat_alert(message, color=color, sender='Cabot/%s' % service.name)
98-
99-
100-
def _send_hipchat_alert(message, color='green', sender='Cabot'):
101-
room = settings.HIPCHAT_ALERT_ROOM
102-
api_key = settings.HIPCHAT_API_KEY
103-
url = settings.HIPCHAT_URL
104-
resp = requests.post(url + '?auth_token=' + api_key, data={
105-
'room_id': room,
106-
'from': sender[:15],
107-
'message': message,
108-
'notify': 1,
109-
'color': color,
110-
'message_format': 'text',
111-
})
112-
113-
114-
def send_sms_alert(service, users, duty_officers):
115-
client = TwilioRestClient(
116-
settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
117-
mobiles = [u.profile.prefixed_mobile_number for u in users if hasattr(
118-
u, 'profile') and u.profile.mobile_number]
119-
if service.is_critical:
120-
mobiles += [u.profile.prefixed_mobile_number for u in duty_officers if hasattr(
121-
u, 'profile') and u.profile.mobile_number]
122-
c = Context({
123-
'service': service,
124-
'host': settings.WWW_HTTP_HOST,
125-
'scheme': settings.WWW_SCHEME,
126-
})
127-
message = Template(sms_template).render(c)
128-
mobiles = list(set(mobiles))
129-
for mobile in mobiles:
130-
try:
131-
client.sms.messages.create(
132-
to=mobile,
133-
from_=settings.TWILIO_OUTGOING_NUMBER,
134-
body=message,
135-
)
136-
except Exception, e:
137-
logger.exception('Error sending twilio sms: %s' % e)
138-
139-
140-
def send_telephone_alert(service, users, duty_officers):
141-
# No need to call to say things are resolved
142-
if service.overall_status != service.CRITICAL_STATUS:
143-
return
144-
client = TwilioRestClient(
145-
settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
146-
mobiles = [u.profile.prefixed_mobile_number for u in duty_officers if hasattr(
147-
u, 'profile') and u.profile.mobile_number]
148-
url = 'http://%s%s' % (settings.WWW_HTTP_HOST,
149-
reverse('twiml-callback', kwargs={'service_id': service.id}))
150-
for mobile in mobiles:
151-
try:
152-
client.calls.create(
153-
to=mobile,
154-
from_=settings.TWILIO_OUTGOING_NUMBER,
155-
url=url,
156-
method='GET',
157-
)
158-
except Exception, e:
159-
logger.exception('Error making twilio phone call: %s' % e)
160-
48+
for alert in service.alerts.all():
49+
alert.send_alert(service, users, duty_officers)
16150

162-
def telephone_alert_twiml_callback(service):
163-
c = Context({'service': service})
164-
t = Template(telephone_template).render(c)
165-
r = twiml.Response()
166-
r.say(t, voice='woman')
167-
r.hangup()
168-
return r
51+
def update_alert_plugins():
52+
for plugin_subclass in AlertPlugin.__subclasses__():
53+
plugin = plugin_subclass.objects.get_or_create(title=plugin_subclass.name)
54+
return AlertPlugin.objects.all()

0 commit comments

Comments
 (0)