Repository: fossasia/open-event-server

Issue:
Implement a health check endpoint.
Implement a health check endpoint similar to http://api.susi.ai/susi/status.json at `/status` which would check the following things. 

- Connection to the database. (And a few quick select queries using all the sqlalchemy models to ensure the models are inline with the migrations)
- Connection to redis
- Connection to the internet

The endpoint should return a `200` if all checks are okay. Else a `503 Service Unavailable` error to be returned along with information on which check failed. 

**Why is this required ?** 
- This will be used by kuberentes to know when the web instance is ready to receive requests


Discussion:
@niranjan94 Since this will be out of version control, should this be json-api compatible or not ?
Not jsonapi compatible


Resolving patch:
diff --git a/app/__init__.py b/app/__init__.py
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -25,6 +25,7 @@
 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
 
 import sqlalchemy as sa
 
@@ -38,7 +39,7 @@
 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
 
 
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -160,6 +161,13 @@ def __call__(self, *args, **kwargs):
 
 celery = make_celery(current_app)
 
+# Health-check
+health = HealthCheck(current_app, "/health-check")
+envdump = EnvironmentDump(current_app, "/environment", include_config=False)
+health.add_check(health_check_celery)
+health.add_check(health_check_db)
+health.add_check(health_check_migrations)
+
 
 # http://stackoverflow.com/questions/9824172/find-out-whether-celery-task-exists
 @after_task_publish.connect
diff --git a/app/views/healthcheck.py b/app/views/healthcheck.py
new file mode 100644
--- /dev/null
+++ b/app/views/healthcheck.py
@@ -0,0 +1,60 @@
+from app.models import db
+from celery.task.control import inspect
+from errno import errorcode
+
+
+def health_check_celery():
+    """
+    Check health status of celery and redis broker
+    :return:
+    """
+    try:
+        d = inspect().stats()
+        if not d:
+            return False, 'No running Celery workers were found.'
+    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.'
+        return False, msg
+    except ImportError as e:
+        return False, str(e)
+    return True, 'celery ok'
+
+
+def health_check_db():
+    """
+    Check health status of db
+    :return:
+    """
+    try:
+        db.session.execute('SELECT 1')
+        return True, 'database ok'
+    except:
+        return False, 'Error connecting to database'
+
+
+def health_check_migrations():
+    """
+    Checks whether database is up to date with migrations by performing a select query on each model
+    :return:
+    """
+    # Get all the models in the db, all models should have a explicit __tablename__
+    classes, models, table_names = [], [], []
+    # noinspection PyProtectedMember
+    for class_ in db.Model._decl_class_registry.values():
+        try:
+            table_names.append(class_.__tablename__)
+            classes.append(class_)
+        except:
+            pass
+    for table in db.metadata.tables.items():
+        if table[0] in table_names:
+            models.append(classes[table_names.index(table[0])])
+
+    for model in models:
+        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'


Test patch: