Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 316815e

Browse files
Add ES healthchecks to /healthcheck/ endpoint (#1047)
* Add api_client fixture to conftest * Add ES check to healthcheck endpoint * Fix timeout format
1 parent afcd7b8 commit 316815e

File tree

5 files changed

+85
-16
lines changed

5 files changed

+85
-16
lines changed
Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
from django.conf import settings
2+
from rest_framework import status
3+
from rest_framework.exceptions import APIException
4+
from rest_framework.request import Request
15
from rest_framework.response import Response
26
from rest_framework.views import APIView
37

48

9+
class ElasticsearchHealthcheckException(APIException):
10+
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
11+
12+
513
class HealthCheck(APIView):
614
"""
7-
Returns a "200 OK" response if the server is running normally.
15+
Returns a "200 OK" response if the server is running normally. Returns 503
16+
otherwise.
817
918
This endpoint is used in production to ensure that the server should receive
1019
traffic. If no response is provided, the server is deregistered from the
@@ -13,5 +22,20 @@ class HealthCheck(APIView):
1322

1423
swagger_schema = None
1524

16-
def get(self, request, format=None):
25+
def _check_es(self) -> Response | None:
26+
"""
27+
Checks Elasticsearch cluster health. Raises an exception if ES is not healthy.
28+
"""
29+
es_health = settings.ES.cluster.health(timeout="5s")
30+
31+
if es_health["timed_out"]:
32+
raise ElasticsearchHealthcheckException("es_timed_out")
33+
34+
if (status := es_health["status"]) != "green":
35+
raise ElasticsearchHealthcheckException(f"es_status_{status}")
36+
37+
def get(self, request: Request):
38+
if "check_es" in request.query_params:
39+
self._check_es()
40+
1741
return Response({"status": "200 OK"}, status=200)

api/test/unit/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from rest_framework.test import APIClient
2+
3+
import pytest
4+
5+
6+
@pytest.fixture
7+
def api_client():
8+
return APIClient()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pook
2+
import pytest
3+
4+
5+
def mock_health_response(status="green", timed_out=False):
6+
return (
7+
pook.get(pook.regex(r"_cluster\/health"))
8+
.times(1)
9+
.reply(200)
10+
.json(
11+
{
12+
"status": status if not timed_out else None,
13+
"timed_out": timed_out,
14+
}
15+
)
16+
)
17+
18+
19+
def test_health_check_plain(api_client):
20+
res = api_client.get("/healthcheck/")
21+
assert res.status_code == 200
22+
23+
24+
def test_health_check_es_timed_out(api_client):
25+
mock_health_response(timed_out=True)
26+
pook.on()
27+
res = api_client.get("/healthcheck/", data={"check_es": True})
28+
pook.off()
29+
30+
assert res.status_code == 503
31+
assert res.json()["detail"] == "es_timed_out"
32+
33+
34+
@pytest.mark.parametrize("status", ("yellow", "red"))
35+
def test_health_check_es_status_bad(status, api_client):
36+
mock_health_response(status=status)
37+
pook.on()
38+
res = api_client.get("/healthcheck/", data={"check_es": True})
39+
pook.off()
40+
41+
assert res.status_code == 503
42+
assert res.json()["detail"] == f"es_status_{status}"
43+
44+
45+
def test_health_check_es_all_good(api_client):
46+
mock_health_response(status="green")
47+
pook.on()
48+
res = api_client.get("/healthcheck/", data={"check_es": True})
49+
pook.off()
50+
51+
assert res.status_code == 200

api/test/unit/views/image_views_test.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from pathlib import Path
55
from test.factory.models.image import ImageFactory
66

7-
from rest_framework.test import APIClient
8-
97
import pytest
108
from requests import Request, Response
119

@@ -17,11 +15,6 @@
1715
_MOCK_IMAGE_INFO = json.loads((_MOCK_IMAGE_PATH / "sample-image-info.json").read_text())
1816

1917

20-
@pytest.fixture
21-
def api_client():
22-
return APIClient()
23-
24-
2518
@dataclass
2619
class RequestsFixture:
2720
requests: list[Request]

api/test/unit/views/media_views_test.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from unittest import mock
88
from unittest.mock import MagicMock, patch
99

10-
from rest_framework.test import APIClient
11-
1210
import pytest
1311
import pytest_django.asserts
1412
import requests as requests_lib
@@ -23,11 +21,6 @@
2321
_MOCK_IMAGE_INFO = json.loads((_MOCK_IMAGE_PATH / "sample-image-info.json").read_text())
2422

2523

26-
@pytest.fixture
27-
def api_client():
28-
return APIClient()
29-
30-
3124
@dataclass
3225
class SentRequest:
3326
request: PreparedRequest

0 commit comments

Comments
 (0)