Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions src/sentry/hybridcloud/rpc/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
_T = TypeVar("_T")

_IS_RPC_METHOD_ATTR = "__is_rpc_method"
_REGION_RESOLUTION_ATTR = "__region_resolution"
_REGION_RESOLUTION_OPTIONAL_RETURN_ATTR = "__region_resolution_optional_return"
_CELL_RESOLUTION_ATTR = "__cell_resolution"
_CELL_RESOLUTION_OPTIONAL_RETURN_ATTR = "__cell_resolution_optional_return"


class RpcException(Exception):
Expand Down Expand Up @@ -98,7 +98,7 @@ def get_name_segments(self) -> Sequence[str]:
return self.service_name, self.method_name

def _extract_cell_resolution(self) -> CellResolutionStrategy | None:
cell_resolution = getattr(self.base_function, _REGION_RESOLUTION_ATTR, None)
cell_resolution = getattr(self.base_function, _CELL_RESOLUTION_ATTR, None)

is_cell_service = self.base_service_cls.local_mode == SiloMode.CELL
if not is_cell_service and cell_resolution is not None:
Expand All @@ -111,28 +111,28 @@ def _extract_cell_resolution(self) -> CellResolutionStrategy | None:

return cell_resolution

def resolve_to_region(self, arguments: ArgumentDict) -> _RegionResolutionResult:
def resolve_to_cell(self, arguments: ArgumentDict) -> _CellResolutionResult:
if self._cell_resolution is None:
raise self._setup_exception("Does not run on the region silo")
raise self._setup_exception("Does not run on the cell silo")

try:
region = self._cell_resolution.resolve(arguments)
return _RegionResolutionResult(region)
cell = self._cell_resolution.resolve(arguments)
return _CellResolutionResult(cell=cell)
except CellMappingNotFound:
if getattr(self.base_function, _REGION_RESOLUTION_OPTIONAL_RETURN_ATTR, False):
return _RegionResolutionResult(None, is_early_halt=True)
if getattr(self.base_function, _CELL_RESOLUTION_OPTIONAL_RETURN_ATTR, False):
return _CellResolutionResult(cell=None, is_early_halt=True)
else:
raise


@dataclass(frozen=True)
class _RegionResolutionResult:
region: Cell | None
class _CellResolutionResult:
cell: Cell | None
is_early_halt: bool = False

def __post_init__(self) -> None:
if (self.region is None) != self.is_early_halt:
raise ValueError("region must be supplied if and only if not halting early")
if (self.cell is None) != self.is_early_halt:
raise ValueError("cell must be supplied if and only if not halting early")


class DelegatingRpcService(DelegatedBySiloMode["RpcService"]):
Expand Down Expand Up @@ -183,18 +183,18 @@ def cell_rpc_method(
) -> Callable[[Callable[..., _T]], Callable[..., _T]]:
"""Decorate methods to be exposed as part of the RPC interface.

In addition, resolves the region based on the resolve callback function.
In addition, resolves the cell based on the resolve callback function.
Should be applied only to methods of an RpcService subclass.

The `return_none_if_mapping_not_found` option indicates that, if we fail to find
a region in which to look for the queried object, the decorated method should
a cell in which to look for the queried object, the decorated method should
return `None` indicating that the queried object does not exist. This should be
set only on methods with an `Optional[...]` return type.
"""

def decorator(method: Callable[..., _T]) -> Callable[..., _T]:
setattr(method, _REGION_RESOLUTION_ATTR, resolve)
setattr(method, _REGION_RESOLUTION_OPTIONAL_RETURN_ATTR, return_none_if_mapping_not_found)
setattr(method, _CELL_RESOLUTION_ATTR, resolve)
setattr(method, _CELL_RESOLUTION_OPTIONAL_RETURN_ATTR, return_none_if_mapping_not_found)
return rpc_method(method)

return decorator
Expand Down Expand Up @@ -343,16 +343,16 @@ def remote_method(service_obj: RpcService, **kwargs: Any) -> Any:
)

