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.

139 lines
4.2 KiB

  1. import time
  2. from threading import Thread
  3. from django.core.management.base import BaseCommand
  4. from django.utils import timezone
  5. from hc.api.models import Check, Flip
  6. def notify(flip_id, stdout):
  7. flip = Flip.objects.get(id=flip_id)
  8. check = flip.owner
  9. # Set the historic status here but *don't save it*.
  10. # It would be nicer to pass the status explicitly, as a separate parameter.
  11. check.status = flip.new_status
  12. # And just to make sure it doesn't get saved by a future coding accident:
  13. setattr(check, "save", None)
  14. tmpl = "Sending alert, status=%s, code=%s\n"
  15. stdout.write(tmpl % (flip.new_status, check.code))
  16. # Set dates for followup nags
  17. if flip.new_status == "down":
  18. check.project.set_next_nag_date()
  19. # Send notifications
  20. errors = flip.send_alerts()
  21. for ch, error in errors:
  22. stdout.write("ERROR: %s %s %s\n" % (ch.kind, ch.value, error))
  23. def notify_on_thread(flip_id, stdout):
  24. t = Thread(target=notify, args=(flip_id, stdout))
  25. t.start()
  26. class Command(BaseCommand):
  27. help = 'Sends UP/DOWN email alerts'
  28. def add_arguments(self, parser):
  29. parser.add_argument(
  30. '--no-loop',
  31. action='store_false',
  32. dest='loop',
  33. default=True,
  34. help='Do not keep running indefinitely in a 2 second wait loop',
  35. )
  36. parser.add_argument(
  37. '--no-threads',
  38. action='store_false',
  39. dest='use_threads',
  40. default=False,
  41. help='Send alerts synchronously, without using threads',
  42. )
  43. def process_one_flip(self, use_threads=True):
  44. """ Find unprocessed flip, send notifications. """
  45. # Order by processed, otherwise Django will automatically order by id
  46. # and make the query less efficient
  47. q = Flip.objects.filter(processed=None).order_by("processed")
  48. flip = q.first()
  49. if flip is None:
  50. return False
  51. q = Flip.objects.filter(id=flip.id, processed=None)
  52. num_updated = q.update(processed=timezone.now())
  53. if num_updated != 1:
  54. # Nothing got updated: another worker process got there first.
  55. return True
  56. if use_threads:
  57. notify_on_thread(flip.id, self.stdout)
  58. else:
  59. notify(flip.id, self.stdout)
  60. return True
  61. def handle_going_down(self):
  62. """ Process a single check going down. """
  63. now = timezone.now()
  64. # In PostgreSQL, add this index to run the below query efficiently:
  65. # CREATE INDEX api_check_up ON api_check (alert_after) WHERE status = 'up'
  66. q = Check.objects.filter(alert_after__lt=now).exclude(status="down")
  67. # Sort by alert_after, to avoid unnecessary sorting by id:
  68. check = q.order_by("alert_after").first()
  69. if check is None:
  70. return False
  71. old_status = check.status
  72. q = Check.objects.filter(id=check.id, status=old_status)
  73. if check.get_status(with_started=False) != "down":
  74. # It is not down yet. Update alert_after
  75. q.update(alert_after=check.going_down_after())
  76. return True
  77. # Atomically update status
  78. flip_time = check.going_down_after()
  79. num_updated = q.update(alert_after=None, status="down")
  80. if num_updated != 1:
  81. # Nothing got updated: another worker process got there first.
  82. return True
  83. flip = Flip(owner=check)
  84. flip.created = flip_time
  85. flip.old_status = old_status
  86. flip.new_status = "down"
  87. flip.save()
  88. return True
  89. def handle(self, use_threads=True, loop=True, *args, **options):
  90. self.stdout.write("sendalerts is now running\n")
  91. i, sent = 0, 0
  92. while True:
  93. # Create flips for any checks going down
  94. while self.handle_going_down():
  95. pass
  96. # Process the unprocessed flips
  97. while self.process_one_flip(use_threads):
  98. sent += 1
  99. if not loop:
  100. break
  101. time.sleep(2)
  102. i += 1
  103. if i % 60 == 0:
  104. timestamp = timezone.now().isoformat()
  105. self.stdout.write("-- MARK %s --\n" % timestamp)
  106. return "Sent %d alert(s)" % sent