|
12 | 12 | from sentry.models.integrations.integration import Integration
|
13 | 13 | from sentry.models.outbox import ControlOutbox, OutboxScope, outbox_context
|
14 | 14 | from sentry.models.savedsearch import SavedSearch
|
| 15 | +from sentry.models.tombstone import RegionTombstone |
15 | 16 | from sentry.models.user import User
|
| 17 | +from sentry.monitors.models import Monitor |
16 | 18 | from sentry.silo.base import SiloMode
|
17 | 19 | from sentry.tasks.deletion.hybrid_cloud import (
|
| 20 | + WatermarkBatch, |
| 21 | + get_ids_for_tombstone_cascade_cross_db, |
18 | 22 | get_watermark,
|
19 | 23 | schedule_hybrid_cloud_foreign_key_jobs,
|
20 | 24 | schedule_hybrid_cloud_foreign_key_jobs_control,
|
21 | 25 | set_watermark,
|
22 | 26 | )
|
23 | 27 | from sentry.testutils.factories import Factories
|
| 28 | +from sentry.testutils.helpers import override_options |
24 | 29 | from sentry.testutils.helpers.task_runner import BurstTaskRunner
|
25 | 30 | from sentry.testutils.outbox import outbox_runner
|
26 | 31 | from sentry.testutils.pytest.fixtures import django_db_all
|
27 |
| -from sentry.testutils.silo import assume_test_silo_mode, control_silo_test |
| 32 | +from sentry.testutils.silo import ( |
| 33 | + assume_test_silo_mode, |
| 34 | + assume_test_silo_mode_of, |
| 35 | + control_silo_test, |
| 36 | + region_silo_test, |
| 37 | +) |
28 | 38 | from sentry.types.region import find_regions_for_user
|
29 | 39 |
|
30 | 40 |
|
@@ -274,3 +284,183 @@ def test_set_null_deletion_behavior(task_runner):
|
274 | 284 | # Deletion set field to null
|
275 | 285 | saved_query = DiscoverSavedQuery.objects.get(id=saved_query.id)
|
276 | 286 | assert saved_query.created_by_id is None
|
| 287 | + |
| 288 | + |
| 289 | +def setup_cross_db_deletion_data(): |
| 290 | + user = Factories.create_user() |
| 291 | + organization = Factories.create_organization(owner=user, name="Delete Me") |
| 292 | + project = Factories.create_project(organization=organization) |
| 293 | + group = Factories.create_group(project=project) |
| 294 | + with assume_test_silo_mode_of(DiscoverSavedQuery, Monitor): |
| 295 | + saved_query = DiscoverSavedQuery.objects.create( |
| 296 | + name="disco-query", |
| 297 | + organization=organization, |
| 298 | + created_by_id=user.id, |
| 299 | + ) |
| 300 | + monitor = Monitor.objects.create( |
| 301 | + organization_id=organization.id, |
| 302 | + project_id=project.id, |
| 303 | + slug="test-monitor", |
| 304 | + name="Test Monitor", |
| 305 | + owner_user_id=user.id, |
| 306 | + ) |
| 307 | + |
| 308 | + return dict( |
| 309 | + user=user, |
| 310 | + organization=organization, |
| 311 | + project=project, |
| 312 | + monitor=monitor, |
| 313 | + group=group, |
| 314 | + saved_query=saved_query, |
| 315 | + ) |
| 316 | + |
| 317 | + |
| 318 | +@django_db_all |
| 319 | +@region_silo_test |
| 320 | +def test_get_ids_for_tombstone_cascade_cross_db(task_runner): |
| 321 | + data = setup_cross_db_deletion_data() |
| 322 | + |
| 323 | + unaffected_data = [] |
| 324 | + for i in range(3): |
| 325 | + unaffected_data.append(setup_cross_db_deletion_data()) |
| 326 | + |
| 327 | + user = data["user"] |
| 328 | + user_id = user.id |
| 329 | + monitor = data["monitor"] |
| 330 | + with assume_test_silo_mode_of(User), outbox_runner(): |
| 331 | + user.delete() |
| 332 | + |
| 333 | + tombstone = RegionTombstone.objects.get( |
| 334 | + object_identifier=user_id, table_name=User._meta.db_table |
| 335 | + ) |
| 336 | + |
| 337 | + highest_tombstone_id = RegionTombstone.objects.aggregate(Max("id")) |
| 338 | + monitor_owner_field = Monitor._meta.get_field("owner_user_id") |
| 339 | + |
| 340 | + with override_options({"hybrid_cloud.allow_cross_db_tombstones": True}): |
| 341 | + ids, oldest_obj = get_ids_for_tombstone_cascade_cross_db( |
| 342 | + tombstone_cls=RegionTombstone, |
| 343 | + model=Monitor, |
| 344 | + field=monitor_owner_field, |
| 345 | + watermark_batch=WatermarkBatch( |
| 346 | + low=0, |
| 347 | + up=highest_tombstone_id["id__max"] + 1, |
| 348 | + has_more=False, |
| 349 | + transaction_id="foobar", |
| 350 | + ), |
| 351 | + ) |
| 352 | + assert ids == [monitor.id] |
| 353 | + assert oldest_obj == tombstone.created_at |
| 354 | + |
| 355 | + |
| 356 | +@django_db_all |
| 357 | +@region_silo_test |
| 358 | +def test_get_ids_for_tombstone_cascade_cross_db_watermark_bounds(task_runner): |
| 359 | + cascade_data = [] |
| 360 | + for i in range(3): |
| 361 | + cascade_data.append(setup_cross_db_deletion_data()) |
| 362 | + |
| 363 | + unaffected_data = [] |
| 364 | + for i in range(3): |
| 365 | + unaffected_data.append(setup_cross_db_deletion_data()) |
| 366 | + |
| 367 | + in_order_tombstones = [] |
| 368 | + for data in cascade_data: |
| 369 | + user = data["user"] |
| 370 | + user_id = user.id |
| 371 | + with assume_test_silo_mode_of(User), outbox_runner(): |
| 372 | + user.delete() |
| 373 | + |
| 374 | + in_order_tombstones.append( |
| 375 | + RegionTombstone.objects.get(object_identifier=user_id, table_name=User._meta.db_table) |
| 376 | + ) |
| 377 | + |
| 378 | + bounds_with_expected_results = [ |
| 379 | + ( |
| 380 | + {"low": 0, "up": in_order_tombstones[1].id}, |
| 381 | + [cascade_data[0]["monitor"].id, cascade_data[1]["monitor"].id], |
| 382 | + ), |
| 383 | + ( |
| 384 | + {"low": in_order_tombstones[1].id, "up": in_order_tombstones[2].id}, |
| 385 | + [cascade_data[2]["monitor"].id], |
| 386 | + ), |
| 387 | + ( |
| 388 | + {"low": 0, "up": in_order_tombstones[0].id - 1}, |
| 389 | + [], |
| 390 | + ), |
| 391 | + ( |
| 392 | + {"low": in_order_tombstones[2].id + 1, "up": in_order_tombstones[2].id + 5}, |
| 393 | + [], |
| 394 | + ), |
| 395 | + ( |
| 396 | + {"low": -1, "up": in_order_tombstones[2].id + 1}, |
| 397 | + [ |
| 398 | + cascade_data[0]["monitor"].id, |
| 399 | + cascade_data[1]["monitor"].id, |
| 400 | + cascade_data[2]["monitor"].id, |
| 401 | + ], |
| 402 | + ), |
| 403 | + ] |
| 404 | + |
| 405 | + for bounds, bounds_with_expected_results in bounds_with_expected_results: |
| 406 | + monitor_owner_field = Monitor._meta.get_field("owner_user_id") |
| 407 | + |
| 408 | + with override_options({"hybrid_cloud.allow_cross_db_tombstones": True}): |
| 409 | + ids, oldest_obj = get_ids_for_tombstone_cascade_cross_db( |
| 410 | + tombstone_cls=RegionTombstone, |
| 411 | + model=Monitor, |
| 412 | + field=monitor_owner_field, |
| 413 | + watermark_batch=WatermarkBatch( |
| 414 | + low=bounds["low"], |
| 415 | + up=bounds["up"], |
| 416 | + has_more=False, |
| 417 | + transaction_id="foobar", |
| 418 | + ), |
| 419 | + ) |
| 420 | + assert ids == bounds_with_expected_results |
| 421 | + |
| 422 | + |
| 423 | +@django_db_all |
| 424 | +@region_silo_test |
| 425 | +def test_get_ids_for_tombstone_cascade_cross_db_with_multiple_tombstone_types(): |
| 426 | + data = setup_cross_db_deletion_data() |
| 427 | + |
| 428 | + unaffected_data = [] |
| 429 | + for i in range(3): |
| 430 | + unaffected_data.append(setup_cross_db_deletion_data()) |
| 431 | + |
| 432 | + # Pollute the tombstone data with references to relationships in other |
| 433 | + # tables matching other User IDs just to ensure we are filtering on the |
| 434 | + # correct table name. |
| 435 | + for udata in unaffected_data: |
| 436 | + unaffected_user = udata["user"] |
| 437 | + RegionTombstone.objects.create( |
| 438 | + table_name="something_table", object_identifier=unaffected_user.id |
| 439 | + ) |
| 440 | + |
| 441 | + user = data["user"] |
| 442 | + user_id = user.id |
| 443 | + monitor = data["monitor"] |
| 444 | + with assume_test_silo_mode_of(User), outbox_runner(): |
| 445 | + user.delete() |
| 446 | + |
| 447 | + tombstone = RegionTombstone.objects.get( |
| 448 | + object_identifier=user_id, table_name=User._meta.db_table |
| 449 | + ) |
| 450 | + |
| 451 | + highest_tombstone_id = RegionTombstone.objects.aggregate(Max("id")) |
| 452 | + |
| 453 | + with override_options({"hybrid_cloud.allow_cross_db_tombstones": True}): |
| 454 | + ids, oldest_obj = get_ids_for_tombstone_cascade_cross_db( |
| 455 | + tombstone_cls=RegionTombstone, |
| 456 | + model=Monitor, |
| 457 | + field=Monitor._meta.get_field("owner_user_id"), |
| 458 | + watermark_batch=WatermarkBatch( |
| 459 | + low=0, |
| 460 | + up=highest_tombstone_id["id__max"] + 1, |
| 461 | + has_more=False, |
| 462 | + transaction_id="foobar", |
| 463 | + ), |
| 464 | + ) |
| 465 | + assert ids == [monitor.id] |
| 466 | + assert oldest_obj == tombstone.created_at |
0 commit comments