Implement periodic tasks 71/19071/1
authormaxbr <maxbr@mi.fu-berlin.de>
Fri, 19 Aug 2016 15:11:58 +0000 (17:11 +0200)
committermaxbr <maxbr@mi.fu-berlin.de>
Fri, 19 Aug 2016 15:11:58 +0000 (17:11 +0200)
JIRA: RELENG-12

The dashboard is now querying jenkins periodically and saving the
results in the database. This fixes delays that were caused by calling
the jenkins API.

Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
28 files changed:
tools/pharos-dashboard/account/middleware.py
tools/pharos-dashboard/account/migrations/0001_initial.py
tools/pharos-dashboard/booking/migrations/0001_initial.py
tools/pharos-dashboard/booking/tests/test_models.py
tools/pharos-dashboard/booking/tests/test_views.py
tools/pharos-dashboard/celerybeat-schedule [new file with mode: 0644]
tools/pharos-dashboard/dashboard/fixtures/dashboard.json
tools/pharos-dashboard/dashboard/migrations/0001_initial.py
tools/pharos-dashboard/dashboard/migrations/0002_auto_20160815_1511.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/0003_auto_20160815_1512.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/0004_resource_slave.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/0005_remove_resource_slavename.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/models.py
tools/pharos-dashboard/dashboard/templatetags/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/views.py
tools/pharos-dashboard/jenkins/adapter.py
tools/pharos-dashboard/jenkins/migrations/0001_initial.py [new file with mode: 0644]
tools/pharos-dashboard/jenkins/migrations/0002_auto_20160815_1226.py [new file with mode: 0644]
tools/pharos-dashboard/jenkins/migrations/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/jenkins/models.py [new file with mode: 0644]
tools/pharos-dashboard/jenkins/tasks.py [new file with mode: 0644]
tools/pharos-dashboard/pharos_dashboard/__init__.py
tools/pharos-dashboard/pharos_dashboard/celery.py [new file with mode: 0644]
tools/pharos-dashboard/pharos_dashboard/settings.py
tools/pharos-dashboard/templates/dashboard/ci_pods.html
tools/pharos-dashboard/templates/dashboard/dev_pods.html
tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html

index f5170ba..6f7cac7 100644 (file)
@@ -1,7 +1,8 @@
-from django.core.exceptions import ObjectDoesNotExist
 from django.utils import timezone
 from django.utils.deprecation import MiddlewareMixin
 
+from account.models import UserProfile
+
 
 class TimezoneMiddleware(MiddlewareMixin):
     """
@@ -10,6 +11,12 @@ class TimezoneMiddleware(MiddlewareMixin):
     """
     def process_request(self, request):
         if request.user.is_authenticated:
-            timezone.activate(request.user.userprofile.timezone)
+            try:
+                tz = request.user.userprofile.timezone
+                timezone.activate(tz)
+            except UserProfile.DoesNotExist:
+                UserProfile.objects.create(user=request.user)
+                tz = request.user.userprofile.timezone
+                timezone.activate(tz)
         else:
             timezone.deactivate()
index 752d517..4ff9510 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2016-08-12 09:51
+# Generated by Django 1.10 on 2016-08-15 12:19
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -13,7 +13,6 @@ class Migration(migrations.Migration):
 
     dependencies = [
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ('dashboard', '0001_initial'),
     ]
 
     operations = [
@@ -21,7 +20,9 @@ class Migration(migrations.Migration):
             name='UserProfile',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('sshkey', models.CharField(max_length=1024)),
+                ('timezone', models.CharField(default='UTC', max_length=100)),
+                ('sshkey', models.CharField(max_length=2048)),
+                ('pgpkey', models.CharField(max_length=2048)),
                 ('company', models.CharField(max_length=200)),
                 ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
             ],
@@ -29,15 +30,4 @@ class Migration(migrations.Migration):
                 'db_table': 'user_profile',
             },
         ),
