Browse Source

Foundation for "fail" pings (cc: #151)

pull/178/head
Pēteris Caune 7 years ago
parent
commit
3fc84ca0ff
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 94 additions and 11 deletions
  1. +23
    -0
      hc/api/migrations/0040_auto_20180517_1336.py
  2. +15
    -1
      hc/api/models.py
  3. +10
    -0
      hc/api/tests/test_check_model.py
  4. +22
    -0
      hc/api/tests/test_ping.py
  5. +3
    -0
      hc/api/urls.py
  6. +2
    -2
      hc/api/views.py
  7. +2
    -2
      static/css/log.css
  8. +10
    -3
      templates/front/log.html
  9. +7
    -3
      templates/front/ping_details.html

+ 23
- 0
hc/api/migrations/0040_auto_20180517_1336.py View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.4 on 2018-05-17 13:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0039_remove_check_last_ping_body'),
]
operations = [
migrations.AddField(
model_name='check',
name='last_ping_was_fail',
field=models.NullBooleanField(default=False),
),
migrations.AddField(
model_name='ping',
name='fail',
field=models.NullBooleanField(default=False),
),
]

+ 15
- 1
hc/api/models.py View File

@ -27,6 +27,8 @@ DEFAULT_GRACE = td(hours=1)
CHECK_KINDS = (("simple", "Simple"),
("cron", "Cron"))
PING_KINDS = (("", "OK"), ("fail", "Fail"))
CHANNEL_KINDS = (("email", "Email"),
("webhook", "Webhook"),
("hipchat", "HipChat"),
@ -70,6 +72,7 @@ class Check(models.Model):
tz = models.CharField(max_length=36, default="UTC")
n_pings = models.IntegerField(default=0)
last_ping = models.DateTimeField(null=True, blank=True)
last_ping_was_fail = models.NullBooleanField(default=False)
has_confirmation_link = models.BooleanField(default=False)
alert_after = models.DateTimeField(null=True, blank=True, editable=False)
status = models.CharField(max_length=6, choices=STATUSES, default="new")
@ -121,6 +124,9 @@ class Check(models.Model):
if self.status in ("new", "paused"):
return self.status
if self.last_ping_was_fail:
return "down"
if now is None:
now = timezone.now()
@ -129,6 +135,11 @@ class Check(models.Model):
def get_alert_after(self):
""" Return the datetime when check potentially goes down. """
# For "fail" pings, sendalerts should the check right
# after receiving the ping, without waiting for the grace time:
if self.last_ping_was_fail:
return self.last_ping
return self.get_grace_start() + self.grace
def in_grace_period(self):
@ -182,9 +193,10 @@ class Check(models.Model):
return result
def ping(self, remote_addr, scheme, method, ua, body):
def ping(self, remote_addr, scheme, method, ua, body, is_fail=False):
self.n_pings = models.F("n_pings") + 1
self.last_ping = timezone.now()
self.last_ping_was_fail = is_fail
self.has_confirmation_link = "confirm" in str(body).lower()
self.alert_after = self.get_alert_after()
if self.status in ("new", "paused"):
@ -195,6 +207,7 @@ class Check(models.Model):
ping = Ping(owner=self)
ping.n = self.n_pings
ping.fail = is_fail
ping.remote_addr = remote_addr
ping.scheme = scheme
ping.method = method
@ -209,6 +222,7 @@ class Ping(models.Model):
n = models.IntegerField(null=True)
owner = models.ForeignKey(Check, models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
fail = models.NullBooleanField(default=False)
scheme = models.CharField(max_length=10, default="http")
remote_addr = models.GenericIPAddressField(blank=True, null=True)
method = models.CharField(max_length=10, blank=True)


+ 10
- 0
hc/api/tests/test_check_model.py View File

@ -87,3 +87,13 @@ class CheckModelTestCase(TestCase):
d = check.to_dict()
self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00")
def test_status_checks_the_fail_flag(self):
check = Check()
check.status = "up"
check.last_ping = timezone.now() - timedelta(minutes=5)
check.last_ping_was_fail = True
# The computed status should be "down" because last_ping_was_fail
# is set.
self.assertEqual(check.get_status(), "down")

+ 22
- 0
hc/api/tests/test_ping.py View File

@ -1,4 +1,5 @@
from django.test import Client, TestCase
from django.utils.timezone import now
from hc.api.models import Check, Ping
@ -114,3 +115,24 @@ class PingTestCase(TestCase):
self.check.refresh_from_db()
self.assertTrue(self.check.has_confirmation_link)
def test_ping_resets_fail_flag(self):
self.check.last_ping_was_fail = True
self.check.save()
r = self.client.get("/ping/%s/" % self.check.code)
self.assertEqual(r.status_code, 200)
self.check.refresh_from_db()
self.assertFalse(self.check.last_ping_was_fail)
def test_fail_endpoint_works(self):
r = self.client.get("/ping/%s/fail" % self.check.code)
self.assertEqual(r.status_code, 200)
self.check.refresh_from_db()
self.assertTrue(self.check.last_ping_was_fail)
self.assertTrue(self.check.alert_after <= now())
ping = Ping.objects.latest("id")
self.assertTrue(ping.fail)

+ 3
- 0
hc/api/urls.py View File

@ -5,6 +5,9 @@ from hc.api import views
urlpatterns = [
path('ping/<uuid:code>/', views.ping, name="hc-ping-slash"),
path('ping/<uuid:code>', views.ping, name="hc-ping"),
path('ping/<uuid:code>/fail', views.ping, {"is_fail": True},
name="hc-fail"),
path('api/v1/checks/', views.checks),
path('api/v1/checks/<uuid:code>', views.update, name="hc-api-update"),
path('api/v1/checks/<uuid:code>/pause', views.pause, name="hc-api-pause"),


+ 2
- 2
hc/api/views.py View File

@ -18,7 +18,7 @@ from hc.lib.badges import check_signature, get_badge_svg
@csrf_exempt
@never_cache
def ping(request, code):
def ping(request, code, is_fail=False):
check = get_object_or_404(Check, code=code)
headers = request.META
@ -29,7 +29,7 @@ def ping(request, code):
ua = headers.get("HTTP_USER_AGENT", "")
body = request.body.decode()
check.ping(remote_addr, scheme, method, ua, body)
check.ping(remote_addr, scheme, method, ua, body, is_fail)
response = HttpResponse("OK")
response["Access-Control-Allow-Origin"] = "*"


+ 2
- 2
static/css/log.css View File

@ -41,7 +41,7 @@
}
}
#log .details span {
#log .details span.ua-body {
font-family: "Lucida Console", Monaco, monospace;
font-size: 11.7px;
color: #888;
@ -72,4 +72,4 @@
#log .alert-info {
font-size: 12px;
}
}

