--- /dev/null
+# 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')),
+            ],
+        ),
+    ]
 
     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)
 
--- /dev/null
+##############################################################################
+# 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)
 
 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
 )
 from resource_inventory.idf_templater import IDFTemplater
 from resource_inventory.pdf_templater import PDFTemplater
+from account.models import Downtime
 
 
 class JobStatus(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 = {}
 
     lab_profile,
     lab_status,
     lab_inventory,
+    lab_downtime,
     specific_job,
     specific_task,
     new_jobs,
     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"),
 
 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
 
 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
     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)
 
 
 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
     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