CSCfi/pebbles

View on GitHub
pebbles/views/alerts.py

Summary

Maintainability
A
3 hrs
Test Coverage
import datetime
import time

import flask_restful as restful
from flask import Blueprint as FlaskBlueprint
from flask import abort, request
from flask_restful import marshal_with, fields, reqparse

from pebbles.models import Alert
from pebbles.models import db
from pebbles.utils import requires_admin
from pebbles.views.commons import auth

alerts = FlaskBlueprint('alerts', __name__)

alert_fields = {
    'id': fields.String,
    'target': fields.String,
    'source': fields.String,
    'status': fields.String,
    'first_seen_ts': fields.Integer,
    'last_seen_ts': fields.Integer,
    'data': fields.Raw,
}

# Limit in seconds when alert status is considered expired
EXPIRY_AGE_LIMIT = 60 * 3


class AlertList(restful.Resource):
    get_parser = reqparse.RequestParser()
    get_parser.add_argument('include_archived', type=str, default=None, location='args')
    get_parser.add_argument('since_ts', type=int, default=0, location='args')

    @auth.login_required
    @requires_admin
    @marshal_with(alert_fields)
    def get(self):
        args = self.get_parser.parse_args()

        q = Alert.query
        if not args.get('include_archived'):
            q = q.filter(Alert.status != 'archived')

        if args.get('since_ts'):
            q = q.filter(Alert._last_seen_ts > datetime.datetime.fromtimestamp(args.get('since_ts')))

        alerts = q.order_by(Alert._first_seen_ts).all()

        # if an alert with status 'ok' is too old, set it expired
        for alert in alerts:
            if alert.status == 'ok' and alert.last_seen_ts < time.time() - EXPIRY_AGE_LIMIT:
                alert.status = 'data expired'

        return alerts

    @auth.login_required
    @requires_admin
    @marshal_with(alert_fields)
    def post(self):
        res = []

        for entry in request.json:
            target = entry.get('target')
            source = entry.get('source')
            status = entry.get('status')
            data = entry.get('data', dict())
            if not (target and source and status):
                return "target, source and status have to be defined", 422

            alert_id = Alert.generate_alert_id(target, source, data)
            alert = Alert.query.filter_by(id=alert_id).first()
            if not alert:
                alert = Alert(alert_id, target, source, status, data)
                db.session.add(alert)
            else:
                alert.status = status
                alert.last_seen_ts = time.time()
            res.append(alert)

        db.session.commit()

        return res


class AlertView(restful.Resource):
    @auth.login_required
    @requires_admin
    @marshal_with(alert_fields)
    def get(self, id):
        q = Alert.query.filter_by(id=id)
        alert = q.first()
        if not alert:
            abort(404)
        return alert


class AlertReset(restful.Resource):
    @auth.login_required
    @requires_admin
    @marshal_with(alert_fields)
    def post(self, target, source):
        firing_alerts = Alert.query.filter_by(target=target, source=source, status='firing').all()
        for alert in firing_alerts:
            alert.status = 'archived'

        status = 'ok'
        data = dict()

        alert_id = Alert.generate_alert_id(target, source, data)
        alert = Alert.query.filter_by(id=alert_id).first()
        if not alert:
            alert = Alert(alert_id, target, source, status, data)
            db.session.add(alert)
        else:
            alert.last_seen_ts = time.time()

        db.session.commit()
        return firing_alerts


class SystemStatus(restful.Resource):
    @staticmethod
    def get():
        active_alerts = Alert.query.filter(Alert.status != 'archived').all()

        for alert in active_alerts:
            if alert.status == 'firing' or alert.last_seen_ts < time.time() - EXPIRY_AGE_LIMIT:
                return 'warning'

        return 'ok'