1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
# Copyright (C) 2010-2018 Linaro Limited
#
# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later
import base64
import sys
from django import forms
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db.models import Count, IntegerField, OuterRef, Q, Subquery, Value
from django.http import (
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
HttpResponseRedirect,
HttpResponseServerError,
JsonResponse,
)
from django.shortcuts import render
from django.template import loader
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import requires_csrf_token
from django.views.decorators.http import require_POST
from lava_scheduler_app.dbutils import device_summary, device_type_summary
from lava_scheduler_app.models import ExtendedUser, RemoteArtifactsAuth, TestJob, Worker
from lava_server.bread_crumbs import BreadCrumb, BreadCrumbTrail
from linaro_django_xmlrpc.models import AuthToken
class ExtendedUserIRCForm(forms.ModelForm):
class Meta:
model = ExtendedUser
fields = ("irc_server", "irc_handle", "user")
widgets = {"user": forms.HiddenInput}
class ExtendedUserTableLengthForm(forms.ModelForm):
class Meta:
model = ExtendedUser
fields = ("table_length", "user")
widgets = {"user": forms.HiddenInput}
def healthz(request):
try:
User.objects.first()
return JsonResponse({"health": "good"})
except Exception:
return JsonResponse(
{"health": "bad", "error": "database connection lost"}, status=500
)
def prometheus(request):
# Authenticate using Basic auth
if request.headers.get("Authorization"):
auth = request.headers["Authorization"]
if not auth.startswith("Basic "):
return HttpResponseBadRequest("Only Basic authentication is supported")
try:
(name, secret) = (
base64.standard_b64decode(auth[len("Basic ") :])
.decode("utf-8")
.split(":", 1)
)
except ValueError:
return HttpResponseBadRequest("Invalid basic authentication")
user = AuthToken.get_user_for_secret(name, secret)
if user is None:
return HttpResponseBadRequest("Unknown user")
request.user = user
data = ""
(device_stats, running_jobs_count) = device_summary()
data += f"""# TYPE devices_online counter
devices_online {device_stats['num_online']}
# TYPE devices_not_retired counter
devices_not_retired {device_stats['num_not_retired']}
# TYPE devices_running counter
devices_running {device_stats['active_devices']}
# TYPE jobs_running counter
jobs_running {running_jobs_count}
# TYPE devices_health_check_total counter
devices_health_check_total {device_stats['health_checks_total']}
# TYPE devices_health_check_complete counter
devices_health_check_complete {device_stats['health_checks_complete']}
"""
# Device-types
dts = device_type_summary(request.user).annotate(
queued_jobs=Subquery(
TestJob.objects.filter(
Q(state=TestJob.STATE_SUBMITTED),
Q(requested_device_type=OuterRef("device_type")),
)
.annotate(dummy_group_by=Value(1)) # Disable GROUP BY
.values("dummy_group_by")
.annotate(queued_jobs=Count("*"))
.values("queued_jobs"),
output_field=IntegerField(),
),
)
data += "# TYPE device_type counter"
for dt in dts:
data += f"""
device_type{{name="{dt['device_type']}",state="idle"}} {dt['idle']}
device_type{{name="{dt['device_type']}",state="busy"}} {dt['busy']}
device_type{{name="{dt['device_type']}",state="offline"}} {dt['offline']}
device_type{{name="{dt['device_type']}",state="maintenance"}} {dt['maintenance']}
device_type{{name="{dt['device_type']}",state="queue"}} {dt['queued_jobs'] or 0}"""
worker_stats = Worker.objects.exclude(health=Worker.HEALTH_RETIRED).aggregate(
num_not_retired=Count("pk"),
num_online=Count("pk", filter=Q(state=Worker.STATE_ONLINE)),
num_offline=Count("pk", filter=Q(state=Worker.STATE_OFFLINE)),
num_maintenance=Count("pk", filter=Q(health=Worker.HEALTH_MAINTENANCE)),
num_active=Count("pk", filter=Q(health=Worker.HEALTH_ACTIVE)),
)
data += f"""
# TYPE workers_not_retired counter
workers_not_retired {worker_stats['num_not_retired']}
# TYPE workers_online counter
workers_online {worker_stats['num_online']}
# TYPE workers_offline counter
workers_offline {worker_stats['num_offline']}
# TYPE workers_maintenance counter
workers_maintenance {worker_stats['num_maintenance']}
# TYPE workers_active counter
workers_active {worker_stats['num_active']}
"""
return HttpResponse(data, content_type="text/plain; version=0.0.4")
@BreadCrumb(_("LAVA"))
def index(request):
# Load and render the template
return render(
request, "index.html", {"bread_crumb_trail": BreadCrumbTrail.leading_to(index)}
)
@BreadCrumb(_("About you ({you})"), parent=index)
@login_required
def me(request):
ExtendedUser.objects.get_or_create(user=request.user)
data = {
"irc_form": ExtendedUserIRCForm(instance=request.user.extendeduser),
"table_length_form": ExtendedUserTableLengthForm(
instance=request.user.extendeduser
),
"bread_crumb_trail": BreadCrumbTrail.leading_to(
me, you=request.user.get_full_name() or request.user.username
),
}
return render(request, "me.html", data)
@login_required
@require_POST
def update_irc_settings(request):
extended_user = request.user.extendeduser
form = ExtendedUserIRCForm(request.POST, instance=extended_user)
if form.is_valid():
extended_user = form.save()
return HttpResponseRedirect(reverse("lava.me"))
@login_required
@requires_csrf_token
def update_remote_auth(request):
if request.method == "POST":
token_id = request.POST.get("id", None)
token_name = request.POST.get("name", None)
token_hash = request.POST.get("token", None)
if not token_id:
RemoteArtifactsAuth.objects.create(
name=token_name, token=token_hash, user=request.user
)
else:
token = RemoteArtifactsAuth.objects.get(pk=token_id)
if token.user != request.user:
raise PermissionDenied()
token.name = token_name
token.token = token_hash
token.save()
return HttpResponseRedirect(reverse("lava.me"))
@login_required
def delete_remote_auth(request, pk):
token = RemoteArtifactsAuth.objects.get(pk=pk)
if token.user != request.user:
raise PermissionDenied()
token.delete()
return HttpResponseRedirect(reverse("lava.me"))
@login_required
@require_POST
def update_table_length_setting(request):
extended_user = request.user.extendeduser
form = ExtendedUserTableLengthForm(request.POST, instance=extended_user)
if form.is_valid():
extended_user = form.save()
return HttpResponseRedirect(reverse("lava.me"))
@requires_csrf_token
def server_error(request, template_name="500.html"):
exc_type, value, _ = sys.exc_info()
context_dict = {
"user": request.user,
"request": request,
"exception_type": exc_type,
"exception_value": value,
}
template = loader.get_template(template_name)
return HttpResponseServerError(template.render(context_dict, request))
@requires_csrf_token
def permission_error(request, exception, template_name="403.html"):
template = loader.get_template(template_name)
return HttpResponseForbidden(template.render({}, request))
|