summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/account/migrations/0004_downtime.py24
-rw-r--r--src/account/models.py20
-rw-r--r--src/api/forms.py16
-rw-r--r--src/api/models.py34
-rw-r--r--src/api/urls.py2
-rw-r--r--src/api/views.py36
-rw-r--r--src/booking/views.py5
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