if cls.local_mode == SiloMode.CELL:
result = signature.resolve_to_region(kwargs)
result = signature.resolve_to_cell(kwargs)
if result.is_early_halt:
return None
region = result.region
cell = result.cell
else:
region = None
cell = None

serial_arguments = signature.serialize_arguments(kwargs)
return dispatch_remote_call(
region, cls.key, method_name, serial_arguments, use_test_client=use_test_client
cell, cls.key, method_name, serial_arguments, use_test_client=use_test_client
)

return remote_method
Expand Down Expand Up @@ -482,39 +482,39 @@ def result_to_dict(value: Any) -> Any:


def dispatch_remote_call(
region: Cell | None,
cell: Cell | None,
service_name: str,
method_name: str,
serial_arguments: ArgumentDict,
use_test_client: bool = False,
) -> Any:
remote_silo_call = _RemoteSiloCall(region, service_name, method_name, serial_arguments)
remote_silo_call = _RemoteSiloCall(cell, service_name, method_name, serial_arguments)
return remote_silo_call.dispatch(use_test_client)


@dataclass(frozen=True)
class _RemoteSiloCall:
region: Cell | None
cell: Cell | None
service_name: str
method_name: str
serial_arguments: ArgumentDict

@property
def address(self) -> str:
if self.region is None:
if self.cell is None:
if not settings.SENTRY_CONTROL_ADDRESS:
raise RpcServiceSetupException(
self.service_name, self.method_name, "Control silo address is not configured"
)
return settings.SENTRY_CONTROL_ADDRESS
else:
if not self.region.address:
if not self.cell.address:
raise RpcServiceSetupException(
self.service_name,
self.method_name,
f"Address for region {self.region.name!r} is not configured",
f"Address for cell {self.cell.name!r} is not configured",
)
return self.region.address
return self.cell.address

@property
def path(self) -> str:
Expand All @@ -532,7 +532,7 @@ def dispatch(self, use_test_client: bool = False) -> Any:

def _metrics_tags(self, **additional_tags: str | int) -> Mapping[str, str | int | None]:
return dict(
rpc_destination_region=self.region.name if self.region else "control",
rpc_destination_region=self.cell.name if self.cell else "control",
rpc_method=f"{self.service_name}.{self.method_name}",
**additional_tags,
)
Expand Down Expand Up @@ -666,14 +666,14 @@ def _fire_test_request(self, headers: Mapping[str, str], data: bytes) -> Any:
f"remote service method to {self.path} called inside transaction! Move service calls to outside of transactions."
)

if self.region:
if self.cell:
target_mode = SiloMode.CELL
else:
target_mode = SiloMode.CONTROL

with (
SingleProcessSiloModeState.exit(),
SingleProcessSiloModeState.enter(target_mode, self.region),
SingleProcessSiloModeState.enter(target_mode, self.cell),
):
extra: Mapping[str, Any] = {
f"HTTP_{k.replace('-', '_').upper()}": v for k, v in headers.items()
Expand Down
8 changes: 4 additions & 4 deletions tests/sentry/hybridcloud/test_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def test_get_method_timeout(self) -> None:
test_class = _RemoteSiloCall(
service_name="organization_service",
method_name="get_org_by_id",
region=None,
cell=None,
serial_arguments={},
)

Expand All @@ -238,7 +238,7 @@ def test_get_method_timeout(self) -> None:
test_class = _RemoteSiloCall(
service_name="organization_service",
method_name="get_org_by_id",
region=None,
cell=None,
serial_arguments={},
)

Expand Down Expand Up @@ -275,7 +275,7 @@ def test_get_method_retry_count(self) -> None:
test_class = _RemoteSiloCall(
service_name="organization_service",
method_name="get_org_by_id",
region=None,
cell=None,
serial_arguments={},
)

Expand All @@ -290,7 +290,7 @@ def test_get_method_retry_count(self) -> None:
test_class = _RemoteSiloCall(
service_name="organization_service",
method_name="get_org_by_id",
region=None,
cell=None,
serial_arguments={},
)

Expand Down
Loading