Skip to content

Commit c3e99bb

Browse files
authored
fix(cells) Fix ViewSiloLimit when applied to function views (#103949)
Fix parameter handling and request attribute extraction for view functions that have `ViewSiloLimit` applied to them. Fixes SENTRY-5DP1
1 parent 4d39e60 commit c3e99bb

File tree

2 files changed

+57
-3
lines changed

2 files changed

+57
-3
lines changed

src/sentry/web/frontend/base.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from django.utils.decorators import method_decorator
2323
from django.views.decorators.csrf import csrf_exempt
2424
from django.views.generic import View
25-
from rest_framework.request import Request
2625

2726
from sentry import options
2827
from sentry.api.exceptions import DataSecrecyError
@@ -80,10 +79,11 @@ def handle_when_unavailable(
8079
current_mode: SiloMode,
8180
available_modes: Iterable[SiloMode],
8281
) -> Callable[..., Any]:
83-
def handle(obj: Any, request: Request, *args: Any, **kwargs: Any) -> HttpResponse:
82+
def handle(*args: Any, **kwargs: Any) -> HttpResponse:
83+
method, path = self._request_attrs(args, kwargs)
8484
mode_str = ", ".join(str(m) for m in available_modes)
8585
message = (
86-
f"Received {request.method} request at {request.path!r} to server in "
86+
f"Received {method} request at {path!r} to server in "
8787
f"{current_mode} mode. This view is available only in: {mode_str}"
8888
)
8989
if settings.FAIL_ON_UNAVAILABLE_API_CALL:
@@ -94,6 +94,15 @@ def handle(obj: Any, request: Request, *args: Any, **kwargs: Any) -> HttpRespons
9494

9595
return handle
9696

97+
def _request_attrs(self, args: Iterable[Any], kwargs: Mapping[str, Any]) -> tuple[str, str]:
98+
for arg in args:
99+
if isinstance(arg, HttpRequest):
100+
return (arg.method or "unknown", arg.path)
101+
for value in kwargs.values():
102+
if isinstance(value, HttpRequest):
103+
return (value.method or "unknown", value.path)
104+
return ("unknown", "unknown")
105+
97106
def __call__(self, decorated_obj: Any) -> Any:
98107
if isinstance(decorated_obj, type):
99108
if not issubclass(decorated_obj, View):
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import pytest
2+
from rest_framework.response import Response
3+
4+
from sentry.silo.base import SiloMode
5+
from sentry.testutils.cases import APITestCase
6+
from sentry.testutils.silo import assume_test_silo_mode
7+
from sentry.web.frontend.base import BaseView, ViewSiloLimit
8+
9+
10+
class ViewSiloLimitTest(APITestCase):
11+
def _test_active_on(self, endpoint_mode, active_mode, expect_to_be_active):
12+
@ViewSiloLimit(endpoint_mode)
13+
def view_func(request):
14+
pass
15+
16+
@ViewSiloLimit(endpoint_mode)
17+
class DummyView(BaseView):
18+
def get(self, request):
19+
return Response("dummy-view", 200)
20+
21+
view_class_func = DummyView.as_view()
22+
with assume_test_silo_mode(active_mode):
23+
request = self.make_request(method="GET", path="/dummy/")
24+
setattr(request, "subdomain", "acme")
25+
26+
if expect_to_be_active:
27+
view_func(request)
28+
view_class_func(request)
29+
else:
30+
with pytest.raises(ViewSiloLimit.AvailabilityError):
31+
view_func(request)
32+
with pytest.raises(ViewSiloLimit.AvailabilityError):
33+
view_class_func(request)
34+
35+
def test_with_active_mode(self) -> None:
36+
self._test_active_on(SiloMode.REGION, SiloMode.REGION, True)
37+
self._test_active_on(SiloMode.CONTROL, SiloMode.CONTROL, True)
38+
39+
def test_with_inactive_mode(self) -> None:
40+
self._test_active_on(SiloMode.REGION, SiloMode.CONTROL, False)
41+
self._test_active_on(SiloMode.CONTROL, SiloMode.REGION, False)
42+
43+
def test_with_monolith_mode(self) -> None:
44+
self._test_active_on(SiloMode.REGION, SiloMode.MONOLITH, True)
45+
self._test_active_on(SiloMode.CONTROL, SiloMode.MONOLITH, True)

0 commit comments

Comments
 (0)