From d8e79661c69671dd424dca5cc3f7f2f855b0afed Mon Sep 17 00:00:00 2001 From: David Reyna Date: Fri, 28 Jul 2017 17:14:13 -0700 Subject: toaster: enable remote HTTP API for status aggregation Add support for Toaster aggregators with a set of api links that return JSON data for (a) builds in progress, (b) builds completed, (c) specific build data, and (d) an is-alive health ping link. [YOCTO #11794] Signed-off-by: David Reyna Signed-off-by: Richard Purdie --- lib/toaster/toastergui/templates/health.html | 6 ++ lib/toaster/toastergui/urls.py | 7 ++- lib/toaster/toastergui/views.py | 86 +++++++++++++++++++++++++++- lib/toaster/toastermain/urls.py | 5 +- 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 lib/toaster/toastergui/templates/health.html diff --git a/lib/toaster/toastergui/templates/health.html b/lib/toaster/toastergui/templates/health.html new file mode 100644 index 000000000..f17fdbc43 --- /dev/null +++ b/lib/toaster/toastergui/templates/health.html @@ -0,0 +1,6 @@ + + + Toaster Health + Ok + + diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py index 6aebc3f83..3ad5566b1 100644 --- a/lib/toaster/toastergui/urls.py +++ b/lib/toaster/toastergui/urls.py @@ -244,6 +244,11 @@ urlpatterns = [ url(r'^mostrecentbuilds$', widgets.MostRecentBuildsView.as_view(), name='most_recent_builds'), - # default redirection + # JSON data for aggregators + url(r'^api/builds$', views.json_builds, name='json_builds'), + url(r'^api/building$', views.json_building, name='json_building'), + url(r'^api/build/(?P\d+)$', views.json_build, name='json_build'), + + # default redirection url(r'^$', RedirectView.as_view(url='landing', permanent=True)), ] diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py index 5720b9d5e..334bb4a2e 100755 --- a/lib/toaster/toastergui/views.py +++ b/lib/toaster/toastergui/views.py @@ -35,7 +35,7 @@ from orm.models import BitbakeVersion, CustomImageRecipe from django.core.urlresolvers import reverse, resolve from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from django.http import HttpResponseNotFound +from django.http import HttpResponseNotFound, JsonResponse from django.utils import timezone from datetime import timedelta, datetime from toastergui.templatetags.projecttags import json as jsonfilter @@ -1256,6 +1256,89 @@ def managedcontextprocessor(request): } return ret +# REST-based API calls to return build/building status to external Toaster +# managers and aggregators via JSON + +def _json_build_status(build_id,extend): + build_stat = None + try: + build = Build.objects.get( pk = build_id ) + build_stat = {} + build_stat['id'] = build.id + build_stat['name'] = build.build_name + build_stat['machine'] = build.machine + build_stat['distro'] = build.distro + build_stat['start'] = build.started_on + # look up target name + target= Target.objects.get( build = build ) + if target: + if target.task: + build_stat['target'] = '%s:%s' % (target.target,target.task) + else: + build_stat['target'] = '%s' % (target.target) + else: + build_stat['target'] = '' + # look up project name + project = Project.objects.get( build = build ) + if project: + build_stat['project'] = project.name + else: + build_stat['project'] = '' + if Build.IN_PROGRESS == build.outcome: + now = timezone.now() + timediff = now - build.started_on + build_stat['seconds']='%.3f' % timediff.total_seconds() + build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone) + build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse) + tf = Task.objects.filter(build = build) + tfc = tf.count() + if tfc > 0: + tfd = tf.exclude(order__isnull=True).count() + else: + tfd = 0 + build_stat['task']='%d:%d' % (tfd,tfc) + else: + build_stat['outcome'] = build.get_outcome_text() + timediff = build.completed_on - build.started_on + build_stat['seconds']='%.3f' % timediff.total_seconds() + build_stat['stop'] = build.completed_on + messages = LogMessage.objects.all().filter(build = build) + errors = len(messages.filter(level=LogMessage.ERROR) | + messages.filter(level=LogMessage.EXCEPTION) | + messages.filter(level=LogMessage.CRITICAL)) + build_stat['errors'] = errors + warnings = len(messages.filter(level=LogMessage.WARNING)) + build_stat['warnings'] = warnings + if extend: + build_stat['cooker_log'] = build.cooker_log_path + except Exception as e: + build_state = str(e) + return build_stat + +def json_builds(request): + build_table = [] + builds = [] + try: + builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on") + for build in builds: + build_table.append(_json_build_status(build.id,False)) + except Exception as e: + build_table = str(e) + return JsonResponse({'builds' : build_table, 'count' : len(builds)}) + +def json_building(request): + build_table = [] + builds = [] + try: + builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on") + for build in builds: + build_table.append(_json_build_status(build.id,False)) + except Exception as e: + build_table = str(e) + return JsonResponse({'building' : build_table, 'count' : len(builds)}) + +def json_build(request,build_id): + return JsonResponse({'build' : _json_build_status(build_id,True)}) import toastermain.settings @@ -1694,3 +1777,4 @@ if True: return render(request, "unavailable_artifact.html") except (ObjectDoesNotExist, IOError): return render(request, "unavailable_artifact.html") + diff --git a/lib/toaster/toastermain/urls.py b/lib/toaster/toastermain/urls.py index bb325596b..6750bdf3a 100644 --- a/lib/toaster/toastermain/urls.py +++ b/lib/toaster/toastermain/urls.py @@ -20,9 +20,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.conf.urls import patterns, include, url -from django.views.generic import RedirectView +from django.views.generic import RedirectView, TemplateView from django.views.decorators.cache import never_cache - import bldcollector.views import logging @@ -46,6 +45,8 @@ urlpatterns = [ # in the future. url(r'^orm/eventfile$', bldcollector.views.eventfile), + url(r'^health$', TemplateView.as_view(template_name="health.html"), name='Toaster Health'), + # if no application is selected, we have the magic toastergui app here url(r'^$', never_cache(RedirectView.as_view(url='/toastergui/', permanent=True))), ] -- cgit 1.2.3-korg