123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- from __future__ import annotations
- import time
- from unittest import mock
- import pytest
- from django.db.models import F
- from django.utils import timezone
- from sentry.models.grouprelease import GroupRelease
- from sentry.models.project import Project
- from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment
- from sentry.models.repository import Repository
- from sentry.release_health.release_monitor.base import BaseReleaseMonitorBackend
- from sentry.release_health.release_monitor.metrics import MetricReleaseMonitorBackend
- from sentry.release_health.release_monitor.sessions import SessionReleaseMonitorBackend
- from sentry.release_health.tasks import monitor_release_adoption, process_projects_with_sessions
- from sentry.testutils.abstract import Abstract
- from sentry.testutils.cases import BaseMetricsTestCase, SnubaTestCase, TestCase
- from sentry.testutils.helpers.datetime import before_now, iso_format
- pytestmark = pytest.mark.sentry_metrics
- class BaseTestReleaseMonitor(TestCase, SnubaTestCase):
- __test__ = Abstract(__module__, __qualname__) # type: ignore[name-defined] # python/mypy#10570
- backend_class: type[BaseReleaseMonitorBackend]
- def setUp(self):
- super().setUp()
- backend = self.backend_class()
- self.backend = mock.patch("sentry.release_health.tasks.release_monitor", backend)
- self.backend.__enter__()
- self.project = self.create_project()
- self.project1 = self.create_project()
- self.project2 = self.create_project()
- self.project1.update(flags=F("flags").bitor(Project.flags.has_releases))
- self.project2.update(flags=F("flags").bitor(Project.flags.has_releases))
- self.repo = Repository.objects.create(
- organization_id=self.organization.id, name=self.organization.id
- )
- self.release = self.create_release(project=self.project, version="foo@1.0.0")
- self.release2 = self.create_release(project=self.project, version="foo@2.0.0")
- self.release3 = self.create_release(project=self.project2, version="bar@1.0.0")
- self.environment = self.create_environment(name="prod", project=self.project1)
- self.environment2 = self.create_environment(name="canary", project=self.project2)
- self.group = self.create_group(
- project=self.project, message="Kaboom!", first_release=self.release
- )
- self.rpe = ReleaseProjectEnvironment.objects.create(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- )
- self.rpe1 = ReleaseProjectEnvironment.objects.create(
- project_id=self.project1.id,
- release_id=self.release2.id,
- environment_id=self.environment.id,
- )
- self.rpe2 = ReleaseProjectEnvironment.objects.create(
- project_id=self.project1.id,
- release_id=self.release3.id,
- environment_id=self.environment.id,
- )
- self.rpe3 = ReleaseProjectEnvironment.objects.create(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- )
- GroupRelease.objects.create(
- group_id=self.group.id, release_id=self.release.id, project_id=self.project.id
- )
- self.event = self.store_event(
- data={
- "message": "Kaboom!",
- "platform": "python",
- "timestamp": iso_format(before_now(seconds=10)),
- "stacktrace": {
- "frames": [
- {
- "function": "handle_set_commits",
- "abs_path": "/usr/src/sentry/src/sentry/tasks.py",
- "module": "sentry.tasks",
- "in_app": True,
- "lineno": 30,
- "filename": "sentry/tasks.py",
- },
- {
- "function": "set_commits",
- "abs_path": "/usr/src/sentry/src/sentry/models/release.py",
- "module": "sentry.models.release",
- "in_app": True,
- "lineno": 39,
- "filename": "sentry/models/release.py",
- },
- ]
- },
- "tags": {"sentry:release": self.release.version},
- "fingerprint": ["finterpring"],
- },
- project_id=self.project.id,
- )
- GroupRelease.objects.create(
- group_id=self.event.group.id, project_id=self.project.id, release_id=self.release.id
- )
- def tearDown(self):
- self.backend.__exit__(None, None, None)
- def test_simple(self):
- self.bulk_store_sessions([self.build_session(project_id=self.project1) for _ in range(11)])
- self.bulk_store_sessions(
- [
- self.build_session(project_id=self.project2, environment=self.environment2.name)
- for _ in range(1)
- ]
- )
- assert not self.project1.flags.has_sessions
- now = timezone.now()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted=None,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted__gte=now,
- ).exists()
- test_data = [
- {
- "org_id": [self.organization.id],
- "project_id": [self.project2.id, self.project1.id],
- },
- ]
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- self.project1.refresh_from_db()
- assert self.project1.flags.has_sessions
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted__gte=now,
- ).exists()
- def test_simple_no_sessions(self):
- now = timezone.now()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted=None,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted__gte=now,
- ).exists()
- test_data = [
- {
- "org_id": [self.organization.id],
- "project_id": [self.project2.id, self.project1.id],
- },
- ]
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted=None,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted__gte=now,
- ).exists()
- def test_release_is_unadopted_with_sessions(self):
- # Releases that are returned with sessions but no longer meet the threshold get unadopted
- self.bulk_store_sessions([self.build_session(project_id=self.project1) for _ in range(1)])
- self.bulk_store_sessions(
- [
- self.build_session(project_id=self.project2, environment=self.environment2)
- for _ in range(11)
- ]
- )
- self.bulk_store_sessions(
- [self.build_session(project_id=self.project1, release=self.release2) for _ in range(20)]
- )
- now = timezone.now()
- self.rpe.update(adopted=now)
- self.rpe1.update(adopted=now)
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=now,
- unadopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release2.id,
- environment_id=self.environment.id,
- adopted=now,
- unadopted=None,
- ).exists()
- test_data = [
- {
- "org_id": [self.organization.id],
- "project_id": [self.project2.id, self.project1.id],
- },
- ]
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=now,
- unadopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release2.id,
- environment_id=self.environment.id,
- adopted=now,
- unadopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted=now,
- unadopted__gte=now,
- ).exists()
- # Make sure re-adopting works
- self.bulk_store_sessions([self.build_session(project_id=self.project1) for _ in range(50)])
- time.sleep(1)
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=now, # doesn't get updated, unadopted just gets set to null
- unadopted=None,
- ).exists()
- def test_release_is_unadopted_without_sessions(self):
- # This test should verify that releases that have no sessions (i.e. no result from snuba)
- # get marked as unadopted
- now = timezone.now()
- self.rpe.update(adopted=now)
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=now,
- unadopted=None,
- ).exists()
- test_data = [
- {
- "org_id": [self.organization.id],
- "project_id": [self.project2.id, self.project1.id],
- },
- ]
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=now,
- unadopted__gte=now,
- ).exists()
- def test_multi_proj_env_release_counter(self):
- self.bulk_store_sessions(
- [
- self.build_session(
- project_id=self.project1,
- )
- for _ in range(11)
- ]
- )
- self.bulk_store_sessions(
- [
- self.build_session(project_id=self.project2, environment=self.environment2)
- for _ in range(1)
- ]
- )
- self.bulk_store_sessions(
- [self.build_session(project_id=self.project1, release=self.release2) for _ in range(1)]
- )
- self.bulk_store_sessions(
- [self.build_session(project_id=self.project1, release=self.release3) for _ in range(1)]
- )
- now = timezone.now()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted=None,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted__gte=now,
- ).exists()
- test_data = [
- {
- "org_id": [self.organization.id],
- "project_id": [self.project2.id, self.project1.id],
- },
- ]
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project2.id,
- release_id=self.release.id,
- environment_id=self.environment2.id,
- adopted__gte=now,
- ).exists()
- def test_monitor_release_adoption(self):
- now = timezone.now()
- self.org2 = self.create_organization(
- name="Yet Another Test Org",
- owner=self.user,
- )
- self.org2_project = self.create_project(organization=self.org2)
- self.org2_project.update(flags=F("flags").bitor(Project.flags.has_releases))
- self.org2_release = self.create_release(project=self.org2_project, version="org@2.0.0")
- self.org2_environment = self.create_environment(name="yae", project=self.org2_project)
- self.org2_rpe = ReleaseProjectEnvironment.objects.create(
- project_id=self.org2_project.id,
- release_id=self.org2_release.id,
- environment_id=self.org2_environment.id,
- )
- self.bulk_store_sessions(
- [
- self.build_session(
- org_id=self.org2,
- project_id=self.org2_project,
- release=self.org2_release,
- environment=self.org2_environment,
- )
- for _ in range(20)
- ]
- )
- # Tests the scheduled task to ensure it properly processes each org
- self.bulk_store_sessions(
- [
- self.build_session(
- project_id=self.project1,
- )
- for _ in range(11)
- ]
- )
- self.bulk_store_sessions(
- [
- self.build_session(project_id=self.project2, environment=self.environment2)
- for _ in range(1)
- ]
- )
- with self.tasks():
- monitor_release_adoption()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment_id=self.environment.id,
- adopted__gte=now,
- unadopted=None,
- ).exists()
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.org2_project.id,
- release_id=self.org2_release.id,
- environment_id=self.org2_environment.id,
- adopted__gte=now,
- unadopted=None,
- ).exists()
- def test_missing_rpe_is_created(self):
- self.bulk_store_sessions(
- [
- self.build_session(
- project_id=self.project1, release=self.release2, environment="somenvname"
- )
- for _ in range(20)
- ]
- )
- self.bulk_store_sessions(
- [
- self.build_session(project_id=self.project1, release=self.release2, environment="")
- for _ in range(20)
- ]
- )
- now = timezone.now()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release.id,
- environment__name="somenvname",
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release2.id,
- environment__name="",
- ).exists()
- test_data = [
- {
- "org_id": [self.organization.id],
- "project_id": [self.project2.id, self.project1.id],
- },
- ]
- # This will make the appropriate models (Environment, ReleaseProject, ReleaseEnvironment and ReleaseProjectEnvironment)
- process_projects_with_sessions(test_data[0]["org_id"][0], test_data[0]["project_id"])
- assert ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release2.id,
- environment__name="somenvname",
- adopted__gte=now,
- ).exists()
- assert not ReleaseProjectEnvironment.objects.filter(
- project_id=self.project1.id,
- release_id=self.release2.id,
- environment__name="",
- ).exists()
- def test_has_releases_is_set(self):
- no_release_project = self.create_project()
- assert not no_release_project.flags.has_releases
- self.bulk_store_sessions(
- [
- self.build_session(
- project_id=no_release_project, release=self.release2, environment="somenvname"
- )
- ]
- )
- process_projects_with_sessions(no_release_project.organization_id, [no_release_project.id])
- no_release_project.refresh_from_db()
- assert no_release_project.flags.has_releases
- def test_no_env(self):
- no_env_project = self.create_project()
- assert not no_env_project.flags.has_releases
- # If environment is None, we shouldn't make any changes
- self.bulk_store_sessions(
- [self.build_session(project_id=no_env_project, release=self.release2, environment=None)]
- )
- process_projects_with_sessions(no_env_project.organization_id, [no_env_project.id])
- no_env_project.refresh_from_db()
- assert not no_env_project.flags.has_releases
- class TestSessionReleaseMonitor(BaseTestReleaseMonitor):
- backend_class = SessionReleaseMonitorBackend
- class TestMetricReleaseMonitor(BaseTestReleaseMonitor, BaseMetricsTestCase):
- backend_class = MetricReleaseMonitorBackend
|