From ed4021ad472ac15bfef836caa34a201e6bcf7d6f Mon Sep 17 00:00:00 2001 From: "Chris J. Karr" Date: Tue, 30 May 2017 17:10:51 +0000 Subject: [PATCH] Inital automated status check work --- admin.py | 9 +- management/commands/pdk_run_status_checks.py | 15 ++++ .../commands/pdk_status_check_battery.py | 87 +++++++++++++++++++ migrations/0017_datasourcealert.py | 31 +++++++ migrations/0018_datasourcealert_updated.py | 22 +++++ .../0019_datasourcealert_alert_level.py | 22 +++++ models.py | 34 ++++++++ templates/pdk_base.html | 2 +- templates/pdk_home.html | 6 +- templates/pdk_source.html | 9 +- templates/tag_date_ago.html | 1 + templates/tag_source_alerts_table.html | 47 ++++++++++ templates/tag_system_alerts_table.html | 49 +++++++++++ templatetags/passive_data_kit.py | 79 +++++++++++++++++ 14 files changed, 406 insertions(+), 7 deletions(-) create mode 100644 management/commands/pdk_run_status_checks.py create mode 100644 management/commands/pdk_status_check_battery.py create mode 100644 migrations/0017_datasourcealert.py create mode 100644 migrations/0018_datasourcealert_updated.py create mode 100644 migrations/0019_datasourcealert_alert_level.py create mode 100644 templates/tag_source_alerts_table.html create mode 100644 templates/tag_system_alerts_table.html diff --git a/admin.py b/admin.py index 458ad94..0f8e5f2 100644 --- a/admin.py +++ b/admin.py @@ -1,7 +1,7 @@ from django.contrib.gis import admin from .models import DataPoint, DataBundle, DataSource, DataSourceGroup, \ - DataPointVisualizations, ReportJob + DataPointVisualizations, ReportJob, DataSourceAlert @admin.register(DataPointVisualizations) class DataPointVisualizationsAdmin(admin.OSMGeoAdmin): @@ -34,3 +34,10 @@ class DataSourceAdmin(admin.OSMGeoAdmin): class ReportJobAdmin(admin.OSMGeoAdmin): list_display = ('requester', 'requested', 'started', 'completed') list_filter = ('requested', 'started', 'completed',) + +@admin.register(DataSourceAlert) +class DataSourceAlertAdmin(admin.OSMGeoAdmin): + list_display = ('alert_name', 'data_source', 'generator_identifier', 'active', 'alert_level', \ + 'created', 'updated',) + list_filter = ('active', 'alert_level', 'created', 'alert_name', 'generator_identifier', \ + 'data_source',) diff --git a/management/commands/pdk_run_status_checks.py b/management/commands/pdk_run_status_checks.py new file mode 100644 index 0000000..e6ced8a --- /dev/null +++ b/management/commands/pdk_run_status_checks.py @@ -0,0 +1,15 @@ +from django.core.management import call_command, get_commands +from django.core.management.base import BaseCommand + +from ...decorators import handle_lock + +class Command(BaseCommand): + help = 'Runs data sanity checks to generate any alerts for potential data issues.' + + @handle_lock + def handle(self, *args, **options): + command_names = get_commands().keys() + + for command_name in command_names: + if command_name.startswith('pdk_status_check_'): + call_command(command_name) diff --git a/management/commands/pdk_status_check_battery.py b/management/commands/pdk_status_check_battery.py new file mode 100644 index 0000000..2bf2f59 --- /dev/null +++ b/management/commands/pdk_status_check_battery.py @@ -0,0 +1,87 @@ +# pylint: disable=line-too-long, no-member + +from django.core.management.base import BaseCommand +from django.utils import timezone + +from ...decorators import handle_lock + +from ...models import DataPoint, DataSource, DataSourceAlert + +GENERATOR = 'pdk-device-battery' +CRITICAL_LEVEL = 20 +WARNING_LEVEL = 33 + +class Command(BaseCommand): + help = 'Runs the battery level status check to alert when battery level is low.' + + @handle_lock + def handle(self, *args, **options): # pylint: disable=too-many-branches, too-many-statements + for source in DataSource.objects.all(): + last_battery = DataPoint.objects.filter(source=source.identifier, generator_identifier=GENERATOR).order_by('-created').first() + last_alert = DataSourceAlert.objects.filter(data_source=source, generator_identifier=GENERATOR, active=True).order_by('-created').first() + + alert_name = None + alert_details = {} + alert_level = 'info' + + if last_battery is not None: + properties = last_battery.fetch_properties() + + if properties['level'] < CRITICAL_LEVEL: + alert_name = 'Battery Level Critically Low' + alert_details['message'] = 'Latest battery level is ' + str(properties['level']) + '%.' + alert_level = 'critical' + + elif properties['level'] < WARNING_LEVEL: + alert_name = 'Battery Level Low' + alert_details['message'] = 'Latest battery level is ' + str(properties['level']) + '%.' + alert_level = 'warning' + + if alert_name is not None: + if last_alert is None or last_alert.alert_name != alert_name or last_alert.alert_level != alert_level: + if last_alert is not None: + last_alert.active = False + last_alert.updated = timezone.now() + last_alert.save() + + new_alert = DataSourceAlert(alert_name=alert_name, data_source=source, generator_identifier=GENERATOR) + new_alert.alert_level = alert_level + new_alert.update_alert_details(alert_details) + new_alert.created = timezone.now() + new_alert.updated = timezone.now() + new_alert.active = True + + new_alert.save() + else: + last_alert.updated = timezone.now() + last_alert.update_alert_details(alert_details) + + last_alert.save() + elif last_alert is not None: + last_alert.updated = timezone.now() + last_alert.active = False + + last_alert.save() + else: + alert_name = 'No Battery Levels Logged' + alert_details['message'] = 'No battery levels have been logged for this device yet.' + + if last_alert is None or last_alert.alert_name != alert_name or last_alert.alert_level != alert_level: + if last_alert is not None: + last_alert.active = False + last_alert.updated = timezone.now() + last_alert.save() + + new_alert = DataSourceAlert(alert_name=alert_name, data_source=source, generator_identifier=GENERATOR) + new_alert.alert_level = alert_level + new_alert.update_alert_details(alert_details) + new_alert.created = timezone.now() + new_alert.updated = timezone.now() + new_alert.active = True + + new_alert.save() + else: + last_alert.updated = timezone.now() + last_alert.update_alert_details(alert_details) + + last_alert.save() diff --git a/migrations/0017_datasourcealert.py b/migrations/0017_datasourcealert.py new file mode 100644 index 0000000..207f563 --- /dev/null +++ b/migrations/0017_datasourcealert.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-30 14:21 +# pylint: skip-file + +from __future__ import unicode_literals + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('passive_data_kit', '0016_datapoint_secondary_identifier'), + ] + + operations = [ + migrations.CreateModel( + name='DataSourceAlert', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('alert_name', models.CharField(max_length=1024)), + ('alert_details', django.contrib.postgres.fields.jsonb.JSONField()), + ('generator_identifier', models.CharField(blank=True, max_length=1024, null=True)), + ('created', models.DateTimeField()), + ('active', models.BooleanField(default=True)), + ('data_source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='alerts', to='passive_data_kit.DataSource')), + ], + ), + ] diff --git a/migrations/0018_datasourcealert_updated.py b/migrations/0018_datasourcealert_updated.py new file mode 100644 index 0000000..c075ea6 --- /dev/null +++ b/migrations/0018_datasourcealert_updated.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-30 14:38 +# pylint: skip-file + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('passive_data_kit', '0017_datasourcealert'), + ] + + operations = [ + migrations.AddField( + model_name='datasourcealert', + name='updated', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/migrations/0019_datasourcealert_alert_level.py b/migrations/0019_datasourcealert_alert_level.py new file mode 100644 index 0000000..9d28778 --- /dev/null +++ b/migrations/0019_datasourcealert_alert_level.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-30 14:56 +# pylint: skip-file + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('passive_data_kit', '0018_datasourcealert_updated'), + ] + + operations = [ + migrations.AddField( + model_name='datasourcealert', + name='alert_level', + field=models.CharField(choices=[('info', 'Informative'), ('warning', 'Warning'), ('critical', 'Critical')], default='info', max_length=64), + ), + ] diff --git a/models.py b/models.py index 23418ea..e8e76ce 100644 --- a/models.py +++ b/models.py @@ -161,6 +161,40 @@ def generator_statistics(self): return generators +ALERT_LEVEL_CHOICES = ( + ('info', 'Informative'), + ('warning', 'Warning'), + ('critical', 'Critical'), +) + +class DataSourceAlert(models.Model): + alert_name = models.CharField(max_length=1024) + alert_level = models.CharField(max_length=64, choices=ALERT_LEVEL_CHOICES, default='info') + + if install_supports_jsonfield(): + alert_details = JSONField() + else: + alert_details = models.TextField(max_length=(32 * 1024 * 1024 * 1024)) + + data_source = models.ForeignKey(DataSource, related_name='alerts') + generator_identifier = models.CharField(max_length=1024, null=True, blank=True) + + created = models.DateTimeField() + updated = models.DateTimeField(null=True, blank=True) + + active = models.BooleanField(default=True) + + def fetch_alert_details(self): + if install_supports_jsonfield(): + return self.alert_details + + return json.loads(self.alert_details) + + def update_alert_details(self, details): + if install_supports_jsonfield(): + self.alert_details = details + else: + self.alert_details = json.dumps(details, indent=2) class DataPointVisualizations(models.Model): source = models.CharField(max_length=1024, db_index=True) diff --git a/templates/pdk_base.html b/templates/pdk_base.html index 3124266..7fd35de 100644 --- a/templates/pdk_base.html +++ b/templates/pdk_base.html @@ -8,7 +8,7 @@ - Dashboard Template for Bootstrap + Passive Data Kit diff --git a/templates/pdk_home.html b/templates/pdk_home.html index 793c44d..8b6a6b6 100644 --- a/templates/pdk_home.html +++ b/templates/pdk_home.html @@ -4,7 +4,9 @@ {% block sidebar %}