diff options
-rw-r--r-- | src/account/migrations/0004_downtime.py | 24 | ||||
-rw-r--r-- | src/account/models.py | 20 | ||||
-rw-r--r-- | src/api/forms.py | 16 | ||||
-rw-r--r-- | src/api/models.py | 34 | ||||
-rw-r--r-- | src/api/urls.py | 2 | ||||
-rw-r--r-- | src/api/views.py | 36 | ||||
-rw-r--r-- | src/booking/views.py | 5 |
7 files changed, 135 insertions, 2 deletions
diff --git a/src/account/migrations/0004_downtime.py b/src/account/migrations/0004_downtime.py new file mode 100644 index 0000000..fc700d1 --- /dev/null +++ b/src/account/migrations/0004_downtime.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2 on 2019-08-13 16:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0003_publicnetwork'), + ] + + operations = [ + migrations.CreateModel( + name='Downtime', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start', models.DateTimeField()), + ('end', models.DateTimeField()), + ('description', models.TextField(default='This lab will be down for maintenance')), + ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), + ], + ), + ] diff --git a/src/account/models.py b/src/account/models.py index 4fc7c40..4862231 100644 --- a/src/account/models.py +++ b/src/account/models.py @@ -168,3 +168,23 @@ class PublicNetwork(models.Model): in_use = models.BooleanField(default=False) cidr = models.CharField(max_length=50, default="0.0.0.0/0") gateway = models.CharField(max_length=50, default="0.0.0.0") + + +class Downtime(models.Model): + start = models.DateTimeField() + end = models.DateTimeField() + lab = models.ForeignKey(Lab, on_delete=models.CASCADE) + description = models.TextField(default="This lab will be down for maintenance") + + def save(self, *args, **kwargs): + if self.start >= self.end: + raise ValueError('Start date is after end date') + + # check for overlapping downtimes + overlap_start = Downtime.objects.filter(lab=self.lab, start__gt=self.start, start__lt=self.end).exists() + overlap_end = Downtime.objects.filter(lab=self.lab, end__lt=self.end, end__gt=self.start).exists() + + if overlap_start or overlap_end: + raise ValueError('Overlapping Downtime') + + return super(Downtime, self).save(*args, **kwargs) diff --git a/src/api/forms.py b/src/api/forms.py new file mode 100644 index 0000000..1b74a9b --- /dev/null +++ b/src/api/forms.py @@ -0,0 +1,16 @@ +############################################################################## +# Copyright (c) 2019 Parker Berberian 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 +############################################################################# + +import django.forms as forms + + +class DowntimeForm(forms.Form): + start = forms.DateTimeField() + end = forms.DateTimeField() + description = forms.CharField(max_length=1000, required=False) diff --git a/src/api/models.py b/src/api/models.py index 1f708ae..682785b 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -13,6 +13,7 @@ from django.db import models from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404 from django.urls import reverse +from django.utils import timezone import json import uuid @@ -30,6 +31,7 @@ from resource_inventory.models import ( ) from resource_inventory.idf_templater import IDFTemplater from resource_inventory.pdf_templater import PDFTemplater +from account.models import Downtime class JobStatus(object): @@ -67,6 +69,38 @@ class LabManager(object): def __init__(self, lab): self.lab = lab + def get_downtime(self): + return Downtime.objects.filter(start__lt=timezone.now(), end__gt=timezone.now(), lab=self.lab) + + def get_downtime_json(self): + downtime = self.get_downtime().first() # should only be one item in queryset + if downtime: + return { + "is_down": True, + "start": downtime.start, + "end": downtime.end, + "description": downtime.description + } + return {"is_down": False} + + def create_downtime(self, form): + """ + takes in a dictionary that describes the model. + { + "start": utc timestamp + "end": utc timestamp + "description": human text (optional) + } + For timestamp structure, https://docs.djangoproject.com/en/2.2/ref/forms/fields/#datetimefield + """ + Downtime.objects.create( + start=form.cleaned_data['start'], + end=form.cleaned_data['end'], + description=form.cleaned_data['description'], + lab=self.lab + ) + return self.get_downtime_json() + def update_host_remote_info(self, data, host_id): host = get_object_or_404(Host, labid=host_id, lab=self.lab) info = {} diff --git a/src/api/urls.py b/src/api/urls.py index 778f6eb..b8b9cff 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -31,6 +31,7 @@ from api.views import ( lab_profile, lab_status, lab_inventory, + lab_downtime, specific_job, specific_task, new_jobs, @@ -47,6 +48,7 @@ urlpatterns = [ path('labs/<slug:lab_name>/profile', lab_profile), path('labs/<slug:lab_name>/status', lab_status), path('labs/<slug:lab_name>/inventory', lab_inventory), + path('labs/<slug:lab_name>/downtime', lab_downtime), path('labs/<slug:lab_name>/hosts/<slug:host_id>', lab_host), path('labs/<slug:lab_name>/hosts/<slug:host_id>/bmc', update_host_bmc), path('labs/<slug:lab_name>/booking/<int:booking_id>/pdf', get_pdf, name="get-pdf"), diff --git a/src/api/views.py b/src/api/views.py index fb28958..a5153d7 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -12,6 +12,7 @@ from django.contrib.auth.decorators import login_required from django.shortcuts import redirect from django.utils.decorators import method_decorator +from django.utils import timezone from django.views import View from django.http.response import JsonResponse, HttpResponse from rest_framework import viewsets @@ -20,6 +21,7 @@ from django.views.decorators.csrf import csrf_exempt from api.serializers.booking_serializer import BookingSerializer from api.serializers.old_serializers import UserSerializer +from api.forms import DowntimeForm from account.models import UserProfile from booking.models import Booking from api.models import LabManagerTracker, get_task @@ -150,6 +152,40 @@ def current_jobs(request, lab_name=""): return JsonResponse(lab_manager.get_current_jobs(), safe=False) +def lab_downtime(request, lab_name=""): + lab_token = request.META.get('HTTP_AUTH_TOKEN') + lab_manager = LabManagerTracker.get(lab_name, lab_token) + if request.method == "GET": + return JsonResponse(lab_manager.get_downtime_json()) + if request.method == "POST": + return post_lab_downtime(request, lab_manager) + if request.method == "DELETE": + return delete_lab_downtime(lab_manager) + return HttpResponse(status=405) + + +def post_lab_downtime(request, lab_manager): + current_downtime = lab_manager.get_downtime() + if current_downtime.exists(): + return JsonResponse({"error": "Lab is already in downtime"}, status=422) + form = DowntimeForm(request.POST) + if form.is_valid(): + return JsonResponse(lab_manager.create_downtime(form)) + else: + return JsonResponse(form.errors.get_json_data(), status=400) + + +def delete_lab_downtime(lab_manager): + current_downtime = lab_manager.get_downtime() + if current_downtime.exists(): + dt = current_downtime.first() + dt.end = timezone.now() + dt.save() + return JsonResponse(lab_manager.get_downtime_json(), safe=False) + else: + return JsonResponse({"error": "Lab is not in downtime"}, status=422) + + def done_jobs(request, lab_name=""): lab_token = request.META.get('HTTP_AUTH_TOKEN') lab_manager = LabManagerTracker.get(lab_name, lab_token) diff --git a/src/booking/views.py b/src/booking/views.py index bad7dc9..8e25952 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -20,7 +20,7 @@ from django.urls import reverse from resource_inventory.models import ResourceBundle, HostProfile, Image, Host from resource_inventory.resource_manager import ResourceManager -from account.models import Lab +from account.models import Lab, Downtime from booking.models import Booking from booking.stats import StatisticsManager from booking.forms import HostReImageForm @@ -79,8 +79,9 @@ class BookingView(TemplateView): def get_context_data(self, **kwargs): booking = get_object_or_404(Booking, id=self.kwargs['booking_id']) title = 'Booking Details' + downtime = Downtime.objects.filter(lab=booking.lab, start__lt=timezone.now, end__gt=timezone.now()).first() context = super(BookingView, self).get_context_data(**kwargs) - context.update({'title': title, 'booking': booking}) + context.update({'title': title, 'booking': booking, 'downtime': downtime}) return context |