-        migrations.CreateModel(
-            name='UserResource',
-            fields=[
-                ('id', models.AutoField(primary_key=True, serialize=False)),
-                ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dashboard.Resource')),
-                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
-            ],
-            options={
-                'db_table': 'user_resource',
-            },
-        ),
     ]
index 57735ee..9706b81 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2016-08-12 09:51
+# Generated by Django 1.10 on 2016-08-15 12:19
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -12,8 +12,8 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
         ('dashboard', '0001_initial'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
@@ -21,10 +21,8 @@ class Migration(migrations.Migration):
             name='Booking',
             fields=[
                 ('id', models.AutoField(primary_key=True, serialize=False)),
-                ('deleted', models.BooleanField(default=False)),
                 ('start', models.DateTimeField()),
                 ('end', models.DateTimeField()),
-                ('status', models.CharField(max_length=20)),
                 ('purpose', models.CharField(max_length=300)),
                 ('resource', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dashboard.Resource')),
                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
index e933f6e..00f6b26 100644 (file)
@@ -6,12 +6,15 @@ from django.utils import timezone
 
 from booking.models import Booking
 from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
 
 
 class BookingModelTestCase(TestCase):
     def setUp(self):
-        self.res1 = Resource.objects.create(name='res1', slavename='s1', description='x', url='x')
-        self.res2 = Resource.objects.create(name='res2', slavename='s2', description='x', url='x')
+        self.slave = JenkinsSlave.objects.create(name='test', url='test')
+
+        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', url='x')
+        self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x', url='x')
 
         self.user1 = User.objects.create(username='user1')
 
index f5b75d1..4f5ee8b 100644 (file)
@@ -12,12 +12,14 @@ from registration.forms import User
 from account.models import UserProfile
 from booking.models import Booking
 from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
 
 
 class BookingViewTestCase(TestCase):
     def setUp(self):
         self.client = Client()
-        self.res1 = Resource.objects.create(name='res1', slavename='s1', description='x', url='x')
+        self.slave = JenkinsSlave.objects.create(name='test', url='test')
+        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', url='x')
         self.user1 = User.objects.create(username='user1')
         self.user1.set_password('user1')
         self.user1profile = UserProfile.objects.create(user=self.user1)
diff --git a/tools/pharos-dashboard/celerybeat-schedule b/tools/pharos-dashboard/celerybeat-schedule
new file mode 100644 (file)
index 0000000..7e5fe85
Binary files /dev/null and b/tools/pharos-dashboard/celerybeat-schedule differ
index f8c1fc1..d90e99b 100644 (file)
@@ -6,9 +6,7 @@
         "name": "Linux Foundation POD 1",
         "slavename": "lf-pod1",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab"
     }
 },
 {
@@ -18,9 +16,7 @@
         "name": "Linux Foundation POD 2",
         "slavename": "lf-pod2",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab"
     }
 },
 {
@@ -30,9 +26,7 @@
         "name": "Ericsson  POD 2",
         "slavename": "ericsson-pod2",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process"
     }
 },
 {
@@ -42,9 +36,7 @@
         "name": "Intel POD 2",
         "slavename": "intel-pod2",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2"
     }
 },
 {
@@ -54,9 +46,7 @@
         "name": "Intel POD 5",
         "slavename": "intel-pod5",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5"
     }
 },
 {
@@ -66,9 +56,7 @@
         "name": "Intel POD 6",
         "slavename": "intel-pod6",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6"
     }
 },
 {
@@ -78,9 +66,7 @@
         "name": "Intel POD 8",
         "slavename": "intel-pod8",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8"
     }
 },
 {
@@ -90,9 +76,7 @@
         "name": "Huawei POD 1",
         "slavename": "huawei-pod1",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
-        "bookable": false,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
 },
 {
         "name": "Intel POD 3",
         "slavename": "intel-pod3",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3"
     }
 },
 {
         "name": "Dell POD 1",
         "slavename": "dell-pod1",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting"
     }
 },
 {
         "name": "Dell POD 2",
         "slavename": "dell-pod2",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting"
     }
 },
 {
         "name": "Orange POD 2",
         "slavename": "orange-pod2",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2"
     }
 },
 {
         "name": "Arm POD 1",
         "slavename": "arm-build1",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab"
     }
 },
 {
         "name": "Ericsson POD 1",
         "slavename": "ericsson-pod1",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process"
     }
 },
 {
         "name": "Huawei POD 2",
         "slavename": "huawei-pod2",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
 },
 {
         "name": "Huawei POD 3",
         "slavename": "huawei-pod3",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
 },
 {
         "name": "Huawei POD 4",
         "slavename": "huawei-pod4",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
 },
 {
         "name": "Intel POD 9",
         "slavename": "intel-pod9",
         "description": "Some description",
-        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9",
-        "bookable": true,
-        "active": true
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9"
     }
 }
 ]
