Skip to content

Commit efe133e

Browse files
dklibanclaude
andcommitted
Decouple /livez endpoint from the database
DomainMiddleware.process_view() runs Domain.objects.get() on every request, including /livez/. This causes the liveness probe to fail when the database is unavailable, triggering cascading pod restarts. Add a skip_domain_middleware flag to LivezView and honour it in DomainMiddleware so the probe depends only on the gunicorn worker being alive. Fixes #7721 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e2c3f4d commit efe133e

4 files changed

Lines changed: 42 additions & 0 deletions

File tree

CHANGES/7721.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Decoupled the `/livez` liveness probe from the database so it returns 200 even when the database is unavailable.

pulpcore/app/views/status.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ class LivezView(APIView):
145145
# allow anyone to access the liveness api
146146
authentication_classes = []
147147
permission_classes = []
148+
# Skip domain DB lookup so this probe stays independent of the database
149+
skip_domain_middleware = True
148150

149151
@extend_schema(
150152
summary="Inspect liveness of Pulp's REST API.",

pulpcore/middleware.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def __call__(self, request):
3838

3939
def process_view(self, request, view_func, view_args, view_kwargs):
4040
"""Remove the domain name if present, called right before view_func is called."""
41+
view_class = getattr(view_func, "view_class", None) or getattr(view_func, "cls", None)
42+
if view_class and getattr(view_class, "skip_domain_middleware", False):
43+
view_kwargs.pop("pulp_domain", None)
44+
return None
4145
domain_name = view_kwargs.pop("pulp_domain", "default")
4246
try:
4347
domain = Domain.objects.get(name=domain_name)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
from django.test import TestCase
4+
5+
from pulpcore.middleware import DomainMiddleware
6+
from pulpcore.plugin.find_url import find_api_root
7+
8+
9+
class TestLivezNoDatabaseAccess(TestCase):
10+
"""Test that GET /livez/ makes zero database queries."""
11+
12+
def test_livez_makes_no_database_queries(self):
13+
_, api_v3_path = find_api_root(set_domain=False, rewrite_header=False)
14+
with self.assertNumQueries(0):
15+
response = self.client.get(f"{api_v3_path}livez/")
16+
self.assertEqual(response.status_code, 200)
17+
18+
19+
class TestDomainMiddlewareSkip(TestCase):
20+
"""Test that DomainMiddleware skips DB lookup for views with skip_domain_middleware=True."""
21+
22+
def setUp(self):
23+
self.middleware = DomainMiddleware(get_response=MagicMock())
24+
self.request = MagicMock()
25+
26+
@patch("pulpcore.middleware.Domain.objects")
27+
@patch("pulpcore.middleware.set_domain")
28+
def test_does_db_lookup_when_flag_not_set(self, mock_set_domain, mock_domain_objects):
29+
view_class = type("NormalView", (), {})
30+
view_func = MagicMock(view_class=view_class)
31+
view_kwargs = {"pulp_domain": "default"}
32+
33+
self.middleware.process_view(self.request, view_func, [], view_kwargs)
34+
35+
mock_domain_objects.get.assert_called_once_with(name="default")

0 commit comments

Comments
 (0)