diff options
Diffstat (limited to 'tools/pharos-dashboard/src/dashboard')
12 files changed, 568 insertions, 0 deletions
diff --git a/tools/pharos-dashboard/src/dashboard/__init__.py b/tools/pharos-dashboard/src/dashboard/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/admin.py b/tools/pharos-dashboard/src/dashboard/admin.py new file mode 100644 index 00000000..a1463a7a --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/admin.py @@ -0,0 +1,16 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib import admin + +from dashboard.models import * + +admin.site.register(Resource) +admin.site.register(Server) diff --git a/tools/pharos-dashboard/src/dashboard/apps.py b/tools/pharos-dashboard/src/dashboard/apps.py new file mode 100644 index 00000000..e0c4f442 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + name = 'dashboard' diff --git a/tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json b/tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json new file mode 100644 index 00000000..f0ac3b2f --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json @@ -0,0 +1,164 @@ +[ +{ + "model": "dashboard.resource", + "pk": 1, + "fields": { + "name": "Linux Foundation POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab" + } +}, +{ + "model": "dashboard.resource", + "pk": 2, + "fields": { + "name": "Linux Foundation POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab" + } +}, +{ + "model": "dashboard.resource", + "pk": 3, + "fields": { + "name": "Ericsson POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process" + } +}, +{ + "model": "dashboard.resource", + "pk": 4, + "fields": { + "name": "Intel POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2" + } +}, +{ + "model": "dashboard.resource", + "pk": 5, + "fields": { + "name": "Intel POD 5", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5" + } +}, +{ + "model": "dashboard.resource", + "pk": 6, + "fields": { + "name": "Intel POD 6", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6" + } +}, +{ + "model": "dashboard.resource", + "pk": 7, + "fields": { + "name": "Intel POD 8", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8" + } +}, +{ + "model": "dashboard.resource", + "pk": 8, + "fields": { + "name": "Huawei POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 9, + "fields": { + "name": "Intel POD 3", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3" + } +}, +{ + "model": "dashboard.resource", + "pk": 10, + "fields": { + "name": "Dell POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 11, + "fields": { + "name": "Dell POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 12, + "fields": { + "name": "Orange POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2" + } +}, +{ + "model": "dashboard.resource", + "pk": 13, + "fields": { + "name": "Arm POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab" + } +}, +{ + "model": "dashboard.resource", + "pk": 14, + "fields": { + "name": "Ericsson POD 1", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process" + } +}, +{ + "model": "dashboard.resource", + "pk": 15, + "fields": { + "name": "Huawei POD 2", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 16, + "fields": { + "name": "Huawei POD 3", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 17, + "fields": { + "name": "Huawei POD 4", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting" + } +}, +{ + "model": "dashboard.resource", + "pk": 18, + "fields": { + "name": "Intel POD 9", + "description": "Some description", + "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9" + } +} +] diff --git a/tools/pharos-dashboard/src/dashboard/migrations/__init__.py b/tools/pharos-dashboard/src/dashboard/migrations/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/models.py b/tools/pharos-dashboard/src/dashboard/models.py new file mode 100644 index 00000000..050834ea --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/models.py @@ -0,0 +1,81 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.utils import timezone +from django.contrib.auth.models import User +from django.db import models + +from jenkins.models import JenkinsSlave + + +class Resource(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=100, unique=True) + description = models.CharField(max_length=300, blank=True, null=True) + url = models.CharField(max_length=100, blank=True, null=True) + owner = models.ForeignKey(User, related_name='user_lab_owner', null=True) + vpn_users = models.ManyToManyField(User, related_name='user_vpn_users') + slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True) + + def get_booking_utilization(self, weeks): + """ + Return a dictionary containing the count of booked and free seconds for a resource in the + range [now,now + weeks] if weeks is positive, + or [now-weeks, now] if weeks is negative + """ + + length = timedelta(weeks=abs(weeks)) + now = timezone.now() + + start = now + end = now + length + if weeks < 0: + start = now - length + end = now + + bookings = self.booking_set.filter(start__lt=start + length, end__gt=start) + + booked_seconds = 0 + for booking in bookings: + booking_start = booking.start + booking_end = booking.end + if booking_start < start: + booking_start = start + if booking_end > end: + booking_end = start + length + total = booking_end - booking_start + booked_seconds += total.total_seconds() + + return {'booked_seconds': booked_seconds, + 'available_seconds': length.total_seconds() - booked_seconds} + + class Meta: + db_table = 'resource' + + def __str__(self): + return self.name + + +class Server(models.Model): + id = models.AutoField(primary_key=True) + resource = models.ForeignKey(Resource, on_delete=models.CASCADE) + name = models.CharField(max_length=100, blank=True) + model = models.CharField(max_length=100, blank=True) + cpu = models.CharField(max_length=100, blank=True) + ram = models.CharField(max_length=100, blank=True) + storage = models.CharField(max_length=100, blank=True) + + class Meta: + db_table = 'server' + + def __str__(self): + return self.name diff --git a/tools/pharos-dashboard/src/dashboard/tasks.py b/tools/pharos-dashboard/src/dashboard/tasks.py new file mode 100644 index 00000000..4c09bf90 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tasks.py @@ -0,0 +1,23 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from celery import shared_task +from datetime import timedelta +from django.utils import timezone + +from jenkins.models import JenkinsStatistic +from notification.models import BookingNotification + + +@shared_task +def database_cleanup(): + now = timezone.now() + JenkinsStatistic.objects.filter(timestamp__lt=now - timedelta(weeks=4)).delete() + BookingNotification.objects.filter(submit_time__lt=now - timedelta(weeks=4)).delete()
\ No newline at end of file diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/__init__.py b/tools/pharos-dashboard/src/dashboard/templatetags/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/templatetags/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py b/tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py new file mode 100644 index 00000000..e7e14257 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.template.defaultfilters import register + + +@register.filter +def jenkins_job_color(job_result): + if job_result == 'SUCCESS': + return '#5cb85c' + if job_result == 'FAILURE': + return '#d9534f' + if job_result == 'UNSTABLE': + return '#EDD62B' + return '#646F73' # job is still building + + +@register.filter +def jenkins_status_color(slave_status): + if slave_status == 'offline': + return '#d9534f' + if slave_status == 'online': + return '#5cb85c' + if slave_status == 'online / idle': + return '#5bc0de' + + +@register.filter +def jenkins_job_blink(job_result): + if job_result == '': # job is still building + return 'class=blink_me' diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py new file mode 100644 index 00000000..70208436 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py @@ -0,0 +1,18 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.template.defaultfilters import register + +from django.conf import settings + + +@register.filter +def jira_issue_url(issue): + return settings.JIRA_URL + '/browse/' + str(issue) diff --git a/tools/pharos-dashboard/src/dashboard/urls.py b/tools/pharos-dashboard/src/dashboard/urls.py new file mode 100644 index 00000000..f04f5ca9 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/urls.py @@ -0,0 +1,40 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_dashboard URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from dashboard.views import * + +urlpatterns = [ + url(r'^ci_pods/$', CIPodsView.as_view(), name='ci_pods'), + url(r'^dev_pods/$', DevelopmentPodsView.as_view(), name='dev_pods'), + url(r'^jenkins_slaves/$', JenkinsSlavesView.as_view(), name='jenkins_slaves'), + url(r'^resource/all/$', LabOwnerView.as_view(), name='resources'), + url(r'^resource/(?P<resource_id>[0-9]+)/$', ResourceView.as_view(), name='resource'), + url(r'^resource/(?P<resource_id>[0-9]+)/booking_utilization/(?P<weeks>-?\d+)/$', + BookingUtilizationJSON.as_view(), name='booking_utilization'), + url(r'^resource/(?P<resource_id>[0-9]+)/jenkins_utilization/(?P<weeks>-?\d+)/$', + JenkinsUtilizationJSON.as_view(), name='jenkins_utilization'), + url(r'^$', DevelopmentPodsView.as_view(), name="index"), +] diff --git a/tools/pharos-dashboard/src/dashboard/views.py b/tools/pharos-dashboard/src/dashboard/views.py new file mode 100644 index 00000000..022a4af0 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/views.py @@ -0,0 +1,143 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from datetime import timedelta + +from django.http import JsonResponse +from django.shortcuts import get_object_or_404 +from django.utils import timezone +from django.views import View +from django.views.generic import TemplateView + +from booking.models import Booking +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class JenkinsSlavesView(TemplateView): + template_name = "dashboard/jenkins_slaves.html" + + def get_context_data(self, **kwargs): + slaves = JenkinsSlave.objects.all() + context = super(JenkinsSlavesView, self).get_context_data(**kwargs) + context.update({'title': "Jenkins Slaves", 'slaves': slaves}) + return context + + +class CIPodsView(TemplateView): + template_name = "dashboard/ci_pods.html" + + def get_context_data(self, **kwargs): + ci_pods = Resource.objects.filter(slave__ci_slave=True) + context = super(CIPodsView, self).get_context_data(**kwargs) + context.update({'title': "CI Pods", 'ci_pods': ci_pods}) + return context + + +class DevelopmentPodsView(TemplateView): + template_name = "dashboard/dev_pods.html" + + def get_context_data(self, **kwargs): + resources = Resource.objects.filter(slave__dev_pod=True) + + bookings = Booking.objects.filter(start__lte=timezone.now()) + bookings = bookings.filter(end__gt=timezone.now()) + + dev_pods = [] + for resource in resources: + booking_utilization = resource.get_booking_utilization(weeks=4) + total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds'] + try: + utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) / + total * 100) + except (ValueError, ZeroDivisionError): + return "" + + dev_pod = (resource, None, utilization_percentage) + for booking in bookings: + if booking.resource == resource: + dev_pod = (resource, booking, utilization_percentage) + dev_pods.append(dev_pod) + + context = super(DevelopmentPodsView, self).get_context_data(**kwargs) + context.update({'title': "Development Pods", 'dev_pods': dev_pods}) + return context + + +class ResourceView(TemplateView): + template_name = "dashboard/resource.html" + + def get_context_data(self, **kwargs): + resource = get_object_or_404(Resource, id=self.kwargs['resource_id']) + utilization = resource.slave.get_utilization(timedelta(days=7)) + bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now()) + context = super(ResourceView, self).get_context_data(**kwargs) + context.update({'title': str(resource), 'resource': resource, 'utilization': utilization, + 'bookings': bookings}) + return context + + +class LabOwnerView(TemplateView): + template_name = "dashboard/resource_all.html" + + def get_context_data(self, **kwargs): + resources = Resource.objects.filter(slave__dev_pod=True) + pods = [] + for resource in resources: + utilization = resource.slave.get_utilization(timedelta(days=7)) + bookings = Booking.objects.filter(resource=resource, end__gt=timezone.now()) + pods.append((resource, utilization, bookings)) + context = super(LabOwnerView, self).get_context_data(**kwargs) + context.update({'title': "Overview", 'pods': pods}) + return context + + +class BookingUtilizationJSON(View): + def get(self, request, *args, **kwargs): + resource = get_object_or_404(Resource, id=kwargs['resource_id']) + utilization = resource.get_booking_utilization(int(kwargs['weeks'])) + utilization = [ + { + 'label': 'Booked', + 'data': utilization['booked_seconds'], + 'color': '#d9534f' + }, + { + 'label': 'Available', + 'data': utilization['available_seconds'], + 'color': '#5cb85c' + }, + ] + return JsonResponse({'data': utilization}) + + +class JenkinsUtilizationJSON(View): + def get(self, request, *args, **kwargs): + resource = get_object_or_404(Resource, id=kwargs['resource_id']) + weeks = int(kwargs['weeks']) + utilization = resource.slave.get_utilization(timedelta(weeks=weeks)) + utilization = [ + { + 'label': 'Offline', + 'data': utilization['offline'], + 'color': '#d9534f' + }, + { + 'label': 'Online', + 'data': utilization['online'], + 'color': '#5cb85c' + }, + { + 'label': 'Idle', + 'data': utilization['idle'], + 'color': '#5bc0de' + }, + ] + return JsonResponse({'data': utilization}) |