alarms: integrate ooni alarms

This commit is contained in:
Iain Learmonth 2022-04-20 15:34:11 +01:00
parent 21a5d41e8c
commit 45a6d27c8b
4 changed files with 88 additions and 2 deletions

View file

@ -172,7 +172,7 @@ class Alarm(db.Model):
bridge = db.relationship("Bridge", back_populates="alarms") bridge = db.relationship("Bridge", back_populates="alarms")
def update_state(self, state: AlarmState, text: str): def update_state(self, state: AlarmState, text: str):
if self.state != state: if self.alarm_state != state:
self.state_changed = datetime.utcnow() self.state_changed = datetime.utcnow()
self.alarm_state = state self.alarm_state = state
self.text = text self.text = text

View file

@ -3,7 +3,7 @@
{% block content %} {% block content %}
<h1 class="h2 mt-3">Alarms</h1> <h1 class="h2 mt-3">Alarms</h1>
<h2 class="h3">Proxies</h2>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm"> <table class="table table-sm">
<thead> <thead>
@ -11,6 +11,8 @@
<th scope="col">Resource</th> <th scope="col">Resource</th>
<th scope="col">Type</th> <th scope="col">Type</th>
<th scope="col">State</th> <th scope="col">State</th>
<th scope="col">Message</th>
<th scope="col">Last Update</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -18,11 +20,15 @@
<tr class="bg-{% if alarm.alarm_state.name == "OK" %}success{% elif alarm.alarm_state.name == "UNKNOWN" %}dark{% else %}danger{% endif %} text-light"> <tr class="bg-{% if alarm.alarm_state.name == "OK" %}success{% elif alarm.alarm_state.name == "UNKNOWN" %}dark{% else %}danger{% endif %} text-light">
{% if alarm.target == "proxy" %} {% if alarm.target == "proxy" %}
<td>Proxy: {{ alarm.proxy.url }} ({{ alarm.proxy.origin.domain_name }})</td> <td>Proxy: {{ alarm.proxy.url }} ({{ alarm.proxy.origin.domain_name }})</td>
{% elif alarm.target == "origin" %}
<td>Origin: {{ alarm.origin.domain_name }}</td>
{% elif alarm.target == "service/cloudfront" %} {% elif alarm.target == "service/cloudfront" %}
<td>AWS CloudFront</td> <td>AWS CloudFront</td>
{% endif %} {% endif %}
<td>{{ alarm.alarm_type }}</td> <td>{{ alarm.alarm_type }}</td>
<td>{{ alarm.alarm_state.name }}</td> <td>{{ alarm.alarm_state.name }}</td>
<td>{{ alarm.text }}</td>
<td>{{ alarm.last_updated.strftime("%a, %d %b %Y %H:%M:%S") }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -0,0 +1,79 @@
from collections import defaultdict
from datetime import datetime
from datetime import timedelta
from typing import Dict
import requests
from app import app
from app.extensions import db
from app.models import Origin, AlarmState, Alarm
def check_origin(domain_name: str):
start_date = (datetime.utcnow() - timedelta(days=2)).strftime("%Y-%m-%dT%H%%3A%M")
end_date = datetime.utcnow().strftime("%Y-%m-%dT%H%%3A%M")
api_url = f"https://api.ooni.io/api/v1/measurements?domain={domain_name}&since={start_date}&until={end_date}"
result = defaultdict(lambda: {"anomaly": 0, "confirmed": 0, "failure": 0, "ok": 0})
return _check_origin(api_url, result)
def _check_origin(api_url: str, result: Dict):
print(f"Processing {api_url}")
req = requests.get(api_url).json()
if not req['results']:
return result
for r in req['results']:
not_ok = False
for s in ["anomaly", "confirmed", "failure"]:
if s in r and r[s]:
result[r["probe_cc"]][s] += 1
not_ok = True
break
if not not_ok:
result[r["probe_cc"]]["ok"] += 1
if req['metadata']['next_url']:
return _check_origin(req['metadata']['next_url'], result)
return result
def threshold_origin(domain_name):
ooni = check_origin(domain_name)
for country in ooni:
total = sum([
ooni[country]["anomaly"],
ooni[country]["confirmed"],
ooni[country]["failure"],
ooni[country]["ok"]
])
total_blocks = sum([
ooni[country]["anomaly"],
ooni[country]["confirmed"]
])
block_perc = round((total_blocks / total * 100), 1)
ooni[country]["block_perc"] = block_perc
ooni[country]["state"] = AlarmState.CRITICAL if block_perc > 20 else AlarmState.OK
ooni[country]["message"] = f"Blocked in {block_perc}% of measurements"
return ooni
def set_ooni_alarm(origin_id: int, country: str, state: AlarmState, text: str):
alarm = Alarm.query.filter(
Alarm.origin_id == origin_id,
Alarm.alarm_type == f"origin-block-ooni-{country}"
).first()
if alarm is None:
alarm = Alarm()
alarm.origin_id = origin_id
alarm.alarm_type = f"origin-block-ooni-{country}"
alarm.target = "origin"
db.session.add(alarm)
alarm.update_state(state, text)
with app.app_context():
origins = Origin.query.filter(Origin.destroyed == None).all()
for origin in origins:
ooni = threshold_origin(origin.domain_name)
for country in ooni:
set_ooni_alarm(origin.id, country.lower(), ooni[country]["state"], ooni[country]["message"])

View file

@ -14,6 +14,7 @@ def set_http_alarm(proxy_id: int, state: AlarmState, text: str):
alarm = Alarm() alarm = Alarm()
alarm.proxy_id = proxy_id alarm.proxy_id = proxy_id
alarm.alarm_type = "http-status" alarm.alarm_type = "http-status"
alarm.target = "proxy"
db.session.add(alarm) db.session.add(alarm)
alarm.update_state(state, text) alarm.update_state(state, text)