diff --git a/changelog.md b/changelog.md index acebc95008..9a48559a19 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Added documentation on using Hubot to integrate with useful services not yet integrated with Zulip directly (e.g. Google Hangouts). - Added new management command to test sending email from Zulip. +- Added Pingdom integration. - Refactored the Zulip puppet modules to be more modular. - Refactored the Tornado event system, fixing old memory leaks. - Implemented running queue processors multithreaded in development, diff --git a/static/images/integrations/logos/pingdom.png b/static/images/integrations/logos/pingdom.png new file mode 100644 index 0000000000..855ec159cb Binary files /dev/null and b/static/images/integrations/logos/pingdom.png differ diff --git a/static/images/integrations/pingdom/001.png b/static/images/integrations/pingdom/001.png new file mode 100644 index 0000000000..e4847e5cea Binary files /dev/null and b/static/images/integrations/pingdom/001.png differ diff --git a/static/images/integrations/pingdom/002.png b/static/images/integrations/pingdom/002.png new file mode 100644 index 0000000000..56f4d2eb41 Binary files /dev/null and b/static/images/integrations/pingdom/002.png differ diff --git a/templates/zerver/integrations.html b/templates/zerver/integrations.html index 5fa61cf7d1..6eb2eb75f1 100644 --- a/templates/zerver/integrations.html +++ b/templates/zerver/integrations.html @@ -164,6 +164,12 @@ Phabricator +
+ + + Pingdom + +
@@ -1236,7 +1242,7 @@ key = NAGIOS_BOT_API_KEY
-

Zulip supports Phabricator integration and can notify you of the +

Zulip supports integration with Phabricator and can notify you of the latest items in your Phabricator feed.

You can follow the instructions at @@ -1248,11 +1254,41 @@ key = NAGIOS_BOT_API_KEY

+
+ +

Zulip supports integration with Pingdom and can notify you of + uptime status changes from your Pingdom dashboard.

+ +

First, create the stream you'd like to use for Pingdom notifications, + and subscribe all interested parties to this stream. We + recommend the name pingdom.

+ +

{{ external_api_uri }}/v1/external/pingdom?api_key=abcdefgh&stream=pingdom

+ +

where api_key is the API key of your Zulip bot, + and stream is the stream name you want the + notifications sent to.

+ +

Next, under following url:

+

https://my.pingdom.com/reports/integration/settings

+

create your integration by clicking on Add Integration button and filling form as following:

+ + +

Last, during creating or editing your check, scroll down to Connect Integrations + section and ensure your integration is checked

+ +

Congratulations! You're done!
Example Zulip notification + looks like this:

+ + + +
+
-

Zulip supports Pivotal Tracker integration and can notify you of changes +

Zulip supports integration with Pivotal Tracker and can notify you of changes to the stories in your Pivotal Tracker project.

