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
3 changes: 1 addition & 2 deletions src/sentry/hybridcloud/rpc/caching/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ class LocalCellCachingService(CellCachingService):
def clear_key(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
Comment thread
lynnagara marked this conversation as resolved.
key: str,
) -> int:
return _consume_generator(_delete_cache(key, SiloMode.CELL))
Expand Down
3 changes: 1 addition & 2 deletions src/sentry/hybridcloud/rpc/caching/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ def get_local_implementation(cls) -> RpcService:
def clear_key(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
Comment thread
lynnagara marked this conversation as resolved.
cell_name: str,
key: str,
) -> int:
pass
Comment on lines 33 to 39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The clear_key method signature changed, but several callers were not updated, which will cause a TypeError at runtime.
Severity: HIGH

Suggested Fix

Update all call sites of CellCachingService.clear_key() to remove the region_name keyword argument, aligning them with the new method signature.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sentry/hybridcloud/rpc/caching/service.py#L33-L39

Potential issue: The `clear_key()` method in `CellCachingService` was updated to remove
the `region_name` parameter from its signature. However, multiple callers of this method
were not updated to reflect this change. Specifically, code paths related to user async
deletion/replication and Sentry App installation replication still attempt to call
`clear_key()` with the `region_name` keyword argument. This will cause a `TypeError` at
runtime when these operations are performed, leading to failures in these background
tasks.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines 33 to 39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The clear_key method signature changed, but its callers were not updated. They still pass the removed region_name parameter, which will cause a TypeError.
Severity: HIGH

Suggested Fix

Update all call sites of cell_caching_service.clear_key() to stop passing the region_name argument and instead pass the required cell_name argument. For example, change calls from clear_key(..., region_name=cell_name) to clear_key(..., cell_name=cell_name).

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sentry/hybridcloud/rpc/caching/service.py#L33-L39

