Repository: fossasia/open-event-server

Issue:
Health-check: Check if migrations are up to date only once
Currently checking if migrations are up to date or not causes a timeout when checked again everytime `/health-check` is called. We can run this on startup in create_app, store a string containing the results in maybe say app.config and whenever a call to /health-check is made, we can parse that saved string.


Discussion:

Resolving patch:
diff --git a/app/__init__.py b/app/__init__.py
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -5,8 +5,6 @@
 warnings.simplefilter('ignore', ExtDeprecationWarning)
 # Keep it before flask extensions are imported
 
-from pytz import utc
-
 from celery import Celery
 from celery.signals import after_task_publish
 import logging
@@ -22,7 +20,6 @@
 from flask_jwt import JWT
 from datetime import timedelta
 from flask_cors import CORS
-from raven.contrib.flask import Sentry
 from flask_rest_jsonapi.errors import jsonapi_errors
 from flask_rest_jsonapi.exceptions import JsonApiException
 from healthcheck import HealthCheck, EnvironmentDump
@@ -39,7 +36,8 @@
 from app.api.helpers.auth import AuthManager
 from app.models.event import Event, EventsUsers
 from app.models.role_invite import RoleInvite
-from app.views.healthcheck import health_check_celery, health_check_db, health_check_migrations
+from app.views.healthcheck import health_check_celery, health_check_db, health_check_migrations, check_migrations
+from app.views.sentry import sentry
 
 
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -123,9 +121,8 @@ def create_app():
                          view_func=app.send_static_file)
 
     # sentry
-    if app.config['SENTRY_DSN']:
-        sentry = Sentry(dsn=app.config['SENTRY_DSN'])
-        sentry.init_app(app)
+    if 'SENTRY_DSN' in app.config:
+        sentry.init_app(app, dsn=app.config['SENTRY_DSN'])
 
     return app, _manager, db, _jwt
 
@@ -166,6 +163,8 @@ def __call__(self, *args, **kwargs):
 envdump = EnvironmentDump(current_app, "/environment", include_config=False)
 health.add_check(health_check_celery)
 health.add_check(health_check_db)
+with current_app.app_context():
+    current_app.config['MIGRATION_STATUS'] = check_migrations()
 health.add_check(health_check_migrations)
 
 
diff --git a/app/views/healthcheck.py b/app/views/healthcheck.py
--- a/app/views/healthcheck.py
+++ b/app/views/healthcheck.py
@@ -1,6 +1,9 @@
 from app.models import db
 from celery.task.control import inspect
 from errno import errorcode
+from flask import current_app
+from app.views.sentry import sentry
+from redis.exceptions import ConnectionError
 
 
 def health_check_celery():
@@ -11,14 +14,23 @@ def health_check_celery():
     try:
         d = inspect().stats()
         if not d:
+            sentry.captureMessage('No running Celery workers were found.')
             return False, 'No running Celery workers were found.'
+    except ConnectionError as e:
+        sentry.captureException()
+        return False, 'cannot connect to redis server'
     except IOError as e:
         msg = "Error connecting to the backend: " + str(e)
         if len(e.args) > 0 and errorcode.get(e.args[0]) == 'ECONNREFUSED':
             msg += ' Check that the Redis server is running.'
+        sentry.captureException()
         return False, msg
     except ImportError as e:
+        sentry.catureException()
         return False, str(e)
+    except Exception:
+        sentry.captureException()
+        return False, 'celery not ok'
     return True, 'celery ok'
 
 
@@ -31,10 +43,11 @@ def health_check_db():
         db.session.execute('SELECT 1')
         return True, 'database ok'
     except:
+        sentry.captureException()
         return False, 'Error connecting to database'
 
 
-def health_check_migrations():
+def check_migrations():
     """
     Checks whether database is up to date with migrations by performing a select query on each model
     :return:
@@ -56,5 +69,22 @@ def health_check_migrations():
         try:
             db.session.query(model).first()
         except:
-            return False, '{} model out of date with migrations'.format(model)
-    return True, 'database up to date with migrations'
+            sentry.captureException()
+            return 'failure,{} model out of date with migrations'.format(model)
+    return 'success,database up to date with migrations'
+
+
+def health_check_migrations():
+    """
+    Parses config var 'MIGRATION_STATUS' obtained from check_migrations function
+    :return:
+    """
+    if 'MIGRATION_STATUS' in current_app.config:
+        result = current_app.config['MIGRATION_STATUS'].split(',')
+        if result[0] == 'success':
+            return True, result[1]
+        else:
+            # the exception will be caught in check_migrations function, so no need for sentry catching exception here
+            return False, result[1]
+    else:
+        return False, 'The health_check_migration test is still running'
diff --git a/app/views/sentry.py b/app/views/sentry.py
new file mode 100644
--- /dev/null
+++ b/app/views/sentry.py
@@ -0,0 +1,3 @@
+from raven.contrib.flask import Sentry
+
+sentry = Sentry()


Test patch:
diff --git a/tests/unittests/test_migrations.py b/tests/unittests/test_migrations.py
new file mode 100644
--- /dev/null
+++ b/tests/unittests/test_migrations.py
@@ -0,0 +1,14 @@
+from app import current_app as app
+from tests.unittests.utils import OpenEventTestCase
+from tests.unittests.setup_database import Setup
+from app.views.healthcheck import check_migrations
+
+
+class TestMigrations(OpenEventTestCase):
+    def setUp(self):
+        self.app = Setup.create_app()
+
+    def test_migrations(self):
+        with app.test_request_context():
+            result = check_migrations().split(',')
+            self.assertEqual(result[0], 'success')