diff --git a/src/sentry/api/endpoints/api_application_details.py b/src/sentry/api/endpoints/api_application_details.py index 6eae213e37d30c..73f6dadeeba959 100644 --- a/src/sentry/api/endpoints/api_application_details.py +++ b/src/sentry/api/endpoints/api_application_details.py @@ -10,8 +10,8 @@ from sentry.api.base import Endpoint, control_silo_endpoint from sentry.api.exceptions import ResourceDoesNotExist from sentry.api.serializers import serialize +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus -from sentry.models.scheduledeletion import ScheduledDeletion class ApiApplicationSerializer(serializers.Serializer): diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index fcfafbbf5c2821..caf997084335ff 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -65,6 +65,7 @@ UPTIME_AUTODETECTION, ) from sentry.datascrubbing import validate_pii_config_update, validate_pii_selectors +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.hybridcloud.rpc import IDEMPOTENCY_KEY_LENGTH from sentry.integrations.utils.codecov import has_codecov_integration from sentry.lang.native.utils import ( @@ -75,7 +76,6 @@ from sentry.models.avatars.organization_avatar import OrganizationAvatar from sentry.models.options.organization_option import OrganizationOption from sentry.models.organization import Organization, OrganizationStatus -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.organizations.services.organization import organization_service from sentry.organizations.services.organization.model import ( RpcOrganization, diff --git a/src/sentry/api/endpoints/project_details.py b/src/sentry/api/endpoints/project_details.py index ba3c4fca0fee5b..8643183113771c 100644 --- a/src/sentry/api/endpoints/project_details.py +++ b/src/sentry/api/endpoints/project_details.py @@ -30,6 +30,7 @@ from sentry.apidocs.parameters import GlobalParams from sentry.constants import RESERVED_PROJECT_SLUGS, ObjectStatus from sentry.datascrubbing import validate_pii_config_update, validate_pii_selectors +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.dynamic_sampling import get_supported_biases_ids, get_user_biases from sentry.grouping.enhancer import Enhancements from sentry.grouping.enhancer.exceptions import InvalidEnhancerConfig @@ -47,7 +48,6 @@ from sentry.models.project import Project from sentry.models.projectbookmark import ProjectBookmark from sentry.models.projectredirect import ProjectRedirect -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.notifications.utils import has_alert_integration from sentry.tasks.delete_seer_grouping_records import call_seer_delete_project_grouping_records diff --git a/src/sentry/api/endpoints/project_rule_details.py b/src/sentry/api/endpoints/project_rule_details.py index 9d655541784a86..009248ae51b0ec 100644 --- a/src/sentry/api/endpoints/project_rule_details.py +++ b/src/sentry/api/endpoints/project_rule_details.py @@ -26,13 +26,13 @@ from sentry.apidocs.examples.issue_alert_examples import IssueAlertExamples from sentry.apidocs.parameters import GlobalParams, IssueAlertParams from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.integrations.jira.actions.create_ticket import JiraCreateTicketAction from sentry.integrations.jira_server.actions.create_ticket import JiraServerCreateTicketAction from sentry.integrations.slack.tasks.find_channel_id_for_rule import find_channel_id_for_rule from sentry.integrations.slack.utils.rule_status import RedisRuleStatus from sentry.mediators.project_rules.updater import Updater from sentry.models.rule import NeglectedRule, RuleActivity, RuleActivityType -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.rules.actions import trigger_sentry_app_action_creators_for_issues from sentry.rules.actions.utils import get_changed_data, get_updated_rule_data from sentry.signals import alert_rule_edited diff --git a/src/sentry/api/endpoints/team_details.py b/src/sentry/api/endpoints/team_details.py index 87d276d1bb2795..69ec7a73b26cdb 100644 --- a/src/sentry/api/endpoints/team_details.py +++ b/src/sentry/api/endpoints/team_details.py @@ -23,7 +23,7 @@ ) from sentry.apidocs.examples.team_examples import TeamExamples from sentry.apidocs.parameters import GlobalParams, TeamParams -from sentry.models.scheduledeletion import RegionScheduledDeletion +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.team import Team, TeamStatus diff --git a/src/sentry/deletions/models/__init__.py b/src/sentry/deletions/models/__init__.py new file mode 100644 index 00000000000000..b0db41538d9c5f --- /dev/null +++ b/src/sentry/deletions/models/__init__.py @@ -0,0 +1,11 @@ +from sentry.deletions.models.scheduleddeletion import ( + RegionScheduledDeletion, + ScheduledDeletion, + get_regional_scheduled_deletion, +) + +__all__ = ( + "get_regional_scheduled_deletion", + "ScheduledDeletion", + "RegionScheduledDeletion", +) diff --git a/src/sentry/deletions/models/scheduleddeletion.py b/src/sentry/deletions/models/scheduleddeletion.py new file mode 100644 index 00000000000000..318ba96a29491d --- /dev/null +++ b/src/sentry/deletions/models/scheduleddeletion.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +import logging +from datetime import datetime, timedelta +from typing import Any, Self +from uuid import uuid4 + +from django.apps import apps +from django.db import models +from django.utils import timezone + +from sentry.backup.scopes import RelocationScope +from sentry.db.models import ( + BoundedBigIntegerField, + JSONField, + Model, + control_silo_model, + region_silo_model, +) +from sentry.silo.base import SiloLimit, SiloMode +from sentry.users.services.user import RpcUser +from sentry.users.services.user.service import user_service + +delete_logger = logging.getLogger("sentry.deletions.api") + + +def default_guid() -> str: + return uuid4().hex + + +def default_date_schedule() -> datetime: + return timezone.now() + timedelta(days=30) + + +class BaseScheduledDeletion(Model): + """ + ScheduledDeletions are, well, relations to arbitrary records in a particular silo that are due for deletion by + the tasks/deletion/scheduled.py job in the future. They are cancellable, and provide automatic, batched cascade + in an async way for performance reasons. + + Note that BOTH region AND control silos need to be able to schedule deletions of different records that will be + reconciled in different places. For that reason, the ScheduledDeletion model is split into two identical models + representing this split. Use the corresponding ScheduledDeletion based on the silo of the model being scheduled + for deletion. + """ + + class Meta: + abstract = True + + __relocation_scope__ = RelocationScope.Excluded + + guid = models.CharField(max_length=32, unique=True, default=default_guid) + app_label = models.CharField(max_length=64) + model_name = models.CharField(max_length=64) + object_id = BoundedBigIntegerField() + date_added = models.DateTimeField(default=timezone.now) + date_scheduled = models.DateTimeField(default=default_date_schedule) + actor_id = BoundedBigIntegerField(null=True) + data = JSONField(default={}) + in_progress = models.BooleanField(default=False) + + @classmethod + def schedule( + cls, instance: Model, days: int = 30, hours: int = 0, data: Any = None, actor: Any = None + ) -> Self: + model = type(instance) + silo_mode = SiloMode.get_current_mode() + model_silo = getattr(model._meta, "silo_limit", None) + assert ( + model_silo + ), "model._meta.silo_limit undefined. This model cannot be used with deletions" + if silo_mode not in model_silo.modes and silo_mode != SiloMode.MONOLITH: + # Pre-empt the fact that our silo protections wouldn't fire for mismatched model <-> silo deletion objects. + raise SiloLimit.AvailabilityError( + f"{model!r} was scheduled for deletion by {cls!r}, but is unavailable in {silo_mode!r}" + ) + + model_name = model.__name__ + record, created = cls.objects.create_or_update( + app_label=instance._meta.app_label, + model_name=model_name, + object_id=instance.pk, + values={ + "date_scheduled": timezone.now() + timedelta(days=days, hours=hours), + "data": data or {}, + "actor_id": actor.id if actor else None, + }, + ) + if not created: + record = cls.objects.get( + app_label=instance._meta.app_label, + model_name=model_name, + object_id=instance.pk, + ) + + delete_logger.info( + "object.delete.queued", + extra={ + "object_id": instance.id, + "transaction_id": record.guid, + "model": type(instance).__name__, + }, + ) + return record + + @classmethod + def cancel(cls, instance: Model): + model_name = type(instance).__name__ + try: + deletion = cls.objects.get( + model_name=model_name, object_id=instance.pk, in_progress=False + ) + except cls.DoesNotExist: + delete_logger.info( + "object.delete.canceled.failed", + extra={"object_id": instance.pk, "model": model_name}, + ) + return + + deletion.delete() + delete_logger.info( + "object.delete.canceled", + extra={"object_id": instance.pk, "model": model_name}, + ) + + def get_model(self) -> type[Any]: + return apps.get_model(self.app_label, self.model_name) + + def get_instance(self) -> Model: + from sentry import deletions + from sentry.deletions.base import ModelDeletionTask + + model = self.get_model() + deletion_task = deletions.get(model=model, query=None) + query_manager = model.objects + if isinstance(deletion_task, ModelDeletionTask): + query_manager = getattr(model, deletion_task.manager_name) + return query_manager.get(pk=self.object_id) + + def get_actor(self) -> RpcUser | None: + if not self.actor_id: + return None + + return user_service.get_user(user_id=self.actor_id) + + +@control_silo_model +class ScheduledDeletion(BaseScheduledDeletion): + """ + This model schedules deletions to be processed in control and monolith silo modes. All historic schedule deletions + occur in this table. In the future, when RegionScheduledDeletions have proliferated for the appropriate models, + we will allow any region models scheduled in this table to finish processing before ensuring that all models discretely + process in either this table or the region table. + """ + + class Meta: + unique_together = (("app_label", "model_name", "object_id"),) + app_label = "sentry" + db_table = "sentry_scheduleddeletion" + + +@region_silo_model +class RegionScheduledDeletion(BaseScheduledDeletion): + """ + This model schedules deletions to be processed in region and monolith silo modes. As new region silo test coverage + increases, new scheduled deletions will begin to occur in this table. Monolith (current saas) will continue + processing them alongside the original scheduleddeletions table, but in the future this table will only be + processed by region silos. + """ + + class Meta: + unique_together = (("app_label", "model_name", "object_id"),) + app_label = "sentry" + db_table = "sentry_regionscheduleddeletion" + + +def get_regional_scheduled_deletion(mode: SiloMode) -> type[BaseScheduledDeletion]: + if mode != SiloMode.CONTROL: + return RegionScheduledDeletion + return ScheduledDeletion diff --git a/src/sentry/deletions/tasks/scheduled.py b/src/sentry/deletions/tasks/scheduled.py index e0ae8daa4f6f10..2f8cfab6a3f1a8 100644 --- a/src/sentry/deletions/tasks/scheduled.py +++ b/src/sentry/deletions/tasks/scheduled.py @@ -8,12 +8,12 @@ from django.db import router, transaction from django.utils import timezone -from sentry.exceptions import DeleteAborted -from sentry.models.scheduledeletion import ( +from sentry.deletions.models.scheduleddeletion import ( BaseScheduledDeletion, RegionScheduledDeletion, ScheduledDeletion, ) +from sentry.exceptions import DeleteAborted from sentry.signals import pending_delete from sentry.silo.base import SiloMode from sentry.tasks.base import instrumented_task, retry diff --git a/src/sentry/incidents/logic.py b/src/sentry/incidents/logic.py index f09b8fbdb11453..5c27bd423efab0 100644 --- a/src/sentry/incidents/logic.py +++ b/src/sentry/incidents/logic.py @@ -24,6 +24,7 @@ from sentry.auth.access import SystemAccess from sentry.constants import CRASH_RATE_ALERT_AGGREGATE_ALIAS, ObjectStatus from sentry.db.models import Model +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.incidents import tasks from sentry.incidents.models.alert_rule import ( AlertRule, @@ -62,7 +63,6 @@ from sentry.models.notificationaction import ActionService, ActionTarget from sentry.models.organization import Organization from sentry.models.project import Project -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.relay.config.metric_extraction import on_demand_metrics_feature_flags from sentry.search.events.builder.base import BaseQueryBuilder from sentry.search.events.constants import ( diff --git a/src/sentry/integrations/api/endpoints/organization_integration_details.py b/src/sentry/integrations/api/endpoints/organization_integration_details.py index 1b7ea1294f93dd..830316dd51285b 100644 --- a/src/sentry/integrations/api/endpoints/organization_integration_details.py +++ b/src/sentry/integrations/api/endpoints/organization_integration_details.py @@ -16,12 +16,12 @@ from sentry.api.base import control_silo_endpoint from sentry.api.serializers import serialize from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.integrations.api.bases.organization_integrations import ( OrganizationIntegrationBaseEndpoint, ) from sentry.integrations.api.serializers.models.integration import OrganizationIntegrationSerializer from sentry.integrations.models.organization_integration import OrganizationIntegration -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.organizations.services.organization import RpcUserOrganizationContext from sentry.shared_integrations.exceptions import ApiError, IntegrationError from sentry.utils.audit import create_audit_entry diff --git a/src/sentry/integrations/api/endpoints/organization_repository_details.py b/src/sentry/integrations/api/endpoints/organization_repository_details.py index 00a6690be4bd49..f6b0ef053095fa 100644 --- a/src/sentry/integrations/api/endpoints/organization_repository_details.py +++ b/src/sentry/integrations/api/endpoints/organization_repository_details.py @@ -15,11 +15,11 @@ from sentry.api.fields.empty_integer import EmptyIntegerField from sentry.api.serializers import serialize from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.hybridcloud.rpc import coerce_id_from from sentry.integrations.services.integration import integration_service from sentry.models.commit import Commit from sentry.models.repository import Repository -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.tasks.repository import repository_cascade_delete_on_hide diff --git a/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py b/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py index 76ce0088bbaedf..5592f4a25a27a5 100644 --- a/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py +++ b/src/sentry/migrations/0001_squashed_0484_break_org_member_user_fk.py @@ -26,12 +26,12 @@ import sentry.db.models.fields.text import sentry.db.models.fields.uuid import sentry.db.models.indexes +import sentry.deletions.models.scheduleddeletion import sentry.models.apiapplication import sentry.models.apigrant import sentry.models.apitoken import sentry.models.broadcast import sentry.models.groupshare -import sentry.models.scheduledeletion import sentry.sentry_apps.models.sentry_app import sentry.sentry_apps.models.sentry_app_installation import sentry.sentry_apps.models.servicehook @@ -2162,7 +2162,7 @@ class Migration(CheckedMigration): ( "guid", models.CharField( - default=sentry.models.scheduledeletion.default_guid, + default=sentry.deletions.models.scheduleddeletion.default_guid, max_length=32, unique=True, ), @@ -2174,7 +2174,7 @@ class Migration(CheckedMigration): ( "date_scheduled", models.DateTimeField( - default=sentry.models.scheduledeletion.default_date_schedule + default=sentry.deletions.models.scheduleddeletion.default_date_schedule ), ), ("actor_id", sentry.db.models.fields.bounded.BoundedBigIntegerField(null=True)), @@ -8993,7 +8993,7 @@ class Migration(CheckedMigration): ( "guid", models.CharField( - default=sentry.models.scheduledeletion.default_guid, + default=sentry.deletions.models.scheduleddeletion.default_guid, max_length=32, unique=True, ), @@ -9005,7 +9005,7 @@ class Migration(CheckedMigration): ( "date_scheduled", models.DateTimeField( - default=sentry.models.scheduledeletion.default_date_schedule + default=sentry.deletions.models.scheduleddeletion.default_date_schedule ), ), ("actor_id", sentry.db.models.fields.bounded.BoundedBigIntegerField(null=True)), diff --git a/src/sentry/migrations/0507_delete_pending_deletion_rules.py b/src/sentry/migrations/0507_delete_pending_deletion_rules.py index 03920d2d91164e..3fce0584bc82ed 100644 --- a/src/sentry/migrations/0507_delete_pending_deletion_rules.py +++ b/src/sentry/migrations/0507_delete_pending_deletion_rules.py @@ -11,8 +11,8 @@ from sentry.utils.query import RangeQuerySetWrapperWithProgressBar if TYPE_CHECKING: + from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.rule import Rule - from sentry.models.scheduledeletion import RegionScheduledDeletion class ObjectStatus: diff --git a/src/sentry/migrations/0515_slugify_invalid_monitors.py b/src/sentry/migrations/0515_slugify_invalid_monitors.py index 693701cf440b8f..556d1262389070 100644 --- a/src/sentry/migrations/0515_slugify_invalid_monitors.py +++ b/src/sentry/migrations/0515_slugify_invalid_monitors.py @@ -13,8 +13,8 @@ from sentry.utils.query import RangeQuerySetWrapperWithProgressBar if TYPE_CHECKING: + from sentry.deletions.models.scheduleddeletion import BaseScheduledDeletion from sentry.models.rule import Rule - from sentry.models.scheduledeletion import BaseScheduledDeletion from sentry.monitors.models import Monitor diff --git a/src/sentry/models/project.py b/src/sentry/models/project.py index 6a1a43cd28046e..7959088256ab8f 100644 --- a/src/sentry/models/project.py +++ b/src/sentry/models/project.py @@ -469,6 +469,7 @@ def get_full_name(self): return self.slug def transfer_to(self, organization): + from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.incidents.models.alert_rule import AlertRule from sentry.integrations.models.external_issue import ExternalIssue from sentry.models.environment import Environment, EnvironmentProject @@ -476,7 +477,6 @@ def transfer_to(self, organization): from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment from sentry.models.releases.release_project import ReleaseProject from sentry.models.rule import Rule - from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.monitors.models import Monitor old_org_id = self.organization_id diff --git a/src/sentry/models/scheduledeletion.py b/src/sentry/models/scheduledeletion.py index 318ba96a29491d..02f4d560f42b03 100644 --- a/src/sentry/models/scheduledeletion.py +++ b/src/sentry/models/scheduledeletion.py @@ -1,180 +1,6 @@ +# TODO(mark) Remove getsentry import shim from __future__ import annotations -import logging -from datetime import datetime, timedelta -from typing import Any, Self -from uuid import uuid4 +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion -from django.apps import apps -from django.db import models -from django.utils import timezone - -from sentry.backup.scopes import RelocationScope -from sentry.db.models import ( - BoundedBigIntegerField, - JSONField, - Model, - control_silo_model, - region_silo_model, -) -from sentry.silo.base import SiloLimit, SiloMode -from sentry.users.services.user import RpcUser -from sentry.users.services.user.service import user_service - -delete_logger = logging.getLogger("sentry.deletions.api") - - -def default_guid() -> str: - return uuid4().hex - - -def default_date_schedule() -> datetime: - return timezone.now() + timedelta(days=30) - - -class BaseScheduledDeletion(Model): - """ - ScheduledDeletions are, well, relations to arbitrary records in a particular silo that are due for deletion by - the tasks/deletion/scheduled.py job in the future. They are cancellable, and provide automatic, batched cascade - in an async way for performance reasons. - - Note that BOTH region AND control silos need to be able to schedule deletions of different records that will be - reconciled in different places. For that reason, the ScheduledDeletion model is split into two identical models - representing this split. Use the corresponding ScheduledDeletion based on the silo of the model being scheduled - for deletion. - """ - - class Meta: - abstract = True - - __relocation_scope__ = RelocationScope.Excluded - - guid = models.CharField(max_length=32, unique=True, default=default_guid) - app_label = models.CharField(max_length=64) - model_name = models.CharField(max_length=64) - object_id = BoundedBigIntegerField() - date_added = models.DateTimeField(default=timezone.now) - date_scheduled = models.DateTimeField(default=default_date_schedule) - actor_id = BoundedBigIntegerField(null=True) - data = JSONField(default={}) - in_progress = models.BooleanField(default=False) - - @classmethod - def schedule( - cls, instance: Model, days: int = 30, hours: int = 0, data: Any = None, actor: Any = None - ) -> Self: - model = type(instance) - silo_mode = SiloMode.get_current_mode() - model_silo = getattr(model._meta, "silo_limit", None) - assert ( - model_silo - ), "model._meta.silo_limit undefined. This model cannot be used with deletions" - if silo_mode not in model_silo.modes and silo_mode != SiloMode.MONOLITH: - # Pre-empt the fact that our silo protections wouldn't fire for mismatched model <-> silo deletion objects. - raise SiloLimit.AvailabilityError( - f"{model!r} was scheduled for deletion by {cls!r}, but is unavailable in {silo_mode!r}" - ) - - model_name = model.__name__ - record, created = cls.objects.create_or_update( - app_label=instance._meta.app_label, - model_name=model_name, - object_id=instance.pk, - values={ - "date_scheduled": timezone.now() + timedelta(days=days, hours=hours), - "data": data or {}, - "actor_id": actor.id if actor else None, - }, - ) - if not created: - record = cls.objects.get( - app_label=instance._meta.app_label, - model_name=model_name, - object_id=instance.pk, - ) - - delete_logger.info( - "object.delete.queued", - extra={ - "object_id": instance.id, - "transaction_id": record.guid, - "model": type(instance).__name__, - }, - ) - return record - - @classmethod - def cancel(cls, instance: Model): - model_name = type(instance).__name__ - try: - deletion = cls.objects.get( - model_name=model_name, object_id=instance.pk, in_progress=False - ) - except cls.DoesNotExist: - delete_logger.info( - "object.delete.canceled.failed", - extra={"object_id": instance.pk, "model": model_name}, - ) - return - - deletion.delete() - delete_logger.info( - "object.delete.canceled", - extra={"object_id": instance.pk, "model": model_name}, - ) - - def get_model(self) -> type[Any]: - return apps.get_model(self.app_label, self.model_name) - - def get_instance(self) -> Model: - from sentry import deletions - from sentry.deletions.base import ModelDeletionTask - - model = self.get_model() - deletion_task = deletions.get(model=model, query=None) - query_manager = model.objects - if isinstance(deletion_task, ModelDeletionTask): - query_manager = getattr(model, deletion_task.manager_name) - return query_manager.get(pk=self.object_id) - - def get_actor(self) -> RpcUser | None: - if not self.actor_id: - return None - - return user_service.get_user(user_id=self.actor_id) - - -@control_silo_model -class ScheduledDeletion(BaseScheduledDeletion): - """ - This model schedules deletions to be processed in control and monolith silo modes. All historic schedule deletions - occur in this table. In the future, when RegionScheduledDeletions have proliferated for the appropriate models, - we will allow any region models scheduled in this table to finish processing before ensuring that all models discretely - process in either this table or the region table. - """ - - class Meta: - unique_together = (("app_label", "model_name", "object_id"),) - app_label = "sentry" - db_table = "sentry_scheduleddeletion" - - -@region_silo_model -class RegionScheduledDeletion(BaseScheduledDeletion): - """ - This model schedules deletions to be processed in region and monolith silo modes. As new region silo test coverage - increases, new scheduled deletions will begin to occur in this table. Monolith (current saas) will continue - processing them alongside the original scheduleddeletions table, but in the future this table will only be - processed by region silos. - """ - - class Meta: - unique_together = (("app_label", "model_name", "object_id"),) - app_label = "sentry" - db_table = "sentry_regionscheduleddeletion" - - -def get_regional_scheduled_deletion(mode: SiloMode) -> type[BaseScheduledDeletion]: - if mode != SiloMode.CONTROL: - return RegionScheduledDeletion - return ScheduledDeletion +__all__ = ("RegionScheduledDeletion",) diff --git a/src/sentry/monitors/endpoints/base_monitor_details.py b/src/sentry/monitors/endpoints/base_monitor_details.py index f220997994ad85..8f9a2c366f3336 100644 --- a/src/sentry/monitors/endpoints/base_monitor_details.py +++ b/src/sentry/monitors/endpoints/base_monitor_details.py @@ -13,10 +13,10 @@ from sentry.api.helpers.environments import get_environments from sentry.api.serializers import serialize from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.environment import Environment from sentry.models.project import Project from sentry.models.rule import Rule, RuleActivity, RuleActivityType -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.monitors.models import ( CheckInStatus, Monitor, diff --git a/src/sentry/monitors/endpoints/base_monitor_environment_details.py b/src/sentry/monitors/endpoints/base_monitor_environment_details.py index b090001a5cbc8e..bbdb5364a5a76e 100644 --- a/src/sentry/monitors/endpoints/base_monitor_environment_details.py +++ b/src/sentry/monitors/endpoints/base_monitor_environment_details.py @@ -7,7 +7,7 @@ from sentry.api.base import BaseEndpointMixin from sentry.api.serializers import serialize from sentry.constants import ObjectStatus -from sentry.models.scheduledeletion import RegionScheduledDeletion +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.monitors.models import MonitorEnvironment, MonitorStatus diff --git a/src/sentry/organizations/services/organization/impl.py b/src/sentry/organizations/services/organization/impl.py index 2d6d44cd5f6853..136ec177bad66a 100644 --- a/src/sentry/organizations/services/organization/impl.py +++ b/src/sentry/organizations/services/organization/impl.py @@ -12,6 +12,7 @@ from sentry.api.serializers import serialize from sentry.backup.dependencies import merge_users_for_model_in_org from sentry.db.postgres.transactions import enforce_constraints +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.hybridcloud.models.outbox import ControlOutbox, outbox_context from sentry.hybridcloud.outbox.category import OutboxCategory, OutboxScope from sentry.hybridcloud.rpc import OptionValue, logger @@ -37,7 +38,6 @@ from sentry.models.rule import Rule, RuleActivity from sentry.models.rulesnooze import RuleSnooze from sentry.models.savedsearch import SavedSearch -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.models.team import Team, TeamStatus from sentry.monitors.models import Monitor from sentry.organizations.services.organization import ( diff --git a/src/sentry/testutils/hybrid_cloud.py b/src/sentry/testutils/hybrid_cloud.py index f07b5937eded1f..20105205ecac5e 100644 --- a/src/sentry/testutils/hybrid_cloud.py +++ b/src/sentry/testutils/hybrid_cloud.py @@ -14,9 +14,12 @@ from django.db.backends.base.base import BaseDatabaseWrapper from sentry.db.postgres.transactions import in_test_transaction_enforcement +from sentry.deletions.models.scheduleddeletion import ( + BaseScheduledDeletion, + get_regional_scheduled_deletion, +) from sentry.models.organizationmember import OrganizationMember from sentry.models.organizationmembermapping import OrganizationMemberMapping -from sentry.models.scheduledeletion import BaseScheduledDeletion, get_regional_scheduled_deletion from sentry.silo.base import SiloMode from sentry.testutils.silo import assume_test_silo_mode diff --git a/tests/sentry/api/endpoints/test_api_application_details.py b/tests/sentry/api/endpoints/test_api_application_details.py index adac1e2657c8ef..b98a88b38081fc 100644 --- a/tests/sentry/api/endpoints/test_api_application_details.py +++ b/tests/sentry/api/endpoints/test_api_application_details.py @@ -1,7 +1,7 @@ from django.urls import reverse +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.testutils.cases import APITestCase from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/api/endpoints/test_organization_details.py b/tests/sentry/api/endpoints/test_organization_details.py index 5b3e56d228a61b..03beb0566d2639 100644 --- a/tests/sentry/api/endpoints/test_organization_details.py +++ b/tests/sentry/api/endpoints/test_organization_details.py @@ -23,6 +23,7 @@ from sentry.auth.authenticators.recovery_code import RecoveryCodeInterface from sentry.auth.authenticators.totp import TotpInterface from sentry.constants import RESERVED_ORGANIZATION_SLUGS, ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.auditlogentry import AuditLogEntry from sentry.models.authprovider import AuthProvider from sentry.models.avatars.organization_avatar import OrganizationAvatar @@ -32,7 +33,6 @@ from sentry.models.organization import Organization, OrganizationStatus from sentry.models.organizationmapping import OrganizationMapping from sentry.models.organizationslugreservation import OrganizationSlugReservation -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.signals import project_created from sentry.silo.safety import unguarded_write from sentry.testutils.cases import APITestCase, TwoFactorAPITestCase diff --git a/tests/sentry/api/endpoints/test_project_details.py b/tests/sentry/api/endpoints/test_project_details.py index af5549e99f8586..20584c2f45009e 100644 --- a/tests/sentry/api/endpoints/test_project_details.py +++ b/tests/sentry/api/endpoints/test_project_details.py @@ -13,6 +13,7 @@ from sentry import audit_log from sentry.constants import RESERVED_PROJECT_SLUGS, ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.dynamic_sampling import DEFAULT_BIASES, RuleType from sentry.dynamic_sampling.rules.base import NEW_MODEL_THRESHOLD_IN_MINUTES from sentry.issues.highlights import get_highlight_preset_for_project @@ -28,7 +29,6 @@ from sentry.models.projectredirect import ProjectRedirect from sentry.models.projectteam import ProjectTeam from sentry.models.rule import Rule -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.silo.base import SiloMode from sentry.silo.safety import unguarded_write from sentry.slug.errors import DEFAULT_SLUG_ERROR_MESSAGE diff --git a/tests/sentry/api/endpoints/test_team_details.py b/tests/sentry/api/endpoints/test_team_details.py index f03765ea214b79..ee415ec81ff8fb 100644 --- a/tests/sentry/api/endpoints/test_team_details.py +++ b/tests/sentry/api/endpoints/test_team_details.py @@ -1,7 +1,7 @@ from sentry import audit_log from sentry.audit_log.services.log.service import log_rpc_service +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.deletedteam import DeletedTeam -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.models.team import Team, TeamStatus from sentry.slug.errors import DEFAULT_SLUG_ERROR_MESSAGE from sentry.testutils.cases import APITestCase diff --git a/tests/sentry/deletions/tasks/test_scheduled.py b/tests/sentry/deletions/tasks/test_scheduled.py index 3e32884fad1fd7..ce24e18d96ba30 100644 --- a/tests/sentry/deletions/tasks/test_scheduled.py +++ b/tests/sentry/deletions/tasks/test_scheduled.py @@ -6,6 +6,11 @@ from django.db.models import QuerySet from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import ( + BaseScheduledDeletion, + RegionScheduledDeletion, + ScheduledDeletion, +) from sentry.deletions.tasks.scheduled import ( reattempt_deletions, reattempt_deletions_control, @@ -14,11 +19,6 @@ ) from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus from sentry.models.repository import Repository -from sentry.models.scheduledeletion import ( - BaseScheduledDeletion, - RegionScheduledDeletion, - ScheduledDeletion, -) from sentry.models.team import Team from sentry.signals import pending_delete from sentry.testutils.abstract import Abstract diff --git a/tests/sentry/deletions/test_apiapplication.py b/tests/sentry/deletions/test_apiapplication.py index ebfc6ebaad7624..c017f3c11b44b5 100644 --- a/tests/sentry/deletions/test_apiapplication.py +++ b/tests/sentry/deletions/test_apiapplication.py @@ -1,9 +1,9 @@ +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.deletions.tasks.hybrid_cloud import schedule_hybrid_cloud_foreign_key_jobs from sentry.deletions.tasks.scheduled import run_scheduled_deletions_control from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus from sentry.models.apigrant import ApiGrant from sentry.models.apitoken import ApiToken -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.sentry_apps.models.servicehook import ServiceHook from sentry.silo.base import SiloMode from sentry.testutils.cases import TransactionTestCase diff --git a/tests/sentry/incidents/endpoints/test_organization_combined_rule_index_endpoint.py b/tests/sentry/incidents/endpoints/test_organization_combined_rule_index_endpoint.py index bff541819fa644..cd09c0f4621497 100644 --- a/tests/sentry/incidents/endpoints/test_organization_combined_rule_index_endpoint.py +++ b/tests/sentry/incidents/endpoints/test_organization_combined_rule_index_endpoint.py @@ -1180,8 +1180,8 @@ def test_last_triggered(self): assert resp.data[0]["lastTriggered"] == datetime.now(UTC) def test_project_deleted(self): + from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.deletions.tasks.scheduled import run_deletion - from sentry.models.scheduledeletion import RegionScheduledDeletion org = self.create_organization(owner=self.user, name="Rowdy Tiger") team = self.create_team(organization=org, name="Mariachi Band", members=[self.user]) diff --git a/tests/sentry/integrations/api/endpoints/test_organization_integration_details.py b/tests/sentry/integrations/api/endpoints/test_organization_integration_details.py index e80a2285ce15a3..66564f5cb8e301 100644 --- a/tests/sentry/integrations/api/endpoints/test_organization_integration_details.py +++ b/tests/sentry/integrations/api/endpoints/test_organization_integration_details.py @@ -1,10 +1,10 @@ from unittest.mock import patch +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.integrations.base import IntegrationInstallation from sentry.integrations.models.integration import Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.models.repository import Repository -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.shared_integrations.exceptions import ApiError, IntegrationError from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase diff --git a/tests/sentry/integrations/api/endpoints/test_organization_repository_details.py b/tests/sentry/integrations/api/endpoints/test_organization_repository_details.py index 3cf167933bccfb..9b2f5f39acf9ef 100644 --- a/tests/sentry/integrations/api/endpoints/test_organization_repository_details.py +++ b/tests/sentry/integrations/api/endpoints/test_organization_repository_details.py @@ -4,10 +4,10 @@ from django.utils import timezone from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.commit import Commit from sentry.models.options.organization_option import OrganizationOption from sentry.models.repository import Repository -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase from sentry.testutils.silo import assume_test_silo_mode diff --git a/tests/sentry/integrations/discord/test_uninstall.py b/tests/sentry/integrations/discord/test_uninstall.py index 5338b8fb938fff..0a9212ae283d4f 100644 --- a/tests/sentry/integrations/discord/test_uninstall.py +++ b/tests/sentry/integrations/discord/test_uninstall.py @@ -4,11 +4,11 @@ import responses from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.integrations.discord.client import USERS_GUILD_URL, DiscordClient from sentry.integrations.models.integration import Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.models.organization import Organization -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase from sentry.testutils.factories import Factories diff --git a/tests/sentry/integrations/models/deletions/test_organizationintegration.py b/tests/sentry/integrations/models/deletions/test_organizationintegration.py index ad005e5bda4c46..a0455f303b6c87 100644 --- a/tests/sentry/integrations/models/deletions/test_organizationintegration.py +++ b/tests/sentry/integrations/models/deletions/test_organizationintegration.py @@ -1,4 +1,5 @@ from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.deletions.tasks.scheduled import run_scheduled_deletions_control from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.models.integration import Integration @@ -7,7 +8,6 @@ from sentry.models.project import Project from sentry.models.projectcodeowners import ProjectCodeOwners from sentry.models.repository import Repository -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.silo.base import SiloMode from sentry.testutils.cases import TransactionTestCase from sentry.testutils.hybrid_cloud import HybridCloudTestMixin diff --git a/tests/sentry/integrations/vercel/test_integration.py b/tests/sentry/integrations/vercel/test_integration.py index 1d27e061128ee7..c6cffa91db2a06 100644 --- a/tests/sentry/integrations/vercel/test_integration.py +++ b/tests/sentry/integrations/vercel/test_integration.py @@ -6,13 +6,13 @@ from rest_framework.serializers import ValidationError from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.identity.vercel import VercelIdentityProvider from sentry.integrations.models.integration import Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.integrations.vercel import VercelClient, VercelIntegrationProvider from sentry.models.project import Project from sentry.models.projectkey import ProjectKey, ProjectKeyStatus -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.models.sentry_app_installation_for_provider import ( SentryAppInstallationForProvider, diff --git a/tests/sentry/integrations/vercel/test_uninstall.py b/tests/sentry/integrations/vercel/test_uninstall.py index b0e0f2003a84f9..88243290c1b39f 100644 --- a/tests/sentry/integrations/vercel/test_uninstall.py +++ b/tests/sentry/integrations/vercel/test_uninstall.py @@ -2,10 +2,10 @@ from fixtures.vercel import SECRET from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.integrations.models.integration import Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.integrations.vercel import VercelClient -from sentry.models.scheduledeletion import ScheduledDeletion from sentry.testutils.cases import APITestCase from sentry.testutils.helpers import override_options from sentry.testutils.silo import control_silo_test diff --git a/tests/sentry/models/test_project.py b/tests/sentry/models/test_project.py index 09428d218a00cb..88d70b6231e492 100644 --- a/tests/sentry/models/test_project.py +++ b/tests/sentry/models/test_project.py @@ -1,6 +1,7 @@ from collections.abc import Iterable from unittest.mock import patch +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.deletions.tasks.hybrid_cloud import schedule_hybrid_cloud_foreign_key_jobs_control from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.types import ExternalProviders @@ -18,7 +19,6 @@ from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment from sentry.models.releases.release_project import ReleaseProject from sentry.models.rule import Rule -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.monitors.models import Monitor, MonitorEnvironment, MonitorType, ScheduleType from sentry.notifications.types import NotificationSettingEnum from sentry.notifications.utils.participants import get_notification_recipients diff --git a/tests/sentry/monitors/endpoints/test_base_monitor_details.py b/tests/sentry/monitors/endpoints/test_base_monitor_details.py index 32fc9226d05d56..31f03683a2fef5 100644 --- a/tests/sentry/monitors/endpoints/test_base_monitor_details.py +++ b/tests/sentry/monitors/endpoints/test_base_monitor_details.py @@ -5,9 +5,9 @@ import pytest from sentry.constants import ObjectStatus +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.models.environment import Environment from sentry.models.rule import Rule, RuleActivity, RuleActivityType -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.monitors.constants import TIMEOUT from sentry.monitors.logic.mark_ok import mark_ok from sentry.monitors.models import ( diff --git a/tests/sentry/monitors/endpoints/test_base_monitor_environment_details.py b/tests/sentry/monitors/endpoints/test_base_monitor_environment_details.py index 382f65391bcdf8..d7672dd7fa7010 100644 --- a/tests/sentry/monitors/endpoints/test_base_monitor_environment_details.py +++ b/tests/sentry/monitors/endpoints/test_base_monitor_environment_details.py @@ -1,5 +1,5 @@ from sentry.constants import ObjectStatus -from sentry.models.scheduledeletion import RegionScheduledDeletion +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.monitors.models import Monitor, MonitorEnvironment, MonitorStatus from sentry.testutils.cases import MonitorTestCase from sentry.testutils.helpers.datetime import freeze_time diff --git a/tests/sentry/web/test_api.py b/tests/sentry/web/test_api.py index 4dcede5c48983b..3b14b44003b357 100644 --- a/tests/sentry/web/test_api.py +++ b/tests/sentry/web/test_api.py @@ -8,11 +8,11 @@ from sentry import options from sentry.api.utils import generate_region_url from sentry.auth import superuser +from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion from sentry.deletions.tasks.scheduled import run_deletion from sentry.models.apitoken import ApiToken from sentry.models.organization import Organization, OrganizationStatus from sentry.models.organizationmember import OrganizationMember -from sentry.models.scheduledeletion import RegionScheduledDeletion from sentry.silo.base import SiloMode from sentry.testutils.cases import TestCase from sentry.testutils.helpers.options import override_options