First, create the stream you'd like to use for Pivotal Tracker diff --git a/zerver/fixtures/pingdom/pingdom_http_up_to_down.json b/zerver/fixtures/pingdom/pingdom_http_up_to_down.json new file mode 100644 index 0000000000..871171e574 --- /dev/null +++ b/zerver/fixtures/pingdom/pingdom_http_up_to_down.json @@ -0,0 +1,34 @@ +{ + "check_id": 2048467, + "check_name": "Test check", + "check_type": "HTTP", + "check_params": { + "basic_auth": false, + "encryption": false, + "full_url": "http:\/\/someurl.com\/", + "header": "User-Agent:Pingdom.com_bot_version_1.4_(http:\/\/www.pingdom.com\/)", + "hostname": "someurl.com", + "ipv6": false, + "port": 80, + "url": "\/" + }, + "tags": [ + + ], + "previous_state": "UP", + "current_state": "DOWN", + "state_changed_timestamp": 1457939434, + "state_changed_utc_time": "2016-03-14T07:10:34", + "long_description": "Non-recoverable failure in name resolution", + "description": "DNS error", + "first_probe": { + "ip": "85.17.156.99", + "ipv6": "2001:1af8:4100:a09b::454", + "location": "Amsterdam 5, Netherlands" + }, + "second_probe": { + "ip": "64.141.100.136", + "ipv6": "", + "location": "Calgary, Canada" + } +} diff --git a/zerver/fixtures/pingdom/pingdom_imap_down_to_up.json b/zerver/fixtures/pingdom/pingdom_imap_down_to_up.json new file mode 100644 index 0000000000..bf7c4e6350 --- /dev/null +++ b/zerver/fixtures/pingdom/pingdom_imap_down_to_up.json @@ -0,0 +1,25 @@ +{ + "check_id": 2051859, + "check_name": "IMAP check", + "check_type": "IMAP", + "check_params": { + "basic_auth": false, + "encryption": true, + "hostname": "imap.someurl.com", + "ipv6": false, + "port": 993 + }, + "tags": [], + "previous_state": "DOWN", + "current_state": "UP", + "state_changed_timestamp": 1458070380, + "state_changed_utc_time": "2016-03-15T19:33:00", + "long_description": "OK", + "description": "OK", + "first_probe": { + "ip": "69.64.56.47", + "ipv6": "", + "location": "St. Louis, MO" + }, + "second_probe": {} +} diff --git a/zerver/fixtures/pingdom/pingdom_imap_up_to_down.json b/zerver/fixtures/pingdom/pingdom_imap_up_to_down.json new file mode 100644 index 0000000000..a7be53f9e7 --- /dev/null +++ b/zerver/fixtures/pingdom/pingdom_imap_up_to_down.json @@ -0,0 +1,29 @@ +{ + "check_id": 2051859, + "check_name": "IMAP check", + "check_type": "IMAP", + "check_params": { + "basic_auth": false, + "encryption": true, + "hostname": "imap.someurl.com", + "ipv6": false, + "port": 993 + }, + "tags": [], + "previous_state": "UP", + "current_state": "DOWN", + "state_changed_timestamp": 1458069480, + "state_changed_utc_time": "2016-03-15T19:18:00", + "long_description": "Invalid hostname, address or socket", + "description": "Unknown target", + "first_probe": { + "ip": "188.138.118.184", + "ipv6": "", + "location": "Strasbourg 2, France" + }, + "second_probe": { + "ip": "76.164.194.74", + "ipv6": "2605:6f80:0:c::449", + "location": "Las Vegas 2, NV" + } +} diff --git a/zerver/fixtures/pingdom/pingdom_smtp_up_to_down.json b/zerver/fixtures/pingdom/pingdom_smtp_up_to_down.json new file mode 100644 index 0000000000..e15ad02811 --- /dev/null +++ b/zerver/fixtures/pingdom/pingdom_smtp_up_to_down.json @@ -0,0 +1,29 @@ +{ + "check_id": 2051844, + "check_name": "SMTP check", + "check_type": "SMTP", + "check_params": { + "basic_auth": false, + "encryption": false, + "hostname": "smtp.someurl.com", + "ipv6": false, + "port": 25 + }, + "tags": [], + "previous_state": "UP", + "current_state": "DOWN", + "state_changed_timestamp": 1458068544, + "state_changed_utc_time": "2016-03-15T19:02:24", + "long_description": "Connection refused", + "description": "Connection refused", + "first_probe": { + "ip": "174.34.162.242", + "ipv6": "", + "location": "Atlanta, GA" + }, + "second_probe": { + "ip": "64.141.100.136", + "ipv6": "", + "location": "Calgary, Canada" + } +} diff --git a/zerver/test_hooks.py b/zerver/test_hooks.py index a5ef0ef3d2..f19e4de157 100644 --- a/zerver/test_hooks.py +++ b/zerver/test_hooks.py @@ -864,3 +864,71 @@ class TravisHookTests(AuthedTestCase): u"Details: [changes](https://github.com/hl7-fhir/fhir-sv" u"n/compare/6dccb98bcfd9...6c457d366a31), [build log](ht" u"tps://travis-ci.org/hl7-fhir/fhir-svn/builds/92495257)")) + + +class PingdomHookTests(AuthedTestCase): + STREAM_NAME = 'pingdom' + TEST_USER_EMAIL = 'hamlet@zulip.com' + URL_TEMPLATE = "/api/v1/external/pingdom?stream={stream}&api_key={api_key}" + + def setUp(self): + api_key = self.get_api_key(self.TEST_USER_EMAIL) + self._url = self.URL_TEMPLATE.format(stream=self.STREAM_NAME, api_key=api_key) + self.subscribe_to_stream(self.TEST_USER_EMAIL, self.STREAM_NAME) + + def test_pingdom_from_up_to_down_http_check_message(self): + """ + Tests if pingdom http check from up to down is handled correctly + """ + body = self._get_fixture_data('http_up_to_down') + self._send_post_request_with_params(body) + + expected_message = u"Service someurl.com changed its HTTP status from UP to DOWN.\nDescription: Non-recoverable failure in name resolution." + msg = self._get_recently_added_message() + self.assertEqual(msg.subject, u"Test check status.") + self.assertEqual(msg.content, expected_message) + + def test_pingdom_from_up_to_down_smtp_check_message(self): + """ + Tests if pingdom smtp check from up to down is handled correctly + """ + body = self._get_fixture_data('smtp_up_to_down') + self._send_post_request_with_params(body) + + expected_message = u"Service smtp.someurl.com changed its SMTP status from UP to DOWN.\nDescription: Connection refused." + msg = self._get_recently_added_message() + self.assertEqual(msg.subject, u"SMTP check status.") + self.assertEqual(msg.content, expected_message) + + def test_pingdom_from_up_to_down_imap_check_message(self): + """ + Tests if pingdom imap check from up to down is handled correctly + """ + body = self._get_fixture_data('imap_up_to_down') + self._send_post_request_with_params(body) + + expected_message = u"Service imap.someurl.com changed its IMAP status from UP to DOWN.\nDescription: Invalid hostname, address or socket." + msg = self._get_recently_added_message() + self.assertEqual(msg.subject, u"IMAP check status.") + self.assertEqual(msg.content, expected_message) + + def test_pingdom_from_down_to_up_imap_check_message(self): + """ + Tests if pingdom imap check from down to up is handled correctly + """ + body = self._get_fixture_data('imap_down_to_up') + self._send_post_request_with_params(body) + + expected_message = u"Service imap.someurl.com changed its IMAP status from DOWN to UP." + msg = self._get_recently_added_message() + self.assertEqual(msg.subject, u"IMAP check status.") + self.assertEqual(msg.content, expected_message) + + def _get_recently_added_message(self): + return Message.objects.filter().order_by('-id')[0] + + def _get_fixture_data(self, name): + return ujson.dumps(ujson.loads(self.fixture_data('pingdom', name))) + + def _send_post_request_with_params(self, json): + return self.client.post(self._url, json, stream_name=self.STREAM_NAME, content_type="application/json") diff --git a/zerver/views/webhooks/pingdom.py b/zerver/views/webhooks/pingdom.py new file mode 100644 index 0000000000..a4d1e850de --- /dev/null +++ b/zerver/views/webhooks/pingdom.py @@ -0,0 +1,68 @@ +# Webhooks for external integrations. +from __future__ import absolute_import +from zerver.models import get_client +from zerver.lib.actions import check_send_message +from zerver.lib.response import json_success, json_error +from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view + +import ujson + + +PINGDOM_SUBJECT_TEMPLATE = '{name} status.' +PINGDOM_MESSAGE_TEMPLATE = 'Service {service_url} changed its {type} status from {previous_state} to {current_state}.' +PINGDOM_MESSAGE_DESCRIPTION_TEMPLATE = 'Description: {description}.' + + +SUPPORTED_CHECK_TYPES = ( + 'HTTP', + 'HTTP_CUSTOM' + 'HTTPS', + 'SMTP', + 'POP3', + 'IMAP', + 'PING', + 'DNS', + 'UDP', + 'PORT_TCP', +) + + +@api_key_only_webhook_view +@has_request_variables +def api_pingdom_webhook(request, user_profile, stream=REQ(default='pingdom')): + payload = ujson.loads(request.body) + check_type = get_check_type(payload) + + if check_type in SUPPORTED_CHECK_TYPES: + subject = get_subject_for_http_request(payload) + body = get_body_for_http_request(payload) + else: + return json_error('Unsupported check_type: {check_type}'.format(check_type=check_type)) + + check_send_message(user_profile, get_client('ZulipPingdomWebhook'), 'stream', [stream], subject, body) + return json_success() + + +def get_subject_for_http_request(payload): + return PINGDOM_SUBJECT_TEMPLATE.format(name=payload['check_name']) + + +def get_body_for_http_request(payload): + current_state = payload['current_state'] + previous_state = payload['previous_state'] + + data = { + 'service_url': payload['check_params']['hostname'], + 'previous_state': previous_state, + 'current_state': current_state, + 'type': get_check_type(payload) + } + body = PINGDOM_MESSAGE_TEMPLATE.format(**data) + if current_state == 'DOWN' and previous_state == 'UP': + description = PINGDOM_MESSAGE_DESCRIPTION_TEMPLATE.format(description=payload['long_description']) + body += '\n{description}'.format(description=description) + return body + + +def get_check_type(payload): + return payload['check_type'] diff --git a/zproject/urls.py b/zproject/urls.py index ec32d448c3..f6131ab096 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -161,6 +161,7 @@ urlpatterns += patterns('zerver.views', url(r'^api/v1/external/zendesk$', 'webhooks.zendesk.api_zendesk_webhook'), url(r'^api/v1/external/pagerduty$', 'webhooks.pagerduty.api_pagerduty_webhook'), url(r'^api/v1/external/travis$', 'webhooks.travis.api_travis_webhook'), + url(r'^api/v1/external/pingdom$', 'webhooks.pingdom.api_pingdom_webhook'), url(r'^user_uploads/(?P(\d*|unk))/(?P.*)', 'get_uploaded_file'), )