- import time
- from threading import Thread
-
- from django.core.management.base import BaseCommand
- from django.utils import timezone
- from hc.api.models import Check, Flip
-
-
- def notify(flip_id, stdout):
- flip = Flip.objects.get(id=flip_id)
-
- check = flip.owner
- # Set the historic status here but *don't save it*.
- # It would be nicer to pass the status explicitly, as a separate parameter.
- check.status = flip.new_status
- # And just to make sure it doesn't get saved by a future coding accident:
- setattr(check, "save", None)
-
- tmpl = "Sending alert, status=%s, code=%s\n"
- stdout.write(tmpl % (flip.new_status, check.code))
-
- # Set dates for followup nags
- if flip.new_status == "down" and check.user.profile:
- check.user.profile.set_next_nag_date()
-
- # Send notifications
- errors = check.send_alert(flip)
- for ch, error in errors:
- stdout.write("ERROR: %s %s %s\n" % (ch.kind, ch.value, error))
-
-
- def notify_on_thread(flip_id, stdout):
- t = Thread(target=notify, args=(flip_id, stdout))
- t.start()
-
-
- class Command(BaseCommand):
- help = 'Sends UP/DOWN email alerts'
-
- def add_arguments(self, parser):
- parser.add_argument(
- '--no-loop',
- action='store_false',
- dest='loop',
- default=True,
- help='Do not keep running indefinitely in a 2 second wait loop',
- )
-
- parser.add_argument(
- '--no-threads',
- action='store_false',
- dest='use_threads',
- default=False,
- help='Send alerts synchronously, without using threads',
- )
-
- def process_one_flip(self, use_threads=True):
- """ Find unprocessed flip, send notifications. """
-
- # Order by processed, otherwise Django will automatically order by id
- # and make the query less efficient
- q = Flip.objects.filter(processed=None).order_by("processed")
- flip = q.first()
- if flip is None:
- return False
-
- q = Flip.objects.filter(id=flip.id, processed=None)
- num_updated = q.update(processed=timezone.now())
- if num_updated != 1:
- # Nothing got updated: another worker process got there first.
- return True
-
- if use_threads:
- notify_on_thread(flip.id, self.stdout)
- else:
- notify(flip.id, self.stdout)
-
- return True
-
- def handle_going_down(self):
- """ Process a single check going down. """
-
- now = timezone.now()
-
- # In PostgreSQL, add this index to run the below query efficiently:
- # CREATE INDEX api_check_up ON api_check (alert_after) WHERE status = 'up'
-
- q = Check.objects.filter(alert_after__lt=now).exclude(status="down")
- # Sort by alert_after, to avoid unnecessary sorting by id:
- check = q.order_by("alert_after").first()
- if check is None:
- return False
-
- old_status = check.status
- q = Check.objects.filter(id=check.id, status=old_status)
-
- if check.get_status(with_started=False) != "down":
- # It is not down yet. Update alert_after
- q.update(alert_after=check.going_down_after())
- return True
-
- # Atomically update status
- flip_time = check.going_down_after()
- num_updated = q.update(alert_after=None, status="down")
- if num_updated != 1:
- # Nothing got updated: another worker process got there first.
- return True
-
- flip = Flip(owner=check)
- flip.created = flip_time
- flip.old_status = old_status
- flip.new_status = "down"
- flip.save()
-
- return True
-
- def handle(self, use_threads=True, loop=True, *args, **options):
- self.stdout.write("sendalerts is now running\n")
-
- i, sent = 0, 0
- while True:
- # Create flips for any checks going down
- while self.handle_going_down():
- pass
-
- # Process the unprocessed flips
- while self.process_one_flip(use_threads):
- sent += 1
-
- if not loop:
- break
-
- time.sleep(2)
- i += 1
- if i % 60 == 0:
- timestamp = timezone.now().isoformat()
- self.stdout.write("-- MARK %s --\n" % timestamp)
-
- return "Sent %d alert(s)" % sent
|