+ 10
- 3
templates/front/log.html View File

@ -39,11 +39,18 @@
</td>
<td class="date"></td>
<td class="time"></td>
<td class="text-right">
{% if event.fail %}
<span class="label label-danger">Failure</span>
{% else %}
<span class="label label-success">OK</span>
{% endif %}
</td>
<td class="details">
<div>
{% if event.scheme == "email" %}
{{ event.ua }}
<span>
<span class="ua-body">
{% if event.body %}
- {{ event.body|trunc }}
{% endif %}
@ -54,7 +61,7 @@
{% if event.remote_addr %}
from {{ event.remote_addr }}
{% endif %}
<span>
<span class="ua-body">
{% if event.ua %}
- {{ event.ua }}
{% endif %}
@ -74,7 +81,7 @@
</td>
<td class="date"></td>
<td class="time"></td>
<td class="alert-info">
<td class="alert-info" colspan="2">
{% if event.channel.kind == "email" %}
Sent email alert to {{ event.channel.value }}
{% elif event.channel.kind == "slack" %}


+ 7
- 3
templates/front/ping_details.html View File

@ -1,5 +1,9 @@
<div class="modal-body">
<h3>Ping #{{ ping.n }}</h3>
<h3>Ping #{{ ping.n }}
{% if ping.fail %}
<span class="text-danger">(received via the <code>/fail</code> endpoint)</span>
{% endif %}
</h3>
<div class="row">
<div class="col-sm-6">
@ -49,11 +53,11 @@
<span class="ua">{{ ping.ua }}</span>
</p>
</div>
{% endif %}
</div>
{% if ping.body %}
<h4>Request Body</h4>
<pre>{{ ping.body }}</pre>
{% endif %}
</div>
</div>

Loading…
Cancel
Save