def handle_email(request): if request.method != "POST": return HttpResponseBadRequest() events = json.loads(request.POST["mandrill_events"]) for event in events: for recipient_address, recipient_name in event["msg"]["to"]: code, domain = recipient_address.split("@") try: check = Check.objects.get(code=code) except ValueError: continue except Check.DoesNotExist: continue check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() if check.status == "new": check.status = "up" check.save() ping = Ping(owner=check) ping.scheme = "email" ping.save() response = HttpResponse("OK") return response
def setUp(self): super(LogTestCase, self).setUp() self.check = Check(user=self.alice) self.check.save() ping = Ping(owner=self.check) ping.save()
def setUp(self): self.alice = User(username="******") self.alice.set_password("password") self.alice.save() self.check = Check(user=self.alice) self.check.save() ping = Ping(owner=self.check) ping.save()
class GetPingsTestCase(BaseTestCase): def setUp(self): super().setUp() self.a1 = Check(project=self.project, name="Alice 1") self.a1.timeout = td(seconds=3600) self.a1.grace = td(seconds=900) self.a1.n_pings = 1 self.a1.status = "new" self.a1.tags = "a1-tag a1-additional-tag" self.a1.desc = "This is description" self.a1.save() self.ping = Ping(owner=self.a1) self.ping.n = 1 self.ping.remote_addr = "1.2.3.4" self.ping.scheme = "https" self.ping.method = "get" self.ping.ua = "foo-agent" self.ping.save() self.url = "/api/v1/checks/%s/pings/" % self.a1.code def get(self, api_key="X" * 32): return self.csrf_client.get(self.url, HTTP_X_API_KEY=api_key) def test_it_works(self): r = self.get() self.assertEqual(r.status_code, 200) self.assertEqual(r["Access-Control-Allow-Origin"], "*") doc = r.json() self.assertEqual(len(doc["pings"]), 1) ping = doc["pings"][0] self.assertEqual(ping["n"], 1) self.assertEqual(ping["remote_addr"], "1.2.3.4") self.assertEqual(ping["scheme"], "https") self.assertEqual(ping["method"], "get") self.assertEqual(ping["ua"], "foo-agent") def test_readonly_key_is_not_allowed(self): self.project.api_key_readonly = "R" * 32 self.project.save() r = self.get(api_key=self.project.api_key_readonly) self.assertEqual(r.status_code, 401) def test_it_rejects_post(self): r = self.csrf_client.post(self.url, HTTP_X_API_KEY="X" * 32) self.assertEqual(r.status_code, 405) def test_it_handles_missing_api_key(self): r = self.client.get(self.url) self.assertContains(r, "missing api key", status_code=401)
def ping(request, code): try: check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() check.last_ping = timezone.now() if check.status == "new": check.status = "up" check.save() ping = Ping(owner=check) headers = request.META ping.remote_addr = headers.get("HTTP_X_REAL_IP", headers["REMOTE_ADDR"]) ping.scheme = headers.get("HTTP_X_SCHEME", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.body = request.body ping.save() response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response
def ping(request, code): try: check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() if check.status in ("new", "paused"): check.status = "up" if check.runs_too_often() == 'over': check.status = "over" check.save() check.refresh_from_db() # check from the db send mail to user on jobs that are running too often check.runs_too_often() ping = Ping(owner=check) headers = request.META ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() response = HttpResponse("OK" + check.status) response["Access-Control-Allow-Origin"] = "*" return response
def ping(request, code): check = get_object_or_404(Check, code=code) check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() check.last_ping_body = request.body[:10000] check.alert_after = check.get_alert_after() if check.status in ("new", "paused"): check.status = "up" check.save() check.refresh_from_db() ping = Ping(owner=check) headers = request.META ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response
def setUp(self): super(LogTestCase, self).setUp() self.alice = User(username="******", email="*****@*****.**") self.alice.set_password("password") self.alice.save() self.check = Check(user=self.alice) self.check.save() ping = Ping(owner=self.check) ping.save()
def test_it_works(self): p = Ping(owner=self.check) p.created = now() p.save() n = Notification.objects.create(owner=self.check, channel=self.channel) n.created = p.created - td(minutes=1) n.save() output = Command().handle() self.assertIn("Pruned 1 notifications", output) self.assertFalse(Notification.objects.exists())
def ping(request, code): try: check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() if check.status == "new": check.status = "up" check.save() check.refresh_from_db() ping = Ping(owner=check) headers = request.META ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response
def setUp(self): super().setUp() self.a1 = Check(project=self.project, name="Alice 1") self.a1.timeout = td(seconds=3600) self.a1.grace = td(seconds=900) self.a1.n_pings = 1 self.a1.status = "new" self.a1.tags = "a1-tag a1-additional-tag" self.a1.desc = "This is description" self.a1.save() self.ping = Ping(owner=self.a1) self.ping.n = 1 self.ping.remote_addr = "1.2.3.4" self.ping.scheme = "https" self.ping.method = "get" self.ping.ua = "foo-agent" self.ping.save() self.url = "/api/v1/checks/%s/pings/" % self.a1.code
def ping(request, code): try: check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() check.n_pings = F("n_pings") + 1 if check.last_ping: now = timezone.now() reverse_grace = check.timeout - check.grace if reverse_grace <= td(seconds=0): too_early = now else: too_early = check.last_ping + reverse_grace if now <= too_early: check.often = True else: check.often = False check.last_ping = timezone.now() if check.status in ("new", "paused"): check.status = "up" check.save() check.refresh_from_db() if check.often: check.send_alert() ping = Ping(owner=check) headers = request.META ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response
def setUp(self): super(LogTestCase, self).setUp() self.check = Check(user=self.alice) self.check.save() ping = Ping(owner=self.check) ping.save() # Older MySQL versions don't store microseconds. This makes sure # the ping is older than any notifications we may create later: ping.created = "2000-01-01T00:00:00+00:00" ping.save()
def setUp(self): super().setUp() self.check = Check(project=self.project) self.check.name = "Daily Backup" self.check.desc = "Line 1\nLine2" self.check.tags = "foo bar" self.check.status = "down" self.check.last_ping = now() - td(minutes=61) self.check.n_pings = 112233 self.check.save() self.ping = Ping(owner=self.check) self.ping.remote_addr = "1.2.3.4" self.ping.body = "Body Line 1\nBody Line 2" self.ping.save() self.channel = Channel(project=self.project) self.channel.kind = "email" self.channel.value = "*****@*****.**" self.channel.email_verified = True self.channel.save() self.channel.checks.add(self.check)
def log(request, code): check = get_object_or_404(Check, code=code) if check.user != request.user: return HttpResponseForbidden() profile = Profile.objects.for_user(request.user) limit = profile.ping_log_limit pings = Ping.objects.filter(owner=check).order_by("-id")[:limit] pings = list(pings.iterator()) # oldest-to-newest order will be more convenient for adding # "not received" placeholders: pings.reverse() # Add a dummy ping object at the end. We iterate over *pairs* of pings # and don't want to handle a special case of a check with a single ping. pings.append(Ping(created=timezone.now())) # Now go through pings, calculate time gaps, and decorate # the pings list for convenient use in template wrapped = [] early = False for older, newer in pairwise(pings): wrapped.append({"ping": older, "early": early}) # Fill in "missed ping" placeholders: expected_date = older.created + check.timeout n_blanks = 0 while expected_date + check.grace < newer.created and n_blanks < 10: wrapped.append({"placeholder_date": expected_date}) expected_date = expected_date + check.timeout n_blanks += 1 # Prepare early flag for next ping to come early = older.created + check.timeout > newer.created + check.grace reached_limit = len(pings) > limit wrapped.reverse() ctx = { "check": check, "pings": wrapped, "num_pings": len(pings), "limit": limit, "show_limit_notice": reached_limit and settings.USE_PAYMENTS } return render(request, "front/log.html", ctx)
def ping(request, code): try: check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() # num_pings= Ping.objects.filter(owner=check).filter(created__range=(check.prev_ping,deadline)) # import pdb; pdb.set_trace() # if len(num_pings) > 1: if check.last_ping: deadline = check.last_ping + check.timeout - check.grace if timezone.now() > check.last_ping and timezone.now() < deadline: check.often = True else: check.often = False check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() if check.status in ("new", "paused"): check.status = "up" check.save() check.refresh_from_db() if check.often: check.often_alert() ping = Ping(owner=check) headers = request.META ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response
def ping(request, code): try: check = Check.objects.get(code=code) except Check.DoesNotExist: return HttpResponseBadRequest() if check.status in ("new", "paused"): check.status = "up" # only confirm if a check is too_often if the check is not in down-wise statuses if check.status not in ("down"): if check.running_too_often(): check.status = "too often" else: check.status = "up" now = timezone.now() check.n_pings = F("n_pings") + 1 check.last_ping = now # store expected time for next ping check.next_ping = now + check.timeout check.save() check.refresh_from_db() ping = Ping(owner=check) headers = request.META ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response
def _ping(headers, check): check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() if check.status == "new": check.status = "up" check.save() check.refresh_from_db() ping = Ping(owner=check) ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) ping.remote_addr = remote_addr.split(",")[0] ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") ping.method = headers["REQUEST_METHOD"] # If User-Agent is longer than 200 characters, truncate it: ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] ping.save() return
class NotifyEmailTestCase(BaseTestCase): def setUp(self): super().setUp() self.check = Check(project=self.project) self.check.name = "Daily Backup" self.check.desc = "Line 1\nLine2" self.check.tags = "foo bar" self.check.status = "down" self.check.last_ping = now() - td(minutes=61) self.check.n_pings = 112233 self.check.save() self.ping = Ping(owner=self.check) self.ping.remote_addr = "1.2.3.4" self.ping.body = "Body Line 1\nBody Line 2" self.ping.save() self.channel = Channel(project=self.project) self.channel.kind = "email" self.channel.value = "*****@*****.**" self.channel.email_verified = True self.channel.save() self.channel.checks.add(self.check) def test_email(self): self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "") # And email should have been sent self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to[0], "*****@*****.**") self.assertTrue("X-Status-Url" in email.extra_headers) self.assertTrue("List-Unsubscribe" in email.extra_headers) self.assertTrue("List-Unsubscribe-Post" in email.extra_headers) html = email.alternatives[0][0] self.assertIn("Daily Backup", html) self.assertIn("Line 1<br>Line2", html) self.assertIn("Alices Project", html) self.assertIn("foo</code>", html) self.assertIn("bar</code>", html) self.assertIn("1 day", html) self.assertIn("from 1.2.3.4", html) self.assertIn("112233", html) self.assertIn("Body Line 1<br>Body Line 2", html) def test_it_shows_cron_schedule(self): self.check.kind = "cron" self.check.schedule = "0 18-23,0-8 * * *" self.check.save() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("<code>0 18-23,0-8 * * *</code>", html) def test_it_truncates_long_body(self): self.ping.body = "X" * 10000 + ", and the rest gets cut off" self.ping.save() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("[truncated]", html) self.assertNotIn("the rest gets cut off", html) def test_it_handles_missing_ping_object(self): self.ping.delete() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("Daily Backup", html) def test_it_handles_missing_profile(self): self.channel.value = "*****@*****.**" self.channel.save() self.channel.notify(self.check) email = mail.outbox[0] self.assertEqual(email.to[0], "*****@*****.**") html = email.alternatives[0][0] self.assertIn("Daily Backup", html) self.assertNotIn("Projects Overview", html) def test_email_transport_handles_json_value(self): payload = {"value": "*****@*****.**", "up": True, "down": True} self.channel.value = json.dumps(payload) self.channel.save() self.channel.notify(self.check) # And email should have been sent self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to[0], "*****@*****.**") def test_it_reports_unverified_email(self): self.channel.email_verified = False self.channel.save() self.channel.notify(self.check) # If an email is not verified, it should say so in the notification: n = Notification.objects.get() self.assertEqual(n.error, "Email not verified") def test_email_checks_up_down_flags(self): payload = {"value": "*****@*****.**", "up": True, "down": False} self.channel.value = json.dumps(payload) self.channel.save() self.channel.notify(self.check) # This channel should not notify on "down" events: self.assertEqual(Notification.objects.count(), 0) self.assertEqual(len(mail.outbox), 0) def test_email_handles_amperstand(self): self.check.name = "Foo & Bar" self.check.save() self.channel.notify(self.check) email = mail.outbox[0] self.assertEqual(email.subject, "DOWN | Foo & Bar")
class NotifyEmailTestCase(BaseTestCase): def setUp(self): super().setUp() self.check = Check(project=self.project) self.check.name = "Daily Backup" self.check.desc = "Line 1\nLine2" self.check.tags = "foo bar" self.check.status = "down" self.check.last_ping = now() - td(minutes=61) self.check.n_pings = 112233 self.check.save() self.ping = Ping(owner=self.check) self.ping.n = 1 self.ping.remote_addr = "1.2.3.4" self.ping.body_raw = b"Body Line 1\nBody Line 2" self.ping.save() self.channel = Channel(project=self.project) self.channel.kind = "email" self.channel.value = "*****@*****.**" self.channel.email_verified = True self.channel.save() self.channel.checks.add(self.check) def test_it_works(self): self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "") # And email should have been sent self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to[0], "*****@*****.**") self.assertEqual(email.extra_headers["X-Status-Url"], n.status_url()) self.assertTrue("List-Unsubscribe" in email.extra_headers) self.assertTrue("List-Unsubscribe-Post" in email.extra_headers) html = email.alternatives[0][0] self.assertIn("Daily Backup", html) self.assertIn("Line 1<br>Line2", html) self.assertIn("Alices Project", html) self.assertIn("foo</code>", html) self.assertIn("bar</code>", html) self.assertIn("1 day", html) self.assertIn("from 1.2.3.4", html) self.assertIn("112233", html) self.assertIn("Body Line 1<br>Body Line 2", html) # Check's code must not be in the html self.assertNotIn(str(self.check.code), html) # Check's code must not be in the plain text body self.assertNotIn(str(self.check.code), email.body) def test_it_displays_body(self): self.ping.body = "Body Line 1\nBody Line 2" self.ping.body_raw = None self.ping.save() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("Line 1<br>Line2", html) @override_settings(S3_BUCKET="test-bucket") @patch("hc.api.models.get_object") def test_it_loads_body_from_object_storage(self, get_object): get_object.return_value = b"Body Line 1\nBody Line 2" self.ping.object_size = 1000 self.ping.body_raw = None self.ping.save() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("Line 1<br>Line2", html) args, kwargs = get_object.call_args code, n = args self.assertEqual(code, self.check.code) self.assertEqual(n, 1) def test_it_shows_cron_schedule(self): self.check.kind = "cron" self.check.schedule = "0 18-23,0-8 * * *" self.check.save() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("<code>0 18-23,0-8 * * *</code>", html) def test_it_truncates_long_body(self): self.ping.body = "X" * 10000 + ", and the rest gets cut off" self.ping.save() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("[truncated]", html) self.assertNotIn("the rest gets cut off", html) def test_it_handles_missing_ping_object(self): self.ping.delete() self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("Daily Backup", html) def test_it_handles_missing_profile(self): self.channel.value = "*****@*****.**" self.channel.save() self.channel.notify(self.check) email = mail.outbox[0] self.assertEqual(email.to[0], "*****@*****.**") html = email.alternatives[0][0] self.assertIn("Daily Backup", html) self.assertNotIn("Projects Overview", html) def test_email_transport_handles_json_value(self): payload = {"value": "*****@*****.**", "up": True, "down": True} self.channel.value = json.dumps(payload) self.channel.save() self.channel.notify(self.check) # And email should have been sent self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to[0], "*****@*****.**") def test_it_reports_unverified_email(self): self.channel.email_verified = False self.channel.save() self.channel.notify(self.check) # If an email is not verified, it should say so in the notification: n = Notification.objects.get() self.assertEqual(n.error, "Email not verified") def test_email_checks_up_down_flags(self): payload = {"value": "*****@*****.**", "up": True, "down": False} self.channel.value = json.dumps(payload) self.channel.save() self.channel.notify(self.check) # This channel should not notify on "down" events: self.assertEqual(Notification.objects.count(), 0) self.assertEqual(len(mail.outbox), 0) def test_email_handles_amperstand(self): self.check.name = "Foo & Bar" self.check.save() self.channel.notify(self.check) email = mail.outbox[0] self.assertEqual(email.subject, "DOWN | Foo & Bar") @override_settings(S3_BUCKET="test-bucket") @patch("hc.api.models.get_object") def test_it_handles_pending_body(self, get_object): get_object.return_value = None self.ping.object_size = 1000 self.ping.body_raw = None self.ping.save() with patch("hc.api.transports.time.sleep"): self.channel.notify(self.check) email = mail.outbox[0] html = email.alternatives[0][0] self.assertIn("The request body data is being processed", html)