You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
5.1 KiB

10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. # coding: utf-8
  2. from datetime import timedelta as td
  3. import hashlib
  4. import json
  5. import uuid
  6. from django.conf import settings
  7. from django.core.urlresolvers import reverse
  8. from django.contrib.auth.models import User
  9. from django.db import models
  10. from django.utils import timezone
  11. import requests
  12. from hc.lib import emails
  13. STATUSES = (("up", "Up"), ("down", "Down"), ("new", "New"))
  14. DEFAULT_TIMEOUT = td(days=1)
  15. DEFAULT_GRACE = td(hours=1)
  16. CHANNEL_KINDS = (("email", "Email"), ("webhook", "Webhook"),
  17. ("pd", "PagerDuty"))
  18. class Check(models.Model):
  19. name = models.CharField(max_length=100, blank=True)
  20. code = models.UUIDField(default=uuid.uuid4, editable=False)
  21. user = models.ForeignKey(User, blank=True, null=True)
  22. created = models.DateTimeField(auto_now_add=True)
  23. timeout = models.DurationField(default=DEFAULT_TIMEOUT)
  24. grace = models.DurationField(default=DEFAULT_GRACE)
  25. last_ping = models.DateTimeField(null=True, blank=True)
  26. alert_after = models.DateTimeField(null=True, blank=True, editable=False)
  27. status = models.CharField(max_length=6, choices=STATUSES, default="new")
  28. def name_then_code(self):
  29. if self.name:
  30. return self.name
  31. return str(self.code)
  32. def url(self):
  33. return settings.PING_ENDPOINT + str(self.code)
  34. def email(self):
  35. return "%s@%s" % (self.code, settings.PING_EMAIL_DOMAIN)
  36. def send_alert(self):
  37. if self.status not in ("up", "down"):
  38. raise NotImplemented("Unexpected status: %s" % self.status)
  39. for channel in self.channel_set.all():
  40. channel.notify(self)
  41. def get_status(self):
  42. if self.status == "new":
  43. return "new"
  44. now = timezone.now()
  45. if self.last_ping + self.timeout > now:
  46. return "up"
  47. if self.last_ping + self.timeout + self.grace > now:
  48. return "grace"
  49. return "down"
  50. def assign_all_channels(self):
  51. if self.user:
  52. channels = Channel.objects.filter(user=self.user)
  53. self.channel_set.add(*channels)
  54. class Ping(models.Model):
  55. owner = models.ForeignKey(Check)
  56. created = models.DateTimeField(auto_now_add=True)
  57. scheme = models.CharField(max_length=10, default="http")
  58. remote_addr = models.GenericIPAddressField(blank=True, null=True)
  59. method = models.CharField(max_length=10, blank=True)
  60. ua = models.CharField(max_length=200, blank=True)
  61. body = models.TextField(blank=True)
  62. class Channel(models.Model):
  63. code = models.UUIDField(default=uuid.uuid4, editable=False)
  64. user = models.ForeignKey(User)
  65. created = models.DateTimeField(auto_now_add=True)
  66. kind = models.CharField(max_length=20, choices=CHANNEL_KINDS)
  67. value = models.CharField(max_length=200, blank=True)
  68. email_verified = models.BooleanField(default=False)
  69. checks = models.ManyToManyField(Check)
  70. def make_token(self):
  71. seed = "%s%s" % (self.code, settings.SECRET_KEY)
  72. seed = seed.encode("utf8")
  73. return hashlib.sha1(seed).hexdigest()
  74. def send_verify_link(self):
  75. args = [self.code, self.make_token()]
  76. verify_link = reverse("hc-verify-email", args=args)
  77. verify_link = settings.SITE_ROOT + verify_link
  78. emails.verify_email(self.value, {"verify_link": verify_link})
  79. def notify(self, check):
  80. n = Notification(owner=check, channel=self)
  81. n.check_status = check.status
  82. if self.kind == "email" and self.email_verified:
  83. ctx = {
  84. "check": check,
  85. "checks": self.user.check_set.order_by("created"),
  86. "now": timezone.now()
  87. }
  88. emails.alert(self.value, ctx)
  89. n.save()
  90. elif self.kind == "webhook" and check.status == "down":
  91. try:
  92. r = requests.get(self.value, timeout=5)
  93. n.status = r.status_code
  94. except requests.exceptions.Timeout:
  95. # Well, we tried
  96. pass
  97. n.save()
  98. elif self.kind == "pd":
  99. if check.status == "down":
  100. event_type = "trigger"
  101. description = "%s is DOWN" % check.name_then_code()
  102. else:
  103. event_type = "resolve"
  104. description = "%s received a ping and is now UP" % \
  105. check.name_then_code()
  106. payload = {
  107. "service_key": self.value,
  108. "incident_key": str(check.code),
  109. "event_type": event_type,
  110. "description": description,
  111. "client": "healthchecks.io",
  112. "client_url": settings.SITE_ROOT
  113. }
  114. url = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
  115. r = requests.post(url, data=json.dumps(payload))
  116. n.status = r.status_code
  117. n.save()
  118. class Notification(models.Model):
  119. owner = models.ForeignKey(Check)
  120. check_status = models.CharField(max_length=6)
  121. channel = models.ForeignKey(Channel)
  122. created = models.DateTimeField(auto_now_add=True)
  123. status = models.IntegerField(default=0)