index b93d829..6343b46 100644 (file)
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2016-08-12 09:51
+# Generated by Django 1.10 on 2016-08-15 12:19
 from __future__ import unicode_literals
 
+from django.conf import settings
 from django.db import migrations, models
 
 
@@ -10,6 +11,7 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
@@ -21,11 +23,18 @@ class Migration(migrations.Migration):
                 ('slavename', models.CharField(blank=True, max_length=50, null=True)),
                 ('description', models.CharField(blank=True, max_length=300, null=True)),
                 ('url', models.CharField(blank=True, max_length=100, null=True)),
-                ('bookable', models.BooleanField(default=False)),
-                ('active', models.BooleanField(default=True)),
+                ('owners', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
             ],
             options={
                 'db_table': 'resource',
             },
         ),
+        migrations.CreateModel(
+            name='ResourceUtilization',
+            fields=[
+                ('timestamp', models.DateTimeField(auto_created=True)),
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('pod_status', models.IntegerField()),
+            ],
+        ),
     ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0002_auto_20160815_1511.py b/tools/pharos-dashboard/dashboard/migrations/0002_auto_20160815_1511.py
new file mode 100644 (file)
index 0000000..67822a7
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-15 15:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('jenkins', '0002_auto_20160815_1226'),
+        ('dashboard', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='resource',
+            name='slavename',
+        ),
+        migrations.AddField(
+            model_name='resource',
+            name='slave',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='jenkins.JenkinsSlave'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0003_auto_20160815_1512.py b/tools/pharos-dashboard/dashboard/migrations/0003_auto_20160815_1512.py
new file mode 100644 (file)
index 0000000..53b4fcd
--- /dev/null
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-15 15:12
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dashboard', '0002_auto_20160815_1511'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='resource',
+            name='slave',
+        ),
+        migrations.AddField(
+            model_name='resource',
+            name='slavename',
+            field=models.CharField(blank=True, max_length=50, null=True),
+        ),
+    ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0004_resource_slave.py b/tools/pharos-dashboard/dashboard/migrations/0004_resource_slave.py
new file mode 100644 (file)
index 0000000..82d45f0
--- /dev/null
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-15 15:13
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('jenkins', '0002_auto_20160815_1226'),
+        ('dashboard', '0003_auto_20160815_1512'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='resource',
+            name='slave',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='jenkins.JenkinsSlave'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0005_remove_resource_slavename.py b/tools/pharos-dashboard/dashboard/migrations/0005_remove_resource_slavename.py
new file mode 100644 (file)
index 0000000..339f8c3
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-15 15:17
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dashboard', '0004_resource_slave'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='resource',
+            name='slavename',
+        ),
+    ]
index 973066b..cb6b92b 100644 (file)
@@ -1,14 +1,17 @@
 from django.contrib.auth.models import User
 from django.db import models