Potential issue: The `clear_key` method in `CellCachingService` was refactored to remove
the `region_name` parameter and make `cell_name` a required keyword argument. However,
multiple callers in `user.py`, `sentry_app_installation.py`, and `sentry_apps.py` were
not updated to match this new signature. These call sites still attempt to pass
`region_name`, which will result in a `TypeError: clear_key() got an unexpected keyword
argument 'region_name'` at runtime. This will break cache invalidation logic for users
and Sentry App installations during async deletion, replication, and other tasks.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,13 @@ def _get_slug_reservation_by_type_from_list(
def provision_organization(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
org_provision_args: OrganizationProvisioningOptions,
) -> RpcOrganizationSlugReservation:
resolved_cell_name = cell_name or region_name
assert resolved_cell_name is not None, "cell_name or region_name must be provided"

# Generate a new non-conflicting slug and org ID
org_id = self._generate_org_snowflake_id(region_name=resolved_cell_name)
org_id = self._generate_org_snowflake_id(region_name=cell_name)
slug = self._generate_org_slug(
region_name=resolved_cell_name, slug=org_provision_args.provision_options.slug
region_name=cell_name, slug=org_provision_args.provision_options.slug
)

# Generate a provisioning outbox for the region and drain
Expand All @@ -158,13 +154,13 @@ def provision_organization(
slug=slug,
organization_id=org_id,
user_id=org_provision_args.provision_options.owning_user_id,
cell_name=resolved_cell_name,
cell_name=cell_name,
)

org_slug_res.save(unsafe_write=True)
create_organization_provisioning_outbox(
organization_id=org_id,
cell_name=resolved_cell_name,
cell_name=cell_name,
org_provision_payload=provision_payload,
).save()

Expand All @@ -183,14 +179,11 @@ def provision_organization(
def update_organization_slug(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
organization_id: int,
desired_slug: str,
require_exact: bool = True,
) -> RpcOrganizationSlugReservation:
resolved_cell_name = cell_name or region_name
assert resolved_cell_name is not None, "cell_name or region_name must be provided"
existing_slug_reservations = list(
OrganizationSlugReservation.objects.filter(organization_id=organization_id)
)
Expand All @@ -209,14 +202,14 @@ def update_organization_slug(
)

# If there's already a matching primary slug reservation for the org,
# just replicate it to the region to kick off the organization sync process
# just replicate it to the cell to kick off the organization sync process
if existing_primary_alias and existing_primary_alias.slug == desired_slug:
existing_primary_alias.handle_async_replication(resolved_cell_name, organization_id)
existing_primary_alias.handle_async_replication(cell_name, organization_id)
return serialize_slug_reservation(existing_primary_alias)

slug_base = desired_slug
if not require_exact:
slug_base = self._generate_org_slug(region_name=resolved_cell_name, slug=slug_base)
slug_base = self._generate_org_slug(region_name=cell_name, slug=slug_base)

try:
with outbox_context(
Expand All @@ -226,7 +219,7 @@ def update_organization_slug(
slug=slug_base,
organization_id=organization_id,
user_id=-1,
cell_name=resolved_cell_name,
cell_name=cell_name,
reservation_type=OrganizationSlugReservationType.TEMPORARY_RENAME_ALIAS.value,
).save(unsafe_write=True)

Expand Down Expand Up @@ -295,23 +288,19 @@ def _validate_primary_slug_updated(
def bulk_create_organization_slug_reservations(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
slug_mapping: dict[int, str],
) -> None:
resolved_cell_name = cell_name or region_name
assert resolved_cell_name is not None, "cell_name or region_name must be provided"

slug_reservations_to_create: list[OrganizationSlugReservation] = []

with outbox_context(transaction.atomic(router.db_for_write(OrganizationSlugReservation))):
for org_id, slug in slug_mapping.items():
slug_reservation = OrganizationSlugReservation(
slug=self._generate_org_slug(slug=slug, region_name=resolved_cell_name),
slug=self._generate_org_slug(slug=slug, region_name=cell_name),
organization_id=org_id,
reservation_type=OrganizationSlugReservationType.TEMPORARY_RENAME_ALIAS.value,
user_id=-1,
cell_name=resolved_cell_name,
cell_name=cell_name,
)
slug_reservation.save(unsafe_write=True)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ class ControlOrganizationProvisioningRpcService(RpcService):
def provision_organization(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
org_provision_args: OrganizationProvisioningOptions,
) -> RpcOrganizationSlugReservation:
"""
Expand All @@ -39,8 +38,7 @@ def provision_organization(
def update_organization_slug(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
organization_id: int,
desired_slug: str,
require_exact: bool = True,
Expand All @@ -66,8 +64,7 @@ def update_organization_slug(
def bulk_create_organization_slug_reservations(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
slug_mapping: dict[int, str],
) -> None:
"""
Expand Down
33 changes: 18 additions & 15 deletions src/sentry/hybridcloud/services/replica/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ def upsert_replicated_api_token(
self,
*,
api_token: RpcApiToken,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
organization: Organization | None = None
if api_token.organization_id is not None:
Expand All @@ -179,7 +178,10 @@ def upsert_replicated_api_token(
handle_replication(ApiToken, destination)

def delete_replicated_api_token(
self, *, apitoken_id: int, cell_name: str | None = None, region_name: str | None = None
self,
*,
apitoken_id: int,
cell_name: str,
) -> None:
with enforce_constraints(transaction.atomic(router.db_for_write(ApiTokenReplica))):
api_token_qs = ApiTokenReplica.objects.filter(apitoken_id=apitoken_id)
Expand All @@ -189,8 +191,7 @@ def upsert_replicated_org_auth_token(
self,
*,
token: RpcOrgAuthToken,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
try:
organization = Organization.objects.get(id=token.organization_id)
Expand All @@ -212,8 +213,7 @@ def upsert_replicated_auth_provider(
self,
*,
auth_provider: RpcAuthProvider,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
try:
organization = Organization.objects.get(id=auth_provider.organization_id)
Expand All @@ -237,8 +237,7 @@ def upsert_replicated_auth_identity(
self,
*,
auth_identity: RpcAuthIdentity,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
destination = AuthIdentityReplica(
auth_identity_id=auth_identity.id,
Expand All @@ -252,7 +251,10 @@ def upsert_replicated_auth_identity(
handle_replication(AuthIdentity, destination)

def upsert_replicated_api_key(
self, *, api_key: RpcApiKey, cell_name: str | None = None, region_name: str | None = None
self,
*,
api_key: RpcApiKey,
cell_name: str,
) -> None:
try:
organization = Organization.objects.get(id=api_key.organization_id)
Expand All @@ -275,8 +277,7 @@ def upsert_replicated_org_slug_reservation(
self,
*,
slug_reservation: RpcOrganizationSlugReservation,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
with enforce_constraints(
transaction.atomic(router.db_for_write(OrganizationSlugReservationReplica))
Expand Down Expand Up @@ -304,8 +305,7 @@ def delete_replicated_org_slug_reservation(
self,
*,
organization_slug_reservation_id: int,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
with enforce_constraints(
transaction.atomic(router.db_for_write(OrganizationSlugReservationReplica))
Expand All @@ -316,7 +316,10 @@ def delete_replicated_org_slug_reservation(
org_slug_qs.delete()

def delete_replicated_auth_provider(
self, *, auth_provider_id: int, cell_name: str | None = None, region_name: str | None = None
self,
*,
auth_provider_id: int,
cell_name: str,
) -> None:
with enforce_constraints(transaction.atomic(router.db_for_write(AuthProviderReplica))):
AuthProviderReplica.objects.filter(auth_provider_id=auth_provider_id).delete()
Expand Down
33 changes: 18 additions & 15 deletions src/sentry/hybridcloud/services/replica/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ def upsert_replicated_auth_provider(
self,
*,
auth_provider: RpcAuthProvider,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
pass

Expand All @@ -81,15 +80,17 @@ def upsert_replicated_auth_identity(
self,
*,
auth_identity: RpcAuthIdentity,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
pass

@cell_rpc_method(resolve=ByCellName())
@abc.abstractmethod
def upsert_replicated_api_key(
self, *, api_key: RpcApiKey, cell_name: str | None = None, region_name: str | None = None
self,
*,
api_key: RpcApiKey,
cell_name: str,
) -> None:
pass

Expand All @@ -99,15 +100,17 @@ def upsert_replicated_api_token(
self,
*,
api_token: RpcApiToken,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
pass

@cell_rpc_method(resolve=ByCellName())
@abc.abstractmethod
def delete_replicated_api_token(
self, *, apitoken_id: int, cell_name: str | None = None, region_name: str | None = None
self,
*,
apitoken_id: int,
cell_name: str,
) -> None:
pass

Expand All @@ -117,8 +120,7 @@ def upsert_replicated_org_auth_token(
self,
*,
token: RpcOrgAuthToken,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
pass

Expand All @@ -128,8 +130,7 @@ def upsert_replicated_org_slug_reservation(
self,
*,
slug_reservation: RpcOrganizationSlugReservation,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
pass

Expand All @@ -139,15 +140,17 @@ def delete_replicated_org_slug_reservation(
self,
*,
organization_slug_reservation_id: int,
cell_name: str | None = None,
region_name: str | None = None,
cell_name: str,
) -> None:
pass

@cell_rpc_method(resolve=ByCellName())
@abc.abstractmethod
def delete_replicated_auth_provider(
self, *, auth_provider_id: int, cell_name: str | None = None, region_name: str | None = None
self,
*,
auth_provider_id: int,
cell_name: str,
) -> None:
pass

Expand Down
3 changes: 1 addition & 2 deletions src/sentry/issues/services/issue/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ class DatabaseBackedIssueService(IssueService):
def get_external_issue_groups(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
external_issue_key: str,
integration_id: int,
) -> list[RpcExternalIssueGroupMetadata] | None:
Expand Down
3 changes: 1 addition & 2 deletions src/sentry/issues/services/issue/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ def get_local_implementation(cls) -> RpcService:
def get_external_issue_groups(
self,
*,
cell_name: str | None = None, # TODO(cells): make required when all callers are updated
region_name: str | None = None, # TODO(cells): remove when all callers are updated
cell_name: str,
external_issue_key: str,
integration_id: int,
) -> list[RpcExternalIssueGroupMetadata] | None:
Expand Down
4 changes: 0 additions & 4 deletions src/sentry/organizations/services/organization/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,10 +650,6 @@ def update_cell_user(
user_is_active=user.is_active, user_email=user.email
)

# TODO(cells): Remove when callers updated
def update_region_user(self, *, user: RpcCellUser, cell_name: str) -> None:
return self.update_cell_user(user=user, cell_name=cell_name)

def get_option(self, *, organization_id: int, key: str) -> OptionValue:
orm_organization = Organization.objects.get_from_cache(id=organization_id)
value = orm_organization.get_option(key)
Expand Down
16 changes: 0 additions & 16 deletions src/sentry/organizations/services/organization/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,22 +498,6 @@ def update_cell_user(
"""
pass

# TODO(cells): Remove when callers updated
@cell_rpc_method(resolve=ByCellName())
@abstractmethod
def update_region_user(
self,
*,
user: RpcCellUser,
cell_name: str,
) -> None:
"""
Update all memberships in a cell to reflect changes in user details.

Will sync is_active and email attributes.
"""
pass

@cell_rpc_method(resolve=ByOrganizationId())
@abstractmethod
def reset_idp_flags(self, *, organization_id: int) -> None:
Expand Down
Loading
Loading