From 37eb3a614a32701b3f66e9d6797ce8071e278dea Mon Sep 17 00:00:00 2001
From: Tanner Prestegard <tanner.prestegard@ligo.org>
Date: Wed, 26 Jun 2019 11:10:46 -0500
Subject: [PATCH] Add maintenance mode functionality

---
 config/settings/base.py                |  5 +++
 config/settings/container/base.py      | 12 ++++++
 gracedb/core/decorators.py             |  9 +++++
 gracedb/core/middleware/maintenance.py | 55 ++++++++++++++++++++++++++
 gracedb/templates/maintenance.html     | 36 +++++++++++++++++
 5 files changed, 117 insertions(+)
 create mode 100644 gracedb/core/decorators.py
 create mode 100644 gracedb/core/middleware/maintenance.py
 create mode 100644 gracedb/templates/maintenance.html

diff --git a/config/settings/base.py b/config/settings/base.py
index 8b3c9d067..4c0156b30 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -21,6 +21,10 @@ def get_from_env(envvar, default_value=None, fail_if_not_found=True):
             'Could not get environment variable {0}'.format(envvar))
     return value
 
+# Maintenance mode
+MAINTENANCE_MODE = False
+MAINTENANCE_MODE_MESSAGE = None
+
 # Version ---------------------------------------------------------------------
 PROJECT_VERSION = '2.6.3'
 
@@ -307,6 +311,7 @@ AUTHENTICATION_BACKENDS = [
 
 # List of middleware classes to use.
 MIDDLEWARE = [
+    'core.middleware.maintenance.MaintenanceModeMiddleware',
     'events.middleware.PerformanceMiddleware',
     'core.middleware.accept.AcceptMiddleware',
     'core.middleware.api.ClientVersionMiddleware',
diff --git a/config/settings/container/base.py b/config/settings/container/base.py
index 54943380b..8fad4fa12 100644
--- a/config/settings/container/base.py
+++ b/config/settings/container/base.py
@@ -61,6 +61,18 @@ TWILIO_AUTH_TOKEN = os.environ.get('DJANGO_TWILIO_AUTH_TOKEN', None)
 if TWILIO_AUTH_TOKEN is None:
     raise ImproperlyConfigured('Could not get Twilio auth token from envvars.')
 
+# Get maintenance mode settings from environment
+maintenance_mode = get_from_env(
+    'DJANGO_MAINTENANCE_MODE_ACTIVE',
+    default_value=False,
+    fail_if_not_found=False
+)
+if (isinstance(maintenance_mode, str) and
+    maintenance_mode.lower() in ['true', 't', '1']):
+    MAINTENANCE_MODE = True
+MAINTENANCE_MODE_MESSAGE = \
+    get_from_env('DJANGO_MAINTENANCE_MODE_MESSAGE', fail_if_not_found=False)
+
 # Get email settings from environment
 EMAIL_BACKEND = 'django_ses.SESBackend'
 AWS_SES_ACCESS_KEY_ID = get_from_env('AWS_SES_ACCESS_KEY_ID')
diff --git a/gracedb/core/decorators.py b/gracedb/core/decorators.py
new file mode 100644
index 000000000..9ba4d4393
--- /dev/null
+++ b/gracedb/core/decorators.py
@@ -0,0 +1,9 @@
+from functools import wraps
+
+
+def ignore_maintenance_mode(view):
+    @wraps(view)
+    def inner(request, *args, **kwargs):
+        return view(request, *args, **kwargs)
+    inner.__dict__['ignore_maintenance_mode'] = True
+    return inner
diff --git a/gracedb/core/middleware/maintenance.py b/gracedb/core/middleware/maintenance.py
new file mode 100644
index 000000000..a60fac9d9
--- /dev/null
+++ b/gracedb/core/middleware/maintenance.py
@@ -0,0 +1,55 @@
+from django.conf import settings
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.urls import resolve
+
+import logging
+
+# Set up logger
+logger = logging.getLogger(__name__)
+
+
+class MaintenanceModeMiddleware(object):
+    accept_header_name = 'HTTP_ACCEPT'
+    default_message = 'The site is temporarily down for maintenance.'
+
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        # Process request -----------------------------------------------------
+        if settings.MAINTENANCE_MODE is True:
+
+            # Check if the view specifies to ignore maintenance mode
+            ignore_maintenance = \
+                self.check_for_ignore_maintenance_mode(request)
+
+            if not ignore_maintenance:
+                # Get message to display
+                maintenance_message = self.get_message()
+
+                accept_header = request.META.get(self.accept_header_name, None)
+                if accept_header and 'text/html' in accept_header:
+                    # Attempt to handle browsers
+                    context = {'message': maintenance_message}
+                    return render(request, 'maintenance.html', context=context,
+                                  status=503)
+                else:
+                    # Anything else (likely client API requests)
+                    return HttpResponse(maintenance_message, status=503)
+
+        # Otherwise, get response and return with no further processing -------
+        response = self.get_response(request)
+        return response
+
+    @staticmethod
+    def check_for_ignore_maintenance_mode(request):
+        resolver_match = resolve(request.path)
+        view_func = resolver_match.func
+        return view_func.__dict__.get('ignore_maintenance_mode', False)
+
+    def get_message(self):
+        message = settings.MAINTENANCE_MODE_MESSAGE
+        if message is None:
+            message = self.default_message
+        return message
diff --git a/gracedb/templates/maintenance.html b/gracedb/templates/maintenance.html
new file mode 100644
index 000000000..3348159e9
--- /dev/null
+++ b/gracedb/templates/maintenance.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block title %}Maintenance{% endblock %}
+{% block heading %}{% endblock %}
+
+{% block headcontents %}
+{{ block.super }}
+<style>
+.maintenance-message {
+    background-color: red;
+    color: white;
+    text-align: center;
+    display: inline-block;
+    border-radius: 5px;
+    padding: 10px;
+    min-width: 400px;
+    max-width: 600px;
+    box-shadow: 0 0 20px rgba(0,0,0,.2);
+}
+.maintenance-message p {
+    font-weight: bold;
+    font-size: 2rem;
+    margin: 0;
+}
+</style>
+{% endblock %}
+
+{% block content %}
+
+<div align="center" style="padding: 30px;">
+    <div class="maintenance-message">
+        <p>{{ message | safe }}</p>
+    </div>
+</div>
+
+{% endblock %}
-- 
GitLab