+from django.utils import timezone
+
+from jenkins.models import JenkinsSlave
 
 
 class Resource(models.Model):
     id = models.AutoField(primary_key=True)
     name = models.CharField(max_length=100, unique=True)
-    slavename = models.CharField(max_length=50, blank=True, null=True)
     description = models.CharField(max_length=300, blank=True, null=True)
     url = models.CharField(max_length=100, blank=True, null=True)
     owners = models.ManyToManyField(User)
+    slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING)
 
     class Meta:
         db_table = 'resource'
@@ -16,6 +19,7 @@ class Resource(models.Model):
     def __str__(self):
         return self.name
 
+
 class ResourceUtilization(models.Model):
     POD_STATUS = {
         'online': 1,
@@ -25,4 +29,4 @@ class ResourceUtilization(models.Model):
 
     id = models.AutoField(primary_key=True)
     timestamp = models.DateTimeField(auto_created=True)
-    pod_status = models.IntegerField()
\ No newline at end of file
+    pod_status = models.IntegerField()
diff --git a/tools/pharos-dashboard/dashboard/templatetags/__init__.py b/tools/pharos-dashboard/dashboard/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py b/tools/pharos-dashboard/dashboard/templatetags/jenkins_filters.py
new file mode 100644 (file)
index 0000000..f7e00a8
--- /dev/null
@@ -0,0 +1,27 @@
+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'
\ No newline at end of file
index ed62b35..a4c0c4e 100644 (file)
@@ -1,19 +1,18 @@
+from datetime import timedelta
 from django.utils import timezone
 from django.views.generic import TemplateView
 
 from booking.models import Booking
 from dashboard.models import Resource
 from jenkins import adapter as jenkins
+from jenkins.models import JenkinsSlave, JenkinsStatistic
 
 
 class JenkinsSlavesView(TemplateView):
     template_name = "dashboard/jenkins_slaves.html"
 
     def get_context_data(self, **kwargs):
-        slaves = jenkins.get_all_slaves()
-        for slave in slaves:
-            jenkins.parse_slave_data(slave, slave)
-
+        slaves = JenkinsSlave.objects.all()
         context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
         context.update({'title': "Jenkins Slaves", 'slaves': slaves})
         return context
@@ -23,15 +22,7 @@ class CIPodsView(TemplateView):
     template_name = "dashboard/ci_pods.html"
 
     def get_context_data(self, **kwargs):
-        resources = Resource.objects.filter().values()  # get resources as a set of dicts
-        ci_pods = []
-        for resource in resources:
-            if not jenkins.is_ci_slave(resource['slavename']):
-                continue
-            ci_slave = jenkins.get_slave(resource['slavename'])
-            jenkins.parse_slave_data(resource, ci_slave)
-            ci_pods.append(resource)
-
+        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
@@ -41,21 +32,18 @@ class DevelopmentPodsView(TemplateView):
     template_name = "dashboard/dev_pods.html"
 
     def get_context_data(self, **kwargs):
-        resources = Resource.objects.filter().values()  # get resources as a set of dicts
-        dev_pods = []
+        resources = Resource.objects.filter(slave__dev_pod=True)
 
-        current_bookings = Booking.objects.filter(start__lte=timezone.now())
-        current_bookings = current_bookings.filter(end__gt=timezone.now())
+        bookings = Booking.objects.filter(start__lte=timezone.now())
+        bookings = bookings.filter(end__gt=timezone.now())
 
+        dev_pods = []
         for resource in resources:
-            if not jenkins.is_dev_pod(resource['slavename']):
-                continue
-            dev_pod = jenkins.get_slave(resource['slavename'])
-            jenkins.parse_slave_data(resource, dev_pod)
-            for booking in current_bookings:
-                if booking.resource.slavename == resource['slavename']:
-                    resource['current_booking'] = booking
-            dev_pods.append(resource)
+            dev_pod = (resource, None)
+            for booking in bookings:
+                if booking.resource == resource:
+                    dev_pod = (resource, booking)
+            dev_pods.append(dev_pod)
 
         context = super(DevelopmentPodsView, self).get_context_data(**kwargs)
         context.update({'title': "Development Pods", 'dev_pods': dev_pods})
index 06233af..fabd535 100644 (file)
@@ -14,7 +14,7 @@ def get_json(url):
             response = requests.get(url)
             json = response.json()
             cache.set(url, json, 180)  # cache result for 180 seconds
-            return response.json()
+            return json
         except requests.exceptions.RequestException as e:
             logger.exception(e)
         except ValueError as e:
@@ -85,26 +85,22 @@ def is_dev_pod(slavename):
         return True
     return False
 
-def parse_slave_data(slave_dict, slave):
-    slave_dict['status'] = get_slave_status(slave)
-    slave_dict['status_color'] = get_status_color(slave)
-    slave_dict['slaveurl'] = get_slave_url(slave)
-    job = get_jenkins_job(slave['displayName'])
-    if job is not None:
-        slave_dict['last_job'] = parse_job(job)
-
 
 def parse_job(job):
     result = parse_job_string(job['lastBuild']['fullDisplayName'])
+    result['building'] = job['lastBuild']['building']
+    result['result'] = ''
+    if not job['lastBuild']['building']:
+        result['result'] = job['lastBuild']['result']
     result['url'] = job['url']
-    result['color'] = get_job_color(job)
-    if job['lastBuild']['building']:
-        result['blink'] = 'class=blink_me'
     return result
 
 
 def parse_job_string(full_displayname):
     job = {}
+    job['scenario'] = ''
+    job['installer'] = ''
+    job['branch'] = ''
     tokens = re.split(r'[ -]', full_displayname)
     for i in range(len(tokens)):
         if tokens[i] == 'os':
@@ -113,34 +109,10 @@ def parse_job_string(full_displayname):
             job['installer'] = tokens[i]
         elif tokens[i] in ['master', 'arno', 'brahmaputra', 'colorado']:
             job['branch'] = tokens[i]
-
     tokens = full_displayname.split(' ')
     job['name'] = tokens[0]
     return job
 
-
-# TODO: use css
-def get_job_color(job):
-    if job['lastBuild']['building'] is True:
-        return '#646F73'
-    result = job['lastBuild']['result']
-    if result == 'SUCCESS':
-        return '#5cb85c'
-    if result == 'FAILURE':
-        return '#d9534f'
-    if result == 'UNSTABLE':
-        return '#EDD62B'
-
-
-# TODO: use css
-def get_status_color(slave):
-    if not slave['offline'] and slave['idle']:
-        return '#5bc0de'
-    if not slave['offline']:
-        return '#5cb85c'
-    return '#d9534f'
-
-
 def get_slave_url(slave):
     return 'https://build.opnfv.org/ci/computer/' + slave['displayName']
 
diff --git a/tools/pharos-dashboard/jenkins/migrations/0001_initial.py b/tools/pharos-dashboard/jenkins/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..a9bb8d5
--- /dev/null
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-15 12:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='JenkinsSlave',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=100, unique=True)),
+                ('status', models.CharField(default='offline', max_length=30)),
+                ('url', models.CharField(max_length=1024)),
+                ('ci_slave', models.BooleanField(default=False)),
+                ('dev_pod', models.BooleanField(default=False)),
+                ('building', models.BooleanField(default=False)),
+                ('last_job_name', models.CharField(default='', max_length=1024)),
+                ('last_job_url', models.CharField(default='', max_length=1024)),
+                ('last_job_scenario', models.CharField(default='', max_length=50)),
+                ('last_job_branch', models.CharField(default='', max_length=50)),
+                ('last_job_installer', models.CharField(default='', max_length=50)),
+                ('last_job_result', models.CharField(default='', max_length=30)),
+            ],
+            options={
+                'db_table': 'jenkins_slave',
+            },
+        ),
+        migrations.CreateModel(
+            name='JenkinsStatistic',
+            fields=[
+                ('timestamp', models.DateTimeField(auto_created=True)),
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('offline', models.BooleanField(default=False)),
+                ('idle', models.BooleanField(default=False)),
+                ('online', models.BooleanField(default=False)),
+                ('slave', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='jenkins.JenkinsSlave')),
+            ],
+            options={
+                'db_table': 'jenkins_statistic',
+            },
+        ),
+    ]
diff --git a/tools/pharos-dashboard/jenkins/migrations/0002_auto_20160815_1226.py b/tools/pharos-dashboard/jenkins/migrations/0002_auto_20160815_1226.py
new file mode 100644 (file)
index 0000000..f1cf7f9
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-15 12:26
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('jenkins', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='jenkinsstatistic',
+            name='timestamp',
+            field=models.DateTimeField(auto_now_add=True),
+        ),
+    ]
diff --git a/tools/pharos-dashboard/jenkins/migrations/__init__.py b/tools/pharos-dashboard/jenkins/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/jenkins/models.py b/tools/pharos-dashboard/jenkins/models.py
new file mode 100644 (file)
index 0000000..f7d54c9
--- /dev/null
@@ -0,0 +1,34 @@
+from django.db import models
+
+
+class JenkinsSlave(models.Model):
+    id = models.AutoField(primary_key=True)
+    name = models.CharField(max_length=100, unique=True)
+    status = models.CharField(max_length=30, default='offline')
+    url = models.CharField(max_length=1024)
+    ci_slave = models.BooleanField(default=False)
+    dev_pod = models.BooleanField(default=False)
+
+    building = models.BooleanField(default=False)
+
+    last_job_name = models.CharField(max_length=1024, default='')
+    last_job_url = models.CharField(max_length=1024, default='')
+    last_job_scenario = models.CharField(max_length=50, default='')
+    last_job_branch = models.CharField(max_length=50, default='')
+    last_job_installer = models.CharField(max_length=50, default='')
+    last_job_result = models.CharField(max_length=30, default='')
+
+    class Meta:
+        db_table = 'jenkins_slave'
+
+
+class JenkinsStatistic(models.Model):
+    id = models.AutoField(primary_key=True)
+    slave = models.ForeignKey(JenkinsSlave, on_delete=models.CASCADE)
+    offline = models.BooleanField(default=False)
+    idle = models.BooleanField(default=False)
+    online = models.BooleanField(default=False)
+    timestamp = models.DateTimeField(auto_now_add=True)
+
+    class Meta:
+        db_table = 'jenkins_statistic'
diff --git a/tools/pharos-dashboard/jenkins/tasks.py b/tools/pharos-dashboard/jenkins/tasks.py
new file mode 100644 (file)
index 0000000..6998cf3
--- /dev/null
@@ -0,0 +1,39 @@
+from celery import shared_task
+
+from jenkins.models import JenkinsSlave, JenkinsStatistic
+from .adapter import *
+
+
+@shared_task
+def sync_jenkins():
+    update_jenkins_slaves()
+
+
+def update_jenkins_slaves():
+    jenkins_slaves = get_all_slaves()
+    for slave in jenkins_slaves:
+        jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'],
+                                                                    url=get_slave_url(slave))
+        jenkins_slave.ci_slave = is_ci_slave(slave['displayName'])
+        jenkins_slave.dev_pod = is_dev_pod(slave['displayName'])
+        jenkins_slave.status = get_slave_status(slave)
+
+        last_job = get_jenkins_job(jenkins_slave.name)
+        if last_job is not None:
+            last_job = parse_job(last_job)
+            jenkins_slave.last_job_name = last_job['name']
+            jenkins_slave.last_job_url = last_job['url']
+            jenkins_slave.last_job_scenario = last_job['scenario']
+            jenkins_slave.last_job_branch = last_job['branch']
+            jenkins_slave.last_job_installer = last_job['installer']
+            jenkins_slave.last_job_result = last_job['result']
+        jenkins_slave.save()
+
+        jenkins_statistic = JenkinsStatistic(slave=jenkins_slave)
+        if jenkins_slave.status == 'online' or jenkins_slave.status == 'building':
+            jenkins_statistic.online = True
+        if jenkins_slave.status == 'offline':
+            jenkins_statistic.offline = True
+        if jenkins_slave.status == 'online / idle':
+            jenkins_statistic.idle = True
+        jenkins_statistic.save()
index e69de29..b6fc817 100644 (file)
@@ -0,0 +1,3 @@
+# This will make sure the app is always imported when
+# Django starts so that shared_task will use this app.
+from .celery import app as celery_app  # noqa
diff --git a/tools/pharos-dashboard/pharos_dashboard/celery.py b/tools/pharos-dashboard/pharos_dashboard/celery.py
new file mode 100644 (file)
index 0000000..4cf6a7a
--- /dev/null
@@ -0,0 +1,20 @@
+import os
+
+from celery import Celery
+
+# set the default Django settings module for the 'celery' program.
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pharos_dashboard.settings')
+
+from django.conf import settings  # noqa
+
+app = Celery('pharos_dashboard')
+
+# Using a string here means the worker will not have to
+# pickle the object when using Windows.
+app.config_from_object('django.conf:settings')
+app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
+
+
+@app.task(bind=True)
+def debug_task(self):
+    print('Request: {0!r}'.format(self.request))
\ No newline at end of file
index b6e9899..7717501 100644 (file)
@@ -15,7 +15,6 @@ import os
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
-
 # Quick-start development settings - unsuitable for production
 # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
 
