From da99b8b034a0566548cf9691df8e09a6efe1689b Mon Sep 17 00:00:00 2001 From: McKenna Jones Date: Fri, 20 Jan 2023 14:26:58 -0700 Subject: [PATCH] Add Health Check endpoint (#392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add simple health endpoint * add curl and healthcheck to dockerfile * convert to view * add simple test * Add unhealthy test * Cleanup * check for LD_SERVER_PORT env var in healthcheck def * Revert changes to middlewares.py Co-authored-by: Sascha Ißbrücker --- Dockerfile | 6 ++++- bookmarks/tests/test_health_view.py | 36 +++++++++++++++++++++++++++++ bookmarks/urls.py | 2 ++ bookmarks/views/__init__.py | 1 + bookmarks/views/health.py | 20 ++++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 bookmarks/tests/test_health_view.py create mode 100644 bookmarks/views/health.py diff --git a/Dockerfile b/Dockerfile index e99dd27..74e1c30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,7 @@ RUN mkdir /opt/venv && \ FROM python:3.10.6-slim-buster as final -RUN apt-get update && apt-get -y install mime-support libpq-dev +RUN apt-get update && apt-get -y install mime-support libpq-dev curl WORKDIR /etc/linkding # copy prod dependencies COPY --from=prod-deps /opt/venv /opt/venv @@ -51,4 +51,8 @@ ENV PATH /opt/venv/bin:$PATH RUN ["chmod", "g+w", "."] # Run bootstrap logic RUN ["chmod", "+x", "./bootstrap.sh"] + +HEALTHCHECK --interval=30s --retries=3 --timeout=1s \ +CMD curl -f http://localhost:${LD_SERVER_PORT:-9090}/health || exit 1 + CMD ["./bootstrap.sh"] diff --git a/bookmarks/tests/test_health_view.py b/bookmarks/tests/test_health_view.py new file mode 100644 index 0000000..a43f79d --- /dev/null +++ b/bookmarks/tests/test_health_view.py @@ -0,0 +1,36 @@ +from unittest.mock import patch + +from django.db import connections +from django.test import TestCase + +from bookmarks.views.settings import app_version + + +class HealthViewTestCase(TestCase): + + def test_health_healthy(self): + response = self.client.get("/health") + + self.assertEqual(response.status_code, 200) + + response_body = response.json() + expected_body = { + 'version': app_version, + 'status': 'healthy' + } + self.assertDictEqual(response_body, expected_body) + + def test_health_unhealhty(self): + with patch.object(connections['default'], 'ensure_connection') as mock_ensure_connection: + mock_ensure_connection.side_effect = Exception('Connection error') + + response = self.client.get("/health") + + self.assertEqual(response.status_code, 500) + + response_body = response.json() + expected_body = { + 'version': app_version, + 'status': 'unhealthy' + } + self.assertDictEqual(response_body, expected_body) diff --git a/bookmarks/urls.py b/bookmarks/urls.py index cdb5016..1c2387d 100644 --- a/bookmarks/urls.py +++ b/bookmarks/urls.py @@ -31,4 +31,6 @@ urlpatterns = [ # Feeds path('feeds//all', AllBookmarksFeed(), name='feeds.all'), path('feeds//unread', UnreadBookmarksFeed(), name='feeds.unread'), + # Health check + path('health', views.health, name='health') ] diff --git a/bookmarks/views/__init__.py b/bookmarks/views/__init__.py index 84f3f08..948a4ad 100644 --- a/bookmarks/views/__init__.py +++ b/bookmarks/views/__init__.py @@ -1,3 +1,4 @@ from .bookmarks import * from .settings import * from .toasts import * +from .health import health diff --git a/bookmarks/views/health.py b/bookmarks/views/health.py new file mode 100644 index 0000000..38923f1 --- /dev/null +++ b/bookmarks/views/health.py @@ -0,0 +1,20 @@ +from django.db import connections +from django.http import JsonResponse + +from bookmarks.views.settings import app_version + + +def health(request): + code = 200 + response = { + 'version': app_version, + 'status': 'healthy' + } + + try: + connections['default'].ensure_connection() + except Exception: + response['status'] = 'unhealthy' + code = 500 + + return JsonResponse(response, status=code)