From 7fb64c8249d799e25889761e2da1e9d7c16fa382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Sun, 21 Nov 2021 13:15:35 +0200 Subject: [PATCH] Implement Pushover emergency alert cancellation when check goes up --- CHANGELOG.md | 5 +++++ hc/api/tests/test_notify_pushover.py | 33 +++++++++++++++++++++++----- hc/api/transports.py | 18 ++++++++++++--- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6d61ab..e45d3125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. +## v1.25.0 - Unreleased + +### Improvements +- Implement Pushover emergency alert cancellation when check goes up + ## v1.24.1 - 2021-11-10 ### Bug Fixes diff --git a/hc/api/tests/test_notify_pushover.py b/hc/api/tests/test_notify_pushover.py index b012ea2c..419f4cc4 100644 --- a/hc/api/tests/test_notify_pushover.py +++ b/hc/api/tests/test_notify_pushover.py @@ -8,8 +8,10 @@ from django.utils.timezone import now from hc.api.models import Channel, Check, Notification, TokenBucket from hc.test import BaseTestCase +API = "https://api.pushover.net/1" -class NotifyTestCase(BaseTestCase): + +class NotifyPushoverTestCase(BaseTestCase): def _setup_data(self, value, status="down", email_verified=True): self.check = Check(project=self.project) self.check.status = status @@ -24,24 +26,27 @@ class NotifyTestCase(BaseTestCase): self.channel.checks.add(self.check) @patch("hc.api.transports.requests.request") - def test_pushover(self, mock_post): + def test_it_works(self, mock_post): self._setup_data("123|0") mock_post.return_value.status_code = 200 self.channel.notify(self.check) - assert Notification.objects.count() == 1 + self.assertEqual(Notification.objects.count(), 1) args, kwargs = mock_post.call_args + self.assertEqual(args[1], API + "/messages.json") + payload = kwargs["data"] self.assertIn("DOWN", payload["title"]) + self.assertEqual(payload["tags"], self.check.unique_key) @patch("hc.api.transports.requests.request") - def test_pushover_up_priority(self, mock_post): + def test_it_supports_up_priority(self, mock_post): self._setup_data("123|0|2", status="up") mock_post.return_value.status_code = 200 self.channel.notify(self.check) - assert Notification.objects.count() == 1 + self.assertEqual(Notification.objects.count(), 1) args, kwargs = mock_post.call_args payload = kwargs["data"] @@ -63,3 +68,21 @@ class NotifyTestCase(BaseTestCase): self.channel.notify(self.check) n = Notification.objects.first() self.assertEqual(n.error, "Rate limit exceeded") + + @patch("hc.api.transports.requests.request") + def test_it_cancels_emergency_notification(self, mock_post): + self._setup_data("123|2|0", status="up") + mock_post.return_value.status_code = 200 + + self.channel.notify(self.check) + self.assertEqual(Notification.objects.count(), 1) + + self.assertEqual(mock_post.call_count, 2) + + cancel_args, cancel_kwargs = mock_post.call_args_list[0] + expected = "/receipts/cancel_by_tag/%s.json" % self.check.unique_key + self.assertEqual(cancel_args[1], API + expected) + + up_args, up_kwargs = mock_post.call_args_list[1] + payload = up_kwargs["data"] + self.assertIn("UP", payload["title"]) diff --git a/hc/api/transports.py b/hc/api/transports.py index 15633149..619c38e6 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -380,25 +380,36 @@ class Pushbullet(HttpTransport): class Pushover(HttpTransport): URL = "https://api.pushover.net/1/messages.json" + CANCEL_TMPL = "https://api.pushover.net/1/receipts/cancel_by_tag/%s.json" def notify(self, check): pieces = self.channel.value.split("|") - user_key, prio = pieces[0], pieces[1] + user_key, down_prio = pieces[0], pieces[1] + # The third element, if present, is the priority for "up" events - if len(pieces) == 3 and check.status == "up": - prio = pieces[2] + up_prio = down_prio + if len(pieces) == 3: + up_prio = pieces[2] from hc.api.models import TokenBucket if not TokenBucket.authorize_pushover(user_key): return "Rate limit exceeded" + # If down events have the emergency priority, + # send a cancel call first + if check.status == "up" and down_prio == "2": + url = self.CANCEL_TMPL % check.unique_key + payload = {"token": settings.PUSHOVER_API_TOKEN} + self.post(url, data=payload) + others = self.checks().filter(status="down").exclude(code=check.code) # list() executes the query, to avoid DB access while # rendering a template ctx = {"check": check, "down_checks": list(others)} text = tmpl("pushover_message.html", **ctx) title = tmpl("pushover_title.html", **ctx) + prio = up_prio if check.status == "up" else down_prio payload = { "token": settings.PUSHOVER_API_TOKEN, @@ -407,6 +418,7 @@ class Pushover(HttpTransport): "title": title, "html": 1, "priority": int(prio), + "tags": check.unique_key, } # Emergency notification