summaryrefslogtreecommitdiffstats
path: root/tools/pharos-dashboard/src/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'tools/pharos-dashboard/src/dashboard')
-rw-r--r--tools/pharos-dashboard/src/dashboard/__init__.py10
-rw-r--r--tools/pharos-dashboard/src/dashboard/admin.py16
-rw-r--r--tools/pharos-dashboard/src/dashboard/apps.py15
-rw-r--r--tools/pharos-dashboard/src/dashboard/fixtures/dashboard.json164
-rw-r--r--tools/pharos-dashboard/src/dashboard/migrations/__init__.py10
-rw-r--r--tools/pharos-dashboard/src/dashboard/models.py81
-rw-r--r--tools/pharos-dashboard/src/dashboard/tasks.py23
-rw-r--r--tools/pharos-dashboard/src/dashboard/templatetags/__init__.py10
-rw-r--r--tools/pharos-dashboard/src/dashboard/templatetags/jenkins_filters.py38
-rw-r--r--tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py18
-rw-r--r--tools/pharos-dashboard/src/dashboard/urls.py40
-rw-r--r--tools/pharos-dashboard/src/dashboard/views.py143
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})