@@ -27,7 +26,6 @@ DEBUG = True
 
 ALLOWED_HOSTS = []
 
-
 # Application definition
 
 INSTALLED_APPS = [
@@ -43,6 +41,8 @@ INSTALLED_APPS = [
     'django.contrib.staticfiles',
     'django.contrib.humanize',
     'bootstrap3',
+    'djcelery',
+    'kombu.transport.django',
 ]
 
 MIDDLEWARE = [
@@ -77,7 +77,6 @@ TEMPLATES = [
 
 WSGI_APPLICATION = 'pharos_dashboard.wsgi.application'
 
-
 # Database
 # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
 
@@ -92,7 +91,6 @@ DATABASES = {
     }
 }
 
-
 # Password validation
 # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
 
@@ -124,7 +122,6 @@ USE_L10N = True
 
 USE_TZ = True
 
-
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/1.10/howto/static-files/
 
@@ -139,3 +136,11 @@ BOOTSTRAP3 = {
 }
 
 LOGIN_REDIRECT_URL = '/'
+
+import djcelery
+
+djcelery.setup_loader()
+# django broker, NOT SAFE FOR PRODUCTION
+BROKER_URL = 'django://'
+CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
+
index d3e5ff6..2982a6f 100644 (file)
@@ -1,5 +1,6 @@
 {% extends "dashboard/table.html" %}
 {% load staticfiles %}
+{% load jenkins_filters %}
 
 {% block table %}
     <thead>
                 <a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
             </th>
             <th>
-                <a target='_blank' href={{ pod.slaveurl }}>{{ pod.slavename }}</a>
+                <a target='_blank' href={{ pod.slave.url }}>{{ pod.slave.name }}</a>
             </th>
-            <th style="background-color:{{ pod.status_color }}">
-                {{ pod.status }}
+            <th style="background-color:{{ pod.slave.status | jenkins_status_color }}">
+                {{ pod.slave.status }}
             </th>
-            <th {{ pod.last_job.blink }}>
-                {{ pod.last_job.installer }}
+            <th {{ pod.slave.last_job_result | jenkins_job_blink }}>
+                {{ pod.slave.last_job_installer }}
             </th>
-            <th {{ pod.last_job.blink }}>
-                {{ pod.last_job.scenario }}
+            <th {{ pod.slave.last_job_result | jenkins_job_blink }}>
+                {{ pod.slave.last_job_scenario }}
             </th>
-            <th {{ pod.last_job.blink }}>
-                {{ pod.last_job.branch }}
+            <th {{ pod.slave.last_job_result | jenkins_job_blink }}>
+                {{ pod.slave.last_job_branch }}
             </th>
-            <th><a {{ pod.last_job.blink }} style="color:{{ pod.last_job.color }}"
-                                            target='_blank'
-                                            href={{ pod.last_job.url }}>{{ pod.last_job.name }}</a>
+            <th><a {{ pod.slave.last_job_result | jenkins_job_blink }}
+                    style="color:{{ pod.slave.last_job_result | jenkins_job_color }}"
+                    target='_blank'
+                    href={{ pod.slave.last_job_url }}>{{ pod.slave.last_job_name }}</a>
             </th>
         </tr>
     {% endfor %}`
index f08e1d1..532a3a1 100644 (file)
@@ -1,5 +1,6 @@
 {% extends "dashboard/table.html" %}
 {% load staticfiles %}
+{% load jenkins_filters %}
 
 {% block table %}
     <thead>
     </tr>
     </thead>
     <tbody>
-    {% for resource in dev_pods %}
+    {% for pod, booking in dev_pods %}
         <tr>
             <th>
-                <a target='_blank' href={{ resource.url }}>{{ resource.name }}</a>
+                <a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
             </th>
             <th>
-                <a target='_blank' href={{ resource.slaveurl }}>{{ resource.slavename }}</a>
+                <a target='_blank' href={{ pod.slave.url }}>{{ pod.slave.name }}</a>
             </th>
             <th>
-                {{ resource.current_booking.user.username }}
+                {{ booking.user.username }}
             </th>
             <th>
-                {{ resource.current_booking.end }}
+                {{ booking.end }}
             </th>
             <th>
-                {{ resource.current_booking.purpose }}
+                {{ booking.purpose }}
             </th>
-            <th style="background-color:{{ resource.status_color }}">
-                {{ resource.status }}
+            <th style="background-color:{{ pod.slave.status | jenkins_status_color }}">
+                {{ pod.slave.status }}
             </th>
             <th>
-                <a href="{% url 'booking:create' resource_id=resource.id %}" class="btn btn-primary">
+                <a href="{% url 'booking:create' resource_id=pod.id %}" class="btn btn-primary">
                     Book
                 </a>
             </th>
index 2d011b4..830ed19 100644 (file)
@@ -1,6 +1,8 @@
 {% extends "dashboard/table.html" %}
 {% load staticfiles %}
 
+{% load jenkins_filters %}
+
 {% block table %}
     <thead>
     <tr>
     {% for slave in slaves %}
         <tr>
             <th><a target='_blank'
-                   href={{ slave.slaveurl }}>{{ slave.displayName }}</a>
+                   href={{ slave.url }}>{{ slave.name }}</a>
             </th>
-            <th style="background-color:{{ slave.status_color }}">
+            <th style="background-color:{{ slave.status | jenkins_status_color }}">
                 {{ slave.status }}
             </th>
-            <th><a {{ slave.last_job.blink }} style="color:{{ slave.last_job.color }}"
-                                              target="_blank" href={{ slave.last_job.url }}>
-                {{ slave.last_job.name }}</a>
+            <th><a {{ slave.last_job_result | jenkins_job_blink }}
+                    style="color:{{ slave.last_job_result | jenkins_job_color }}"
+                    target="_blank" href={{ slave.last_job_url }}>
+                {{ slave.last_job_name }}</a>
             </th>
         </tr>
     {% endfor %}