#########################################################################
#
# Copyright (C) 2017 OSGeo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
import json
import pytz
from datetime import datetime, timedelta
from django.shortcuts import render
from django import forms
from django.contrib import auth
from django.conf import settings
from django.views.generic.base import View
from django.urls import reverse
from django.core.management import call_command
from django.views.decorators.csrf import csrf_exempt
from geonode.decorators import view_decorator, superuser_protected
from geonode.utils import json_response
from geonode.monitoring.collector import CollectorAPI
from geonode.monitoring.models import (
Service,
Host,
Metric,
ServiceTypeMetric,
MetricLabel,
MonitoredResource,
ExceptionEvent,
EventType,
NotificationCheck,
MetricNotificationCheck,
)
from geonode.monitoring.models import do_autoconfigure
from geonode.monitoring.utils import TypeChecks, dump, extend_datetime_input_formats
from geonode.monitoring.service_handlers import exposes
# Create your views here.
[docs]
class MetricsList(View):
[docs]
def get(self, *args, **kwargs):
_metrics = capi.get_metric_names()
out = []
for srv, mlist in _metrics:
out.append(
{"service": srv.name, "metrics": [{"name": m.name, "unit": m.unit, "type": m.type} for m in mlist]}
)
return json_response({"metrics": out})
[docs]
class ServicesList(View):
[docs]
def get_queryset(self):
return Service.objects.filter(active=True).select_related()
[docs]
def get(self, *args, **kwargs):
q = self.get_queryset()
out = []
for item in q:
out.append(
{
"name": item.name,
"host": item.host.name,
"id": item.id,
"type": item.service_type.name,
"check_interval": item.check_interval.total_seconds(),
"last_check": item.last_check,
}
)
return json_response({"services": out})
[docs]
class HostsList(View):
[docs]
def get_queryset(self):
return Host.objects.filter(active=True).select_related()
[docs]
def get(self, *args, **kwargs):
q = self.get_queryset()
out = []
for item in q:
out.append({"name": item.name, "ip": item.ip})
return json_response({"hosts": out})
[docs]
class MetricsFilters(CheckTypeForm):
[docs]
GROUP_BY_RESOURCE = "resource"
[docs]
GROUP_BY_RESOURCE_ON_LABEL = "resource_on_label"
[docs]
GROUP_BY_RESOURCE_ON_USER = "resource_on_user"
[docs]
GROUP_BY_COUNT_ON_RESOURCE = "count_on_resource"
[docs]
GROUP_BY_LABEL = "label"
[docs]
GROUP_BY_USER_ON_LABEL = "user_on_label"
[docs]
GROUP_BY_EVENT_TYPE = "event_type"
[docs]
GROUP_BY_EVENT_TYPE_ON_LABEL = "event_type_on_label"
[docs]
GROUP_BY_EVENT_TYPE_ON_USER = "event_type_on_user"
[docs]
GROUP_BY_CHOICES = (
(
GROUP_BY_RESOURCE,
"By resource",
),
(
GROUP_BY_RESOURCE_ON_LABEL,
"By resource on label",
),
(
GROUP_BY_RESOURCE_ON_USER,
"By resource on user",
),
(
GROUP_BY_COUNT_ON_RESOURCE,
"By resource with count",
),
(
GROUP_BY_LABEL,
"By label",
),
(
GROUP_BY_USER,
"By user",
),
(
GROUP_BY_USER_ON_LABEL,
"By user on label",
),
(
GROUP_BY_EVENT_TYPE,
"By event type",
),
(
GROUP_BY_EVENT_TYPE_ON_LABEL,
"By event type on label",
),
(
GROUP_BY_EVENT_TYPE_ON_USER,
"By event type on user",
),
)
[docs]
service = forms.CharField(required=False)
[docs]
label = forms.CharField(required=False)
[docs]
user = forms.CharField(required=False)
[docs]
resource = forms.CharField(required=False)
[docs]
resource_type = forms.ChoiceField(choices=MonitoredResource.TYPES, required=False)
[docs]
event_type = forms.CharField(required=False)
[docs]
service_type = forms.CharField(required=False)
[docs]
group_by = forms.ChoiceField(choices=GROUP_BY_CHOICES, required=False)
[docs]
def clean_resource(self):
return self._check_type("resource")
[docs]
def clean_service(self):
return self._check_type("service")
[docs]
def clean_label(self):
return self._check_type("label")
[docs]
def clean_user(self):
return self._check_type("user")
[docs]
def clean_event_type(self):
return self._check_type("event_type")
[docs]
def clean_service_type(self):
return self._check_type("service_type")
[docs]
def _check_services(self):
s = self.cleaned_data.get("service")
st = self.cleaned_data.get("service_type")
if st and s:
raise forms.ValidationError("Cannot use service and service type at the same time")
[docs]
def clean(self):
super().clean()
self._check_services()
[docs]
class FilteredView(View):
# form which validates request.GET for get_queryset()
# iterable of pairs (from model field, to key name) to map
# fields from model to elements of output data
# key name for output ({output_name: data})
[docs]
def get_filter_args(self, request):
self.errors = None
if not self.filter_form:
return {}
f = self.filter_form(data=request.GET)
if not f.is_valid():
self.errors = f.errors
return f.cleaned_data
[docs]
def get(self, request, *args, **kwargs):
qargs = self.get_filter_args(request)
if self.errors:
return json_response({"success": False, "status": "errors", "errors": self.errors}, status=400)
q = self.get_queryset(**qargs)
from_fields = [f[0] for f in self.fields_map]
to_fields = [f[1] for f in self.fields_map]
out = [dict(zip(to_fields, (getattr(item, f) for f in from_fields))) for item in q]
data = {self.output_name: out, "success": True, "errors": {}, "status": "ok"}
if self.output_name != "data":
data["data"] = {"key": self.output_name}
return json_response(data)
@view_decorator(superuser_protected, subclass=True)
[docs]
class ResourcesList(FilteredView):
[docs]
fields_map = (
(
"id",
"id",
),
(
"type",
"type",
),
(
"name",
"name",
),
)
[docs]
output_name = "resources"
[docs]
def get_queryset(
self, metric_name=None, resource_type=None, valid_from=None, valid_to=None, last=None, interval=None
):
q = MonitoredResource.objects.all().distinct()
qparams = {}
if resource_type:
qparams["type"] = resource_type
if metric_name:
sm = ServiceTypeMetric.objects.filter(metric__name=metric_name)
qparams["metric_values__service_metric__in"] = sm
if last:
_from = datetime.utcnow().replace(tzinfo=pytz.utc) - timedelta(seconds=last)
if interval is None:
interval = 60
if not isinstance(interval, timedelta):
interval = timedelta(seconds=interval)
valid_from = _from
if valid_from:
qparams["metric_values__valid_from__gte"] = valid_from
if valid_to:
qparams["metric_values__valid_to__lte"] = valid_to
if qparams:
q = q.filter(**qparams)
return q
@view_decorator(superuser_protected, subclass=True)
[docs]
class ResourceTypesList(FilteredView):
[docs]
output_name = "resource_types"
[docs]
def get(self, request, *args, **kwargs):
if self.filter_form:
f = self.filter_form(data=request.GET)
if not f.is_valid():
return json_response({"success": False, "status": "errors", "errors": f.errors}, status=400)
out = [{"name": mrt[0], "type_label": mrt[1]} for mrt in MonitoredResource.TYPES]
data = {self.output_name: out, "success": True, "errors": {}, "status": "ok"}
if self.output_name != "data":
data["data"] = {"key": self.output_name}
return json_response(data)
@view_decorator(superuser_protected, subclass=True)
[docs]
class LabelsList(FilteredView):
[docs]
fields_map = (
(
"id",
"id",
),
(
"name",
"name",
),
)
[docs]
def get_queryset(self, metric_name, valid_from, valid_to, interval=None, last=None):
q = MetricLabel.objects.all().distinct()
qparams = {}
if metric_name:
sm = ServiceTypeMetric.objects.filter(metric__name=metric_name)
qparams["metric_values__service_metric__in"] = sm
if last:
_from = datetime.utcnow().replace(tzinfo=pytz.utc) - timedelta(seconds=last)
if interval is None:
interval = 60
if not isinstance(interval, timedelta):
interval = timedelta(seconds=interval)
valid_from = _from
if valid_from:
qparams["metric_values__valid_from__gte"] = valid_from
if valid_to:
qparams["metric_values__valid_to__lte"] = valid_to
if qparams:
q = q.filter(**qparams)
return q
@view_decorator(superuser_protected, subclass=True)
[docs]
class EventTypeList(FilteredView):
[docs]
fields_map = (
(
"name",
"name",
),
(
"type_label",
"type_label",
),
)
[docs]
output_name = "event_types"
[docs]
def get_queryset(self, **kwargs):
if "ows_service" in kwargs and kwargs["ows_service"] is not None:
if kwargs["ows_service"]:
return EventType.objects.filter(name__icontains="OWS")
else:
return EventType.objects.exclude(name__icontains="OWS")
return EventType.objects.all()
[docs]
def get(self, request, *args, **kwargs):
qargs = self.get_filter_args(request)
if self.errors:
return json_response({"success": False, "status": "errors", "errors": self.errors}, status=400)
q = self.get_queryset(**qargs)
from_fields = [f[0] for f in self.fields_map]
to_fields = [f[1] for f in self.fields_map]
labels = dict(EventType.EVENT_TYPES)
out = [
dict(
zip(
to_fields,
(getattr(item, f) if f != "type_label" else labels[getattr(item, "name")] for f in from_fields),
)
)
for item in q
]
data = {self.output_name: out, "success": True, "errors": {}, "status": "ok"}
if self.output_name != "data":
data["data"] = {"key": self.output_name}
return json_response(data)
@view_decorator(superuser_protected, subclass=True)
[docs]
class MetricDataView(View):
[docs]
def get_filters(self, **kwargs):
out = {}
self.errors = None
f = MetricsFilters(data=self.request.GET)
if not f.is_valid():
self.errors = f.errors
else:
out.update(f.cleaned_data)
return out
[docs]
def get(self, request, *args, **kwargs):
filters = self.get_filters(**kwargs)
if self.errors:
return json_response({"status": "error", "success": False, "errors": self.errors}, status=400)
metric_name = kwargs["metric_name"]
last = filters.pop("last", None)
if last:
td = timedelta(seconds=last)
now = datetime.utcnow().replace(tzinfo=pytz.utc)
filters["valid_from"] = now - td
filters["valid_to"] = now
out = capi.get_metrics_for(metric_name, **filters)
return json_response({"data": out})
[docs]
class ExceptionsListView(FilteredView):
[docs]
fields_map = (
(
"id",
"id",
),
(
"created",
"created",
),
(
"url",
"url",
),
(
"service_data",
"service",
),
(
"error_type",
"error_type",
),
)
[docs]
output_name = "exceptions"
[docs]
def get_queryset(
self,
error_type=None,
valid_from=None,
valid_to=None,
interval=None,
last=None,
service_name=None,
service_type=None,
resource=None,
):
q = ExceptionEvent.objects.all().select_related()
if error_type:
q = q.filter(error_type=error_type)
if last:
_from = datetime.utcnow().replace(tzinfo=pytz.utc) - timedelta(seconds=last)
if interval is None:
interval = 60
if not isinstance(interval, timedelta):
interval = timedelta(seconds=interval)
valid_from = _from
if valid_from:
q = q.filter(created__gte=valid_from)
if valid_to:
q = q.filter(created__lte=valid_to)
if service_name:
q = q.filter(service__name=service_name)
if service_type:
q = q.filter(service__service_type__name=service_type)
if resource:
q = q.filter(request__resources__in=(resource,))
return q
[docs]
class ExceptionDataView(View):
[docs]
def get_object(self, exception_id):
try:
return ExceptionEvent.objects.get(id=exception_id)
except ExceptionEvent.DoesNotExist:
return
[docs]
def get(self, request, exception_id, *args, **kwargs):
e = self.get_object(exception_id)
if not e:
return json_response(errors={"exception_id": "Object not found"}, status=404)
data = e.expose()
return json_response(data)
[docs]
class BeaconView(View):
[docs]
def get(self, request, *args, **kwargs):
service = kwargs.get("exposed")
if not service:
data = [{"name": s, "url": reverse("monitoring:api_beacon_exposed", args=(s,))} for s in exposes.keys()]
return json_response({"exposed": data})
try:
ex = exposes[service]()
except KeyError:
return json_response(errors={"exposed": f"No service for {service}"}, status=404)
out = {"data": ex.expose(), "timestamp": datetime.utcnow().replace(tzinfo=pytz.utc)}
return json_response(out)
[docs]
def index(request):
if auth.get_user(request).is_superuser:
return render(request, "monitoring/index.html")
return render(request, "monitoring/non_superuser.html")
[docs]
class UserNotificationConfigView(View):
[docs]
def get_object(self):
pk = self.kwargs["pk"]
return NotificationCheck.objects.get(pk=pk)
[docs]
def get(self, request, *args, **kwargs):
out = {"success": False, "status": "error", "data": [], "errors": {}}
fields = (
"field_name",
"steps",
"current_value",
"steps_calculated",
"unit",
"is_enabled",
)
if auth.get_user(request).is_authenticated:
obj = self.get_object()
out["success"] = True
out["status"] = "ok"
form = obj.get_user_form()
fields = [dump(r, fields) for r in obj.definitions.all()]
out["data"] = {"form": form.as_table(), "fields": fields, "emails": obj.emails, "notification": dump(obj)}
status = 200
else:
out["errors"]["user"] = ["User is not authenticated"]
status = 401
return json_response(out, status=status)
[docs]
def post(self, request, *args, **kwargs):
out = {"success": False, "status": "error", "data": [], "errors": {}}
if auth.get_user(request).is_authenticated:
obj = self.get_object()
try:
is_json = True
data = json.loads(request.body)
except (
TypeError,
ValueError,
):
is_json = False
data = request.POST.copy()
try:
configs = obj.process_user_form(data, is_json=is_json)
out["success"] = True
out["status"] = "ok"
out["data"] = [dump(c) for c in configs]
status = 200
except forms.ValidationError as err:
out["errors"] = err.errors
status = 400
else:
out["errors"]["user"] = ["User is not authenticated"]
status = 401
return json_response(out, status=status)
if settings.MONITORING_DISABLE_CSRF:
post = csrf_exempt(post)
[docs]
class NotificationsList(FilteredView):
[docs]
fields_map = (
(
"id",
"id",
),
(
"url",
"url",
),
(
"name",
"name",
),
(
"active",
"active",
),
(
"severity",
"severity",
),
(
"description",
"description",
),
)
[docs]
def get_filter_args(self, *args, **kwargs):
self.errors = {}
if not auth.get_user(self.request).is_authenticated:
self.errors = {"user": ["User is not authenticated"]}
return {}
[docs]
def get_queryset(self, *args, **kwargs):
return NotificationCheck.objects.all()
[docs]
def create(self, request, *args, **kwargs):
f = NotificaitonCheckForm(data=request.POST)
if f.is_valid():
d = f.cleaned_data
return NotificationCheck.create(**d)
self.errors = f.errors
[docs]
def post(self, request, *args, **kwargs):
out = {"success": False, "status": "error", "data": [], "errors": {}}
d = self.create(request, *args, **kwargs)
if d is None:
out["errors"] = self.errors
status = 400
else:
out["data"] = dump(d)
out["success"] = True
out["status"] = "ok"
status = 200
return json_response(out, status=status)
[docs]
class StatusCheckView(View):
[docs]
fields = (
"name",
"severity",
"offending_value",
"threshold_value",
"spotted_at",
"valid_from",
"valid_to",
"check_url",
"check_id",
"description",
"message",
)
[docs]
def get(self, request, *args, **kwargs):
capi = CollectorAPI()
checks = capi.get_notifications()
data = {"status": "ok", "success": True, "data": {}}
d = data["data"]
d["problems"] = problems = []
d["health_level"] = "ok"
_levels = (
"fatal",
"error",
"warning",
)
levels = set()
for nc, ncdata in checks:
for ncd in ncdata:
levels.add(ncd.severity)
problems.append(dump(ncd, self.fields))
if levels:
for lyr in _levels:
if lyr in levels:
d["health_level"] = lyr
break
return json_response(data)
[docs]
class CollectMetricsView(View):
"""
- Run command "collect_metrics -n -t xml" via web
"""
[docs]
authkey = "OzhVMECJUn9vDu2oLv1HjGPKByuTBwF8"
[docs]
def get(self, request, *args, **kwargs):
authkey = kwargs.get("authkey")
if not authkey or authkey != self.authkey:
out = {"success": False, "status": "error", "errors": {"denied": ["Call is not permitted"]}}
return json_response(out, status=401)
else:
call_command("collect_metrics", "-n", "-t", "xml")
out = {"success": True, "status": "ok", "errors": {}}
return json_response(out)
[docs]
api_metrics = MetricsList.as_view()
[docs]
api_services = ServicesList.as_view()
[docs]
api_hosts = HostsList.as_view()
[docs]
api_labels = LabelsList.as_view()
[docs]
api_resources = ResourcesList.as_view()
[docs]
api_resource_types = ResourceTypesList.as_view()
[docs]
api_event_types = EventTypeList.as_view()
[docs]
api_metric_data = MetricDataView.as_view()
[docs]
api_metric_collect = CollectMetricsView.as_view()
[docs]
api_exceptions = ExceptionsListView.as_view()
[docs]
api_exception = ExceptionDataView.as_view()
[docs]
api_beacon = BeaconView.as_view()
[docs]
api_user_notification_config = UserNotificationConfigView.as_view()
[docs]
api_user_notifications = NotificationsList.as_view()
[docs]
api_status = StatusCheckView.as_view()