Repository: fedora-infra/bodhi

Issue:
Create proper live and readiness endpoints for bodhi to be used in Open Shift configuration.
Currently we use :8080/ for both, we should have proper endpoints that represent the container is alive and should not be killed (usually /healthz/live) and that the container is ready to serve requests (usually /healthz/ready)


Discussion:

Resolving patch:
diff --git a/bodhi/server/__init__.py b/bodhi/server/__init__.py
--- a/bodhi/server/__init__.py
+++ b/bodhi/server/__init__.py
@@ -315,6 +315,8 @@ def main(global_config, testing=None, session=None, **settings):
     config.add_view('pyramid_fas_openid.verify_openid', route_name='verify_openid')
 
     config.add_route('api_version', '/api_version')
+    config.add_route('liveness', '/healthz/live')
+    config.add_route('readyness', '/healthz/ready')
 
     config.scan('bodhi.server.views')
     config.scan('bodhi.server.services')
diff --git a/bodhi/server/views/generic.py b/bodhi/server/views/generic.py
--- a/bodhi/server/views/generic.py
+++ b/bodhi/server/views/generic.py
@@ -470,6 +470,36 @@ def api_version(request):
     return dict(version=bodhi.server.util.version())
 
 
+@view_config(route_name='liveness', renderer='json')
+def liveness(request):
+    """
+    Return 'ok' as a sign of being alive.
+
+    Args:
+        request (pyramid.request.Request): The current request.
+    Returns:
+        str: 'ok'
+    """
+    return 'ok'
+
+
+@view_config(route_name='readyness', renderer='json')
+def readyness(request):
+    """
+    Return 200 if the app can query the db, to signify being ready.
+
+    Args:
+        request (pyramid.request.Request): The current request.
+    Returns:
+        dict: A dictionary with list of services checked.
+    """
+    try:
+        request.db.execute("SELECT 1")
+        return dict(db_session=True)
+    except Exception:
+        raise Exception("App not ready, is unable to execute a trivial select.")
+
+
 @notfound_view_config(append_slash=True)
 def notfound_view(context, request):
     """


Test patch:
diff --git a/bodhi/tests/server/views/test_generic.py b/bodhi/tests/server/views/test_generic.py
--- a/bodhi/tests/server/views/test_generic.py
+++ b/bodhi/tests/server/views/test_generic.py
@@ -19,6 +19,8 @@
 from unittest import mock
 import copy
 
+import pytest
+from pyramid.testing import DummyRequest
 import webtest
 
 from bodhi.server import main, util
@@ -444,6 +446,22 @@ def test_version(self):
         res = self.app.get('/api_version')
         assert 'version' in res.json_body
 
+    def test_readyness(self):
+        res = self.app.get('/healthz/ready')
+        assert 'db_session' in res.json_body
+
+    def test_readyness_not_ready(self):
+        from bodhi.server.views.generic import readyness
+        request = DummyRequest()
+
+        with pytest.raises(Exception) as exc:
+            readyness(request)
+        assert str(exc.value) == 'App not ready, is unable to execute a trivial select.'
+
+    def test_liveness(self):
+        res = self.app.get('/healthz/live')
+        assert 'ok' == res.json_body
+
     def test_new_update_form(self):
         """Test the new update Form page"""
 
diff --git a/devel/ci/integration/tests/test_bodhi.py b/devel/ci/integration/tests/test_bodhi.py
--- a/devel/ci/integration/tests/test_bodhi.py
+++ b/devel/ci/integration/tests/test_bodhi.py
@@ -151,6 +151,24 @@ def test_get_api_version(bodhi_container):
         raise
 
 
+def test_get_liveness(bodhi_container):
+    """Test ``/healthz/live`` path"""
+    # GET on /healthz/live
+    # this is standard `requests.Response`
+    http_response = bodhi_container.http_request(path="/healthz/live", port=8080)
+    assert http_response.ok
+    assert http_response.json() == 'ok'
+
+
+def test_get_readyness(bodhi_container):
+    """Test ``/healthz/ready`` path"""
+    # GET on /healthz/ready
+    # this is standard `requests.Response`
+    http_response = bodhi_container.http_request(path="/healthz/ready", port=8080)
+    assert http_response.ok
+    assert http_response.json() == {'db_session': True}
+
+
 def test_get_notfound_view(bodhi_container):
     """Test not_found_view path"""
     # GET on /inexisting_path