From af9b7ddeb637278a7705964ba98c8e6a2e7307f4 Mon Sep 17 00:00:00 2001 From: maxbr Date: Mon, 17 Oct 2016 14:56:11 +0200 Subject: [PATCH] Add unit tests JIRA: RELENG-12 This commit increases test statement coverage to 84%. It also fixes the bugs that emerged while testing. Change-Id: I696091f1a07f8b7647872c7cb15f4368a4690619 Signed-off-by: maxbr --- tools/pharos-dashboard/src/account/jira_util.py | 3 +- tools/pharos-dashboard/src/account/models.py | 5 +- .../src/account/tests/test_general.py | 10 +++ tools/pharos-dashboard/src/account/views.py | 22 +++++-- tools/pharos-dashboard/src/api/views.py | 10 +-- tools/pharos-dashboard/src/booking/models.py | 3 +- .../src/booking/tests/test_models.py | 29 +++----- .../src/booking/tests/test_views.py | 37 +++++++++-- tools/pharos-dashboard/src/booking/views.py | 13 ++-- tools/pharos-dashboard/src/dashboard/models.py | 2 +- tools/pharos-dashboard/src/dashboard/tasks.py | 3 +- .../src/dashboard/templatetags/jira_filters.py | 3 +- .../src/dashboard/tests/__init__.py | 10 +++ .../src/dashboard/tests/test_models.py | 69 +++++++++++++++++++ .../src/dashboard/tests/test_views.py | 75 +++++++++++++++++++++ tools/pharos-dashboard/src/dashboard/urls.py | 1 + tools/pharos-dashboard/src/dashboard/views.py | 10 +-- tools/pharos-dashboard/src/jenkins/adapter.py | 2 +- tools/pharos-dashboard/src/jenkins/models.py | 2 + tools/pharos-dashboard/src/jenkins/tasks.py | 15 +++++ tools/pharos-dashboard/src/jenkins/tests.py | 77 ++++++++++++++++++++++ tools/pharos-dashboard/src/notification/models.py | 5 +- tools/pharos-dashboard/src/notification/tasks.py | 2 +- tools/pharos-dashboard/src/notification/tests.py | 41 ++++++++++++ .../src/templates/booking/booking_calendar.html | 47 +++++++------ 25 files changed, 414 insertions(+), 82 deletions(-) create mode 100644 tools/pharos-dashboard/src/dashboard/tests/__init__.py create mode 100644 tools/pharos-dashboard/src/dashboard/tests/test_models.py create mode 100644 tools/pharos-dashboard/src/dashboard/tests/test_views.py create mode 100644 tools/pharos-dashboard/src/notification/tests.py diff --git a/tools/pharos-dashboard/src/account/jira_util.py b/tools/pharos-dashboard/src/account/jira_util.py index c333f8c4..fdb87f77 100644 --- a/tools/pharos-dashboard/src/account/jira_util.py +++ b/tools/pharos-dashboard/src/account/jira_util.py @@ -12,11 +12,10 @@ import base64 import os import oauth2 as oauth +from django.conf import settings from jira import JIRA from tlslite.utils import keyfactory -from django.conf import settings - class SignatureMethod_RSA_SHA1(oauth.SignatureMethod): name = 'RSA-SHA1' diff --git a/tools/pharos-dashboard/src/account/models.py b/tools/pharos-dashboard/src/account/models.py index d87ee183..c2e99028 100644 --- a/tools/pharos-dashboard/src/account/models.py +++ b/tools/pharos-dashboard/src/account/models.py @@ -8,12 +8,9 @@ ############################################################################## -from django.db import models - from django.contrib.auth.models import User -from rest_framework.authtoken.models import Token +from django.db import models -from dashboard.models import Resource def upload_to(object, filename): return object.user.username + '/' + filename diff --git a/tools/pharos-dashboard/src/account/tests/test_general.py b/tools/pharos-dashboard/src/account/tests/test_general.py index 72e7ea11..e8f483b5 100644 --- a/tools/pharos-dashboard/src/account/tests/test_general.py +++ b/tools/pharos-dashboard/src/account/tests/test_general.py @@ -48,3 +48,13 @@ class AccountMiddlewareTestCase(TestCase): self.user1profile.save() self.client.get(url) self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich') + + # if there is no profile for a user, it should be created + user2 = User.objects.create(username='user2') + user2.set_password('user2') + user2.save() + self.client.login(username='user2', password='user2') + self.client.get(url) + self.assertTrue(user2.userprofile) + + diff --git a/tools/pharos-dashboard/src/account/views.py b/tools/pharos-dashboard/src/account/views.py index ac973f53..17fbdc3a 100644 --- a/tools/pharos-dashboard/src/account/views.py +++ b/tools/pharos-dashboard/src/account/views.py @@ -21,7 +21,6 @@ from django.contrib.auth.models import User from django.urls import reverse from django.utils.decorators import method_decorator from django.views.generic import RedirectView, TemplateView, UpdateView - from jira import JIRA from rest_framework.authtoken.models import Token @@ -58,9 +57,16 @@ class JiraLoginView(RedirectView): client.set_signature_method(SignatureMethod_RSA_SHA1()) # Step 1. Get a request token from Jira. - resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST") + try: + resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST") + except Exception as e: + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') + return '/' if resp['status'] != '200': - raise Exception("Invalid response %s: %s" % (resp['status'], content)) + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') + return '/' # Step 2. Store the request token in a session for later use. self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode())) @@ -87,8 +93,15 @@ class JiraAuthenticatedView(RedirectView): client.set_signature_method(SignatureMethod_RSA_SHA1()) # Step 2. Request the authorized access token from Jira. - resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST") + try: + resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST") + except Exception as e: + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') + return '/' if resp['status'] != '200': + messages.add_message(self.request, messages.ERROR, + 'Error: Connection to Jira failed. Please contact an Administrator') return '/' access_token = dict(urllib.parse.parse_qsl(content.decode())) @@ -128,6 +141,7 @@ class JiraAuthenticatedView(RedirectView): # redirect user to settings page to complete profile return url + @method_decorator(login_required, name='dispatch') class UserListView(TemplateView): template_name = "account/user_list.html" diff --git a/tools/pharos-dashboard/src/api/views.py b/tools/pharos-dashboard/src/api/views.py index 55de5c6a..2595e5ef 100644 --- a/tools/pharos-dashboard/src/api/views.py +++ b/tools/pharos-dashboard/src/api/views.py @@ -8,16 +8,16 @@ ############################################################################## -from rest_framework import viewsets - from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect from django.utils.decorators import method_decorator +from django.views import View +from rest_framework import viewsets +from rest_framework.authtoken.models import Token + from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer from booking.models import Booking from dashboard.models import Resource, Server -from django.views import View -from rest_framework.authtoken.models import Token -from django.shortcuts import redirect class BookingViewSet(viewsets.ModelViewSet): diff --git a/tools/pharos-dashboard/src/booking/models.py b/tools/pharos-dashboard/src/booking/models.py index 88ab5589..0b3fa3b1 100644 --- a/tools/pharos-dashboard/src/booking/models.py +++ b/tools/pharos-dashboard/src/booking/models.py @@ -8,13 +8,14 @@ ############################################################################## +from django.conf import settings from django.contrib.auth.models import User from django.db import models from jira import JIRA from jira import JIRAError from dashboard.models import Resource -from django.conf import settings + class Installer(models.Model): id = models.AutoField(primary_key=True) diff --git a/tools/pharos-dashboard/src/booking/tests/test_models.py b/tools/pharos-dashboard/src/booking/tests/test_models.py index 612b35c0..b4cd1133 100644 --- a/tools/pharos-dashboard/src/booking/tests/test_models.py +++ b/tools/pharos-dashboard/src/booking/tests/test_models.py @@ -10,12 +10,11 @@ from datetime import timedelta -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import Permission from django.test import TestCase from django.utils import timezone -from account.models import UserProfile -from booking.models import Booking +from booking.models import * from dashboard.models import Resource from jenkins.models import JenkinsSlave @@ -37,7 +36,10 @@ class BookingModelTestCase(TestCase): self.user1 = User.objects.get(pk=self.user1.id) - def test_start__end(self): + self.installer = Installer.objects.create(name='TestInstaller') + self.scenario = Scenario.objects.create(name='TestScenario') + + def test_start_end(self): """ if the start of a booking is greater or equal then the end, saving should raise a ValueException @@ -88,20 +90,5 @@ class BookingModelTestCase(TestCase): user=self.user1, resource=self.res1)) self.assertTrue( Booking.objects.create(start=start, end=end, - user=self.user1, resource=self.res2)) - - def test_authorization(self): - user = User.objects.create(username='user') - user.userprofile = UserProfile.objects.create(user=user) - self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(), - end=timezone.now() + timedelta(days=1), resource=self.res1, user=user) - self.res1.owner = user - self.assertTrue( - Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1), - resource=self.res1, user=user)) - self.res1.owner = self.owner - user.user_permissions.add(self.add_booking_perm) - user = User.objects.get(pk=user.id) - self.assertTrue( - Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1), - resource=self.res2, user=user)) + user=self.user1, resource=self.res2, scenario=self.scenario, + installer=self.installer)) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/tests/test_views.py b/tools/pharos-dashboard/src/booking/tests/test_views.py index e568c155..c1da013c 100644 --- a/tools/pharos-dashboard/src/booking/tests/test_views.py +++ b/tools/pharos-dashboard/src/booking/tests/test_views.py @@ -10,12 +10,10 @@ from datetime import timedelta -from django.contrib import auth from django.test import Client -from django.utils import timezone -from django.contrib.auth.models import Permission from django.test import TestCase from django.urls import reverse +from django.utils import timezone from django.utils.encoding import force_text from registration.forms import User @@ -37,9 +35,6 @@ class BookingViewTestCase(TestCase): self.user1profile = UserProfile.objects.create(user=self.user1) self.user1.save() - self.add_booking_perm = Permission.objects.get(codename='add_booking') - self.user1.user_permissions.add(self.add_booking_perm) - self.user1 = User.objects.get(pk=self.user1.id) @@ -78,4 +73,34 @@ class BookingViewTestCase(TestCase): self.assertIn('resource', response.context) + def test_booking_view(self): + start = timezone.now() + end = start + timedelta(weeks=1) + booking = Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1) + url = reverse('booking:detail', kwargs={'booking_id':0}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + url = reverse('booking:detail', kwargs={'booking_id':booking.id}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed('booking/booking_detail.html') + self.assertIn('booking', response.context) + + def test_booking_list_view(self): + start = timezone.now() - timedelta(weeks=2) + end = start + timedelta(weeks=1) + Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1) + + url = reverse('booking:list') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed('booking/booking_list.html') + self.assertTrue(len(response.context['bookings']) == 0) + + start = timezone.now() + end = start + timedelta(weeks=1) + Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1) + response = self.client.get(url) + self.assertTrue(len(response.context['bookings']) == 1) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/booking/views.py b/tools/pharos-dashboard/src/booking/views.py index 413573da..6fdca0e0 100644 --- a/tools/pharos-dashboard/src/booking/views.py +++ b/tools/pharos-dashboard/src/booking/views.py @@ -8,8 +8,6 @@ ############################################################################## -from datetime import timedelta - from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin @@ -45,7 +43,7 @@ def create_jira_ticket(user, booking): booking.save() -class BookingFormView(LoginRequiredMixin, FormView): +class BookingFormView(FormView): template_name = "booking/booking_calendar.html" form_class = BookingForm @@ -63,6 +61,11 @@ class BookingFormView(LoginRequiredMixin, FormView): return reverse('booking:create', kwargs=self.kwargs) def form_valid(self, form): + if not self.request.user.is_authenticated: + messages.add_message(self.request, messages.ERROR, + 'You need to be logged in to book a Pod.') + return super(BookingFormView, self).form_invalid(form) + user = self.request.user booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'], @@ -75,9 +78,6 @@ class BookingFormView(LoginRequiredMixin, FormView): except ValueError as err: messages.add_message(self.request, messages.ERROR, err) return super(BookingFormView, self).form_invalid(form) - except PermissionError as err: - messages.add_message(self.request, messages.ERROR, err) - return super(BookingFormView, self).form_invalid(form) try: if settings.CREATE_JIRA_TICKET: create_jira_ticket(user, booking) @@ -101,6 +101,7 @@ class BookingView(TemplateView): context.update({'title': title, 'booking': booking}) return context + class BookingListView(TemplateView): template_name = "booking/booking_list.html" diff --git a/tools/pharos-dashboard/src/dashboard/models.py b/tools/pharos-dashboard/src/dashboard/models.py index e3f22e69..ec6fec76 100644 --- a/tools/pharos-dashboard/src/dashboard/models.py +++ b/tools/pharos-dashboard/src/dashboard/models.py @@ -10,9 +10,9 @@ from datetime import timedelta -from django.utils import timezone from django.contrib.auth.models import User from django.db import models +from django.utils import timezone from jenkins.models import JenkinsSlave diff --git a/tools/pharos-dashboard/src/dashboard/tasks.py b/tools/pharos-dashboard/src/dashboard/tasks.py index 4c09bf90..c5ef5054 100644 --- a/tools/pharos-dashboard/src/dashboard/tasks.py +++ b/tools/pharos-dashboard/src/dashboard/tasks.py @@ -8,8 +8,9 @@ ############################################################################## -from celery import shared_task from datetime import timedelta + +from celery import shared_task from django.utils import timezone from jenkins.models import JenkinsStatistic diff --git a/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py index 70208436..9a97c1d5 100644 --- a/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py +++ b/tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py @@ -8,9 +8,8 @@ ############################################################################## -from django.template.defaultfilters import register - from django.conf import settings +from django.template.defaultfilters import register @register.filter diff --git a/tools/pharos-dashboard/src/dashboard/tests/__init__.py b/tools/pharos-dashboard/src/dashboard/tests/__init__.py new file mode 100644 index 00000000..b5914ce7 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tests/__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/tests/test_models.py b/tools/pharos-dashboard/src/dashboard/tests/test_models.py new file mode 100644 index 00000000..3a3aeab1 --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tests/test_models.py @@ -0,0 +1,69 @@ +############################################################################## +# 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 math import ceil, floor + +from django.test import TestCase +from django.utils import timezone + +from booking.models import * +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class ResourceModelTestCase(TestCase): + def setUp(self): + self.slave = JenkinsSlave.objects.create(name='test', url='test') + self.owner = User.objects.create(username='owner') + + self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', + url='x', owner=self.owner) + + def test_booking_utilization(self): + utilization = self.res1.get_booking_utilization(1) + self.assertTrue(utilization['booked_seconds'] == 0) + self.assertTrue(utilization['available_seconds'] == timedelta(weeks=1).total_seconds()) + + start = timezone.now() + timedelta(days=1) + end = start + timedelta(days=1) + booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1, + user=self.owner) + + utilization = self.res1.get_booking_utilization(1) + booked_seconds = timedelta(days=1).total_seconds() + self.assertEqual(utilization['booked_seconds'], booked_seconds) + + utilization = self.res1.get_booking_utilization(-1) + self.assertEqual(utilization['booked_seconds'], 0) + + booking.delete() + start = timezone.now() - timedelta(days=1) + end = start + timedelta(days=2) + booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1, + user=self.owner) + booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds'] + # use ceil because a fraction of the booked time has already passed now + booked_seconds = ceil(booked_seconds) + self.assertEqual(booked_seconds, timedelta(days=1).total_seconds()) + + booking.delete() + start = timezone.now() + timedelta(days=6) + end = start + timedelta(days=2) + booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1, + user=self.owner) + booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds'] + booked_seconds = floor(booked_seconds) + self.assertEqual(booked_seconds, timedelta(days=1).total_seconds()) + + + + + diff --git a/tools/pharos-dashboard/src/dashboard/tests/test_views.py b/tools/pharos-dashboard/src/dashboard/tests/test_views.py new file mode 100644 index 00000000..f5e17c2a --- /dev/null +++ b/tools/pharos-dashboard/src/dashboard/tests/test_views.py @@ -0,0 +1,75 @@ +############################################################################## +# 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.test import TestCase +from django.urls import reverse + +from dashboard.models import Resource +from jenkins.models import JenkinsSlave + + +class DashboardViewTestCase(TestCase): + def setUp(self): + self.slave_active = JenkinsSlave.objects.create(name='slave_active', url='x', active=True) + self.slave_inactive = JenkinsSlave.objects.create(name='slave_inactive', url='x', + active=False) + self.res_active = Resource.objects.create(name='res_active', slave=self.slave_active, + description='x', url='x') + self.res_inactive = Resource.objects.create(name='res_inactive', slave=self.slave_inactive, + description='x', url='x') + + def test_booking_utilization_json(self): + url = reverse('dashboard:booking_utilization', kwargs={'resource_id': 0, 'weeks': 0}) + self.assertEqual(self.client.get(url).status_code, 404) + + url = reverse('dashboard:booking_utilization', kwargs={'resource_id': self.res_active.id, + 'weeks': 0}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'data') + + def test_jenkins_utilization_json(self): + url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': 0, 'weeks': 0}) + self.assertEqual(self.client.get(url).status_code, 404) + + url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': self.res_active.id, + 'weeks': 0}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'data') + + def test_jenkins_slaves_view(self): + url = reverse('dashboard:jenkins_slaves') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertIn(self.slave_active, response.context['slaves']) + self.assertNotIn(self.slave_inactive, response.context['slaves']) + + def test_ci_pods_view(self): + url = reverse('dashboard:ci_pods') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['ci_pods']), 0) + + self.slave_active.ci_slave = True + self.slave_inactive.ci_slave = True + self.slave_active.save() + self.slave_inactive.save() + + response = self.client.get(url) + self.assertIn(self.res_active, response.context['ci_pods']) + self.assertNotIn(self.res_inactive, response.context['ci_pods']) + + def test_dev_pods_view(self): + url = reverse('dashboard:dev_pods') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['dev_pods']), 0) + diff --git a/tools/pharos-dashboard/src/dashboard/urls.py b/tools/pharos-dashboard/src/dashboard/urls.py index f04f5ca9..609e5d6f 100644 --- a/tools/pharos-dashboard/src/dashboard/urls.py +++ b/tools/pharos-dashboard/src/dashboard/urls.py @@ -24,6 +24,7 @@ Including another URLconf 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url + from dashboard.views import * urlpatterns = [ diff --git a/tools/pharos-dashboard/src/dashboard/views.py b/tools/pharos-dashboard/src/dashboard/views.py index 022a4af0..1848844b 100644 --- a/tools/pharos-dashboard/src/dashboard/views.py +++ b/tools/pharos-dashboard/src/dashboard/views.py @@ -25,7 +25,7 @@ class JenkinsSlavesView(TemplateView): template_name = "dashboard/jenkins_slaves.html" def get_context_data(self, **kwargs): - slaves = JenkinsSlave.objects.all() + slaves = JenkinsSlave.objects.filter(active=True) context = super(JenkinsSlavesView, self).get_context_data(**kwargs) context.update({'title': "Jenkins Slaves", 'slaves': slaves}) return context @@ -35,7 +35,7 @@ class CIPodsView(TemplateView): template_name = "dashboard/ci_pods.html" def get_context_data(self, **kwargs): - ci_pods = Resource.objects.filter(slave__ci_slave=True) + ci_pods = Resource.objects.filter(slave__ci_slave=True, slave__active=True) context = super(CIPodsView, self).get_context_data(**kwargs) context.update({'title': "CI Pods", 'ci_pods': ci_pods}) return context @@ -45,7 +45,7 @@ class DevelopmentPodsView(TemplateView): template_name = "dashboard/dev_pods.html" def get_context_data(self, **kwargs): - resources = Resource.objects.filter(slave__dev_pod=True) + resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True) bookings = Booking.objects.filter(start__lte=timezone.now()) bookings = bookings.filter(end__gt=timezone.now()) @@ -55,7 +55,7 @@ class DevelopmentPodsView(TemplateView): 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']) / + utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) / total * 100) except (ValueError, ZeroDivisionError): return "" @@ -88,7 +88,7 @@ class LabOwnerView(TemplateView): template_name = "dashboard/resource_all.html" def get_context_data(self, **kwargs): - resources = Resource.objects.filter(slave__dev_pod=True) + resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True) pods = [] for resource in resources: utilization = resource.slave.get_utilization(timedelta(days=7)) diff --git a/tools/pharos-dashboard/src/jenkins/adapter.py b/tools/pharos-dashboard/src/jenkins/adapter.py index ff0508d9..edf502f6 100644 --- a/tools/pharos-dashboard/src/jenkins/adapter.py +++ b/tools/pharos-dashboard/src/jenkins/adapter.py @@ -9,8 +9,8 @@ import logging - import re + import requests from django.core.cache import cache diff --git a/tools/pharos-dashboard/src/jenkins/models.py b/tools/pharos-dashboard/src/jenkins/models.py index 0875bba5..8254ff39 100644 --- a/tools/pharos-dashboard/src/jenkins/models.py +++ b/tools/pharos-dashboard/src/jenkins/models.py @@ -29,6 +29,8 @@ class JenkinsSlave(models.Model): last_job_installer = models.CharField(max_length=50, default='') last_job_result = models.CharField(max_length=30, default='') + active = models.BooleanField(default=False) + def get_utilization(self, timedelta): """ Return a dictionary containing the count of idle, online and offline measurements in the time from diff --git a/tools/pharos-dashboard/src/jenkins/tasks.py b/tools/pharos-dashboard/src/jenkins/tasks.py index 7c037827..ea986c1f 100644 --- a/tools/pharos-dashboard/src/jenkins/tasks.py +++ b/tools/pharos-dashboard/src/jenkins/tasks.py @@ -10,6 +10,7 @@ from celery import shared_task +from dashboard.models import Resource from jenkins.models import JenkinsSlave, JenkinsStatistic from .adapter import * @@ -20,14 +21,28 @@ def sync_jenkins(): def update_jenkins_slaves(): + JenkinsSlave.objects.all().update(active=False) + 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.active = True 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) + # if this is a new slave and a pod, check if there is a resource for it, create one if not + if created and 'pod' in slave['displayName']: + # parse resource name from slave name + # naming example: orange-pod1, resource name: Orange POD 1 + tokens = slave['displayName'].split('-') + name = tokens[0].capitalize() + ' POD '# company name + name += tokens[1][3:] # remove 'pod' + resource, created = Resource.objects.get_or_create(name=name) + resource.slave = jenkins_slave + resource.save() + last_job = get_jenkins_job(jenkins_slave.name) if last_job is not None: last_job = parse_job(last_job) diff --git a/tools/pharos-dashboard/src/jenkins/tests.py b/tools/pharos-dashboard/src/jenkins/tests.py index 4f350d20..3723cd38 100644 --- a/tools/pharos-dashboard/src/jenkins/tests.py +++ b/tools/pharos-dashboard/src/jenkins/tests.py @@ -8,9 +8,11 @@ ############################################################################## +from datetime import timedelta from unittest import TestCase import jenkins.adapter as jenkins +from jenkins.models import * # Tests that the data we get with the jenkinsadapter contains all the @@ -28,12 +30,27 @@ class JenkinsAdapterTestCase(TestCase): self.assertTrue('idle' in slave) self.assertTrue('offline' in slave) + def test_get_slave(self): + slaves = jenkins.get_all_slaves() + self.assertEqual(slaves[0], jenkins.get_slave(slaves[0]['displayName'])) + self.assertEqual({}, jenkins.get_slave('098f6bcd4621d373cade4e832627b4f6')) + def test_get_ci_slaves(self): slaves = jenkins.get_ci_slaves() self.assertTrue(len(slaves) > 0) for slave in slaves: self.assertTrue('nodeName' in slave) + def test_get_jenkins_job(self): + slaves = jenkins.get_ci_slaves() + job = None + for slave in slaves: + job = jenkins.get_jenkins_job(slave['nodeName']) + if job is not None: + break + # We need to test at least one job + self.assertNotEqual(job, None) + def test_get_all_jobs(self): jobs = jenkins.get_all_jobs() lastBuild = False @@ -50,3 +67,63 @@ class JenkinsAdapterTestCase(TestCase): self.assertTrue('timestamp' in job['lastBuild']) self.assertTrue('builtOn' in job['lastBuild']) self.assertTrue(lastBuild) + + def test_parse_job(self): + job = { + "displayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado", + "url": "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/", + "lastBuild": { + "building": False, + "fullDisplayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado #37", + "result": "SUCCESS", + "timestamp": 1476283629917, + "builtOn": "lf-pod1" + } + } + + job = jenkins.parse_job(job) + self.assertEqual(job['scenario'], 'os-nosdn-fdio-noha') + self.assertEqual(job['installer'], 'apex') + self.assertEqual(job['branch'], 'colorado') + self.assertEqual(job['result'], 'SUCCESS') + self.assertEqual(job['building'], False) + self.assertEqual(job['url'], + "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/") + self.assertEqual(job['name'], + 'apex-deploy-baremetal-os-nosdn-fdio-noha-colorado') + + def test_get_slave_status(self): + slave = { + 'offline': True, + 'idle': False + } + self.assertEqual(jenkins.get_slave_status(slave), 'offline') + slave = { + 'offline': False, + 'idle': False + } + self.assertEqual(jenkins.get_slave_status(slave), 'online') + slave = { + 'offline': False, + 'idle': True + } + self.assertEqual(jenkins.get_slave_status(slave), 'online / idle') + + +class JenkinsModelTestCase(TestCase): + def test_get_utilization(self): + jenkins_slave = JenkinsSlave.objects.create(name='test', status='offline', url='') + utilization = jenkins_slave.get_utilization(timedelta(weeks=1)) + self.assertEqual(utilization['idle'], 0) + self.assertEqual(utilization['offline'], 0) + self.assertEqual(utilization['online'], 0) + + for i in range(10): + JenkinsStatistic.objects.create(slave=jenkins_slave, + offline=True, idle=True, + online=True) + + utilization = jenkins_slave.get_utilization(timedelta(weeks=1)) + self.assertEqual(utilization['idle'], 10) + self.assertEqual(utilization['offline'], 10) + self.assertEqual(utilization['online'], 10) diff --git a/tools/pharos-dashboard/src/notification/models.py b/tools/pharos-dashboard/src/notification/models.py index 2d199181..0ee275dd 100644 --- a/tools/pharos-dashboard/src/notification/models.py +++ b/tools/pharos-dashboard/src/notification/models.py @@ -26,7 +26,8 @@ class BookingNotification(models.Model): } def save(self, *args, **kwargs): - notifications = self.booking.bookingnotification_set.filter(type=self.type) - if notifications.count() > 1: + notifications = self.booking.bookingnotification_set.filter(type=self.type).exclude( + id=self.id) + if notifications.count() > 0: raise ValueError('Doubled Notification') return super(BookingNotification, self).save(*args, **kwargs) \ No newline at end of file diff --git a/tools/pharos-dashboard/src/notification/tasks.py b/tools/pharos-dashboard/src/notification/tasks.py index 61ab14af..4173433c 100644 --- a/tools/pharos-dashboard/src/notification/tasks.py +++ b/tools/pharos-dashboard/src/notification/tasks.py @@ -8,9 +8,9 @@ ############################################################################## -from celery import shared_task from datetime import timedelta +from celery import shared_task from django.conf import settings from django.utils import timezone diff --git a/tools/pharos-dashboard/src/notification/tests.py b/tools/pharos-dashboard/src/notification/tests.py new file mode 100644 index 00000000..9df9aa60 --- /dev/null +++ b/tools/pharos-dashboard/src/notification/tests.py @@ -0,0 +1,41 @@ +############################################################################## +# 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 unittest import TestCase + +from django.contrib.auth.models import User +from django.utils import timezone + +from booking.models import Booking +from dashboard.models import Resource +from jenkins.models import JenkinsSlave +from notification.models import * + + +class JenkinsModelTestCase(TestCase): + def setUp(self): + self.slave = JenkinsSlave.objects.create(name='test1', url='test') + self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', + url='x') + self.user1 = User.objects.create(username='user1') + + start = timezone.now() + end = start + timedelta(days=1) + self.booking = Booking.objects.create(start=start, end=end, purpose='test', + resource=self.res1, user=self.user1) + + def test_booking_notification(self): + BookingNotification.objects.create(type='test', booking=self.booking, + submit_time=timezone.now()) + + self.assertRaises(ValueError, BookingNotification.objects.create, type='test', + booking=self.booking, + submit_time=timezone.now()) diff --git a/tools/pharos-dashboard/src/templates/booking/booking_calendar.html b/tools/pharos-dashboard/src/templates/booking/booking_calendar.html index 34f425c9..4644e855 100644 --- a/tools/pharos-dashboard/src/templates/booking/booking_calendar.html +++ b/tools/pharos-dashboard/src/templates/booking/booking_calendar.html @@ -33,27 +33,34 @@ Booking
-
- {% bootstrap_form_errors form type='non_fields' %} -
- {% csrf_token %} + {% if user.is_authenticated %} +
+ {% bootstrap_form_errors form type='non_fields' %} + + {% csrf_token %} -
- {% bootstrap_field form.start addon_after='' %} -
-
- {% bootstrap_field form.end addon_after='' %} -
- {% bootstrap_field form.purpose %} - {% bootstrap_field form.installer %} - {% bootstrap_field form.scenario %} - {% buttons %} - - {% endbuttons %} - -
+
+ {% bootstrap_field form.start addon_after='' %} +
+
+ {% bootstrap_field form.end addon_after='' %} +
+ {% bootstrap_field form.purpose %} + {% bootstrap_field form.installer %} + {% bootstrap_field form.scenario %} + {% buttons %} + + {% endbuttons %} + +
+ {% else %} +

Please + + login with Jira + to book this Pod

+ {% endif %}
-- 2.16.6