Browse Source

Check model supports cron-style schedule

pull/109/head
Pēteris Caune 8 years ago
parent
commit
8633a5a892
4 changed files with 102 additions and 12 deletions
  1. +30
    -0
      hc/api/migrations/0027_auto_20161205_0833.py
  2. +32
    -11
      hc/api/models.py
  3. +38
    -1
      hc/api/tests/test_check_model.py
  4. +2
    -0
      requirements.txt

+ 30
- 0
hc/api/migrations/0027_auto_20161205_0833.py View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-05 08:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0026_auto_20160415_1824'),
]
operations = [
migrations.AddField(
model_name='check',
name='schedule',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='check',
name='tz',
field=models.CharField(default='UTC', max_length=36),
),
migrations.AlterField(
model_name='channel',
name='kind',
field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('po', 'Pushover'), ('pushbullet', 'Pushbullet'), ('opsgenie', 'OpsGenie'), ('victorops', 'VictorOps')], max_length=20),
),
]

+ 32
- 11
hc/api/models.py View File

@ -3,8 +3,9 @@
import hashlib
import json
import uuid
from datetime import timedelta as td
from datetime import datetime, timedelta as td
from croniter import croniter
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
@ -53,6 +54,8 @@ class Check(models.Model):
created = models.DateTimeField(auto_now_add=True)
timeout = models.DurationField(default=DEFAULT_TIMEOUT)
grace = models.DurationField(default=DEFAULT_GRACE)
schedule = models.CharField(max_length=100, blank=True)
tz = models.CharField(max_length=36, default="UTC")
n_pings = models.IntegerField(default=0)
last_ping = models.DateTimeField(null=True, blank=True)
alert_after = models.DateTimeField(null=True, blank=True, editable=False)
@ -85,27 +88,45 @@ class Check(models.Model):
return errors
def get_status(self):
def get_grace_start(self):
""" Return the datetime when grace period starts. """
# The common case, grace starts after timeout
if not self.schedule:
return self.last_ping + self.timeout
# The complex case, next ping is expected based on cron schedule
with timezone.override(self.tz):
last_naive = timezone.make_naive(self.last_ping)
it = croniter(self.schedule, last_naive)
next_naive = it.get_next(datetime)
return timezone.make_aware(next_naive, is_dst=False)
def get_status(self, now=None):
""" Return "up" if the check is up or in grace, otherwise "down". """
if self.status in ("new", "paused"):
return self.status
now = timezone.now()
if now is None:
now = timezone.now()
if self.last_ping + self.timeout + self.grace > now:
return "up"
return "down"
return "up" if self.get_grace_start() + self.grace > now else "down"
def get_alert_after(self):
return self.last_ping + self.timeout + self.grace
""" Return the datetime when check potentially goes down. """
return self.get_grace_start() + self.grace
def in_grace_period(self):
""" Return True if check is currently in grace period. """
if self.status in ("new", "paused"):
return False
up_ends = self.last_ping + self.timeout
grace_ends = up_ends + self.grace
return up_ends < timezone.now() < grace_ends
grace_start = self.get_grace_start()
grace_end = grace_start + self.grace
return grace_start < timezone.now() < grace_end
def assign_all_channels(self):
if self.user:


+ 38
- 1
hc/api/tests/test_check_model.py View File

@ -1,4 +1,4 @@
from datetime import timedelta
from datetime import datetime, timedelta
from django.test import TestCase
from django.utils import timezone
@ -37,3 +37,40 @@ class CheckModelTestCase(TestCase):
check.status = "paused"
self.assertFalse(check.in_grace_period())
def test_status_works_with_cron_syntax(self):
dt = timezone.make_aware(datetime(2000, 1, 1), timezone=timezone.utc)
# Expect ping every midnight, default grace is 1 hour
check = Check()
check.timeout = timedelta(minutes=0)
check.schedule = "0 0 * * *"
check.status = "up"
check.last_ping = dt
# 00:30am
now = dt + timedelta(days=1, minutes=30)
self.assertEqual(check.get_status(now), "up")
# 1:30am
now = dt + timedelta(days=1, minutes=90)
self.assertEqual(check.get_status(now), "down")
def test_status_works_with_timezone(self):
dt = timezone.make_aware(datetime(2000, 1, 1), timezone=timezone.utc)
# Expect ping every day at 10am, default grace is 1 hour
check = Check()
check.timeout = timedelta(minutes=0)
check.schedule = "0 10 * * *"
check.status = "up"
check.last_ping = dt
check.tz = "Australia/Brisbane" # UTC+10
# 10:30am
now = dt + timedelta(days=1, minutes=30)
self.assertEqual(check.get_status(now), "up")
# 11:30am
now = dt + timedelta(days=1, minutes=90)
self.assertEqual(check.get_status(now), "down")

+ 2
- 0
requirements.txt View File

@ -1,3 +1,4 @@
croniter
django-appconf==1.0.1
django-ses-backend==0.1.1
Django==1.10.1
@ -5,4 +6,5 @@ django_compressor==2.1
djmail==0.11.0
premailer==2.9.6
psycopg2==2.6.1
pytz==2016.7
requests==2.9.1

Loading…
Cancel
Save