-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(demo-mode): artifact bundle sync (#86302)
- Loading branch information
1 parent
d1f54c8
commit a63a7f8
Showing
6 changed files
with
339 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from datetime import timedelta | ||
|
||
from django.db import router | ||
from django.db.models import Q | ||
from django.utils import timezone | ||
|
||
from sentry import options | ||
from sentry.models.artifactbundle import ( | ||
ArtifactBundle, | ||
ProjectArtifactBundle, | ||
ReleaseArtifactBundle, | ||
) | ||
from sentry.models.files import FileBlobOwner | ||
from sentry.models.organization import Organization | ||
from sentry.models.project import Project | ||
from sentry.tasks.base import instrumented_task | ||
from sentry.utils.db import atomic_transaction | ||
from sentry.utils.demo_mode import get_demo_org, is_demo_mode_enabled | ||
|
||
|
||
@instrumented_task( | ||
name="sentry.demo_mode.tasks.sync_artifact_bundles", | ||
queue="demo_mode", | ||
) | ||
def sync_artifact_bundles(): | ||
|
||
if ( | ||
not options.get("sentry.demo_mode.sync_artifact_bundles.enable") | ||
or not is_demo_mode_enabled() | ||
): | ||
return | ||
|
||
source_org_id = options.get("sentry.demo_mode.sync_artifact_bundles.source_org_id") | ||
source_org = Organization.objects.get(id=source_org_id) | ||
|
||
target_org = get_demo_org() | ||
|
||
lookback_days = options.get("sentry.demo_mode.sync_artifact_bundles.lookback_days") | ||
|
||
_sync_artifact_bundles(source_org, target_org, lookback_days) | ||
|
||
|
||
def _sync_artifact_bundles(source_org: Organization, target_org: Organization, lookback_days=1): | ||
if not source_org or not target_org: | ||
return | ||
|
||
cutoff_date = timezone.now() - timedelta(days=lookback_days) | ||
|
||
artifact_bundles = ArtifactBundle.objects.filter( | ||
Q(organization_id=source_org.id) | Q(organization_id=target_org.id), | ||
date_uploaded__gte=cutoff_date, | ||
) | ||
|
||
source_artifact_bundles = artifact_bundles.filter(organization_id=source_org.id) | ||
target_artifact_bundles = artifact_bundles.filter(organization_id=target_org.id) | ||
|
||
different_artifact_bundles = source_artifact_bundles.exclude( | ||
bundle_id__in=target_artifact_bundles.values_list("bundle_id", flat=True) | ||
) | ||
|
||
for source_artifact_bundle in different_artifact_bundles: | ||
_sync_artifact_bundle(source_artifact_bundle, target_org) | ||
|
||
|
||
def _sync_artifact_bundle(source_artifact_bundle: ArtifactBundle, target_org: Organization): | ||
with atomic_transaction( | ||
using=( | ||
router.db_for_write(ArtifactBundle), | ||
router.db_for_write(FileBlobOwner), | ||
router.db_for_write(ProjectArtifactBundle), | ||
router.db_for_write(ReleaseArtifactBundle), | ||
) | ||
): | ||
blobs = source_artifact_bundle.file.blobs.all() | ||
for blob in blobs: | ||
FileBlobOwner.objects.create( | ||
organization_id=target_org.id, | ||
blob_id=blob.id, | ||
) | ||
|
||
target_artifact_bundle = ArtifactBundle.objects.create( | ||
organization_id=target_org.id, | ||
bundle_id=source_artifact_bundle.bundle_id, | ||
artifact_count=source_artifact_bundle.artifact_count, | ||
date_last_modified=source_artifact_bundle.date_last_modified, | ||
date_uploaded=source_artifact_bundle.date_uploaded, | ||
file=source_artifact_bundle.file, | ||
indexing_state=source_artifact_bundle.indexing_state, | ||
) | ||
|
||
_sync_project_artifact_bundle(source_artifact_bundle, target_artifact_bundle) | ||
_sync_release_artifact_bundle(source_artifact_bundle, target_artifact_bundle) | ||
|
||
|
||
def _sync_project_artifact_bundle( | ||
source_artifact_bundle: ArtifactBundle, | ||
target_artifact_bundle: ArtifactBundle, | ||
): | ||
source_project_artifact_bundle = ProjectArtifactBundle.objects.get( | ||
artifact_bundle_id=source_artifact_bundle.id, | ||
organization_id=source_artifact_bundle.organization_id, | ||
) | ||
|
||
target_project = _find_matching_project( | ||
source_project_artifact_bundle.project_id, | ||
target_artifact_bundle.organization_id, | ||
) | ||
|
||
if not target_project: | ||
return | ||
|
||
ProjectArtifactBundle.objects.create( | ||
project_id=target_project.id, | ||
artifact_bundle_id=target_artifact_bundle.id, | ||
organization_id=target_artifact_bundle.organization_id, | ||
) | ||
|
||
|
||
def _sync_release_artifact_bundle( | ||
source_artifact_bundle: ArtifactBundle, | ||
target_artifact_bundle: ArtifactBundle, | ||
): | ||
source_release_artifact_bundle = ReleaseArtifactBundle.objects.filter( | ||
artifact_bundle_id=source_artifact_bundle.id, | ||
organization_id=source_artifact_bundle.organization_id, | ||
).first() | ||
|
||
if not source_release_artifact_bundle: | ||
return | ||
|
||
ReleaseArtifactBundle.objects.create( | ||
artifact_bundle_id=target_artifact_bundle.id, | ||
organization_id=target_artifact_bundle.organization_id, | ||
dist_name=source_release_artifact_bundle.dist_name, | ||
release_name=source_release_artifact_bundle.release_name, | ||
) | ||
|
||
|
||
def _find_matching_project(project_id, organization_id): | ||
source_project = Project.objects.get(id=project_id) | ||
|
||
return Project.objects.get( | ||
organization_id=organization_id, | ||
slug=source_project.slug, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
from datetime import datetime, timedelta | ||
|
||
import pytest | ||
from django.utils import timezone | ||
|
||
from sentry.demo_mode.tasks import _sync_artifact_bundles | ||
from sentry.models.artifactbundle import ( | ||
ArtifactBundle, | ||
ProjectArtifactBundle, | ||
ReleaseArtifactBundle, | ||
) | ||
from sentry.models.organization import Organization | ||
from sentry.models.project import Project | ||
from sentry.testutils.cases import TestCase | ||
|
||
|
||
class SyncArtifactBundlesTest(TestCase): | ||
|
||
def setUp(self): | ||
self.source_org = self.create_organization(slug="source_org") | ||
self.target_org = self.create_organization(slug="target_org") | ||
self.unrelated_org = self.create_organization(slug="unrelated_org") | ||
|
||
self.source_proj_foo = self.create_project(organization=self.source_org, slug="foo") | ||
self.target_proj_foo = self.create_project(organization=self.target_org, slug="foo") | ||
self.unrelated_proj_foo = self.create_project(organization=self.unrelated_org, slug="foo") | ||
|
||
self.source_proj_bar = self.create_project(organization=self.source_org, slug="bar") | ||
self.target_proj_baz = self.create_project(organization=self.target_org, slug="baz") | ||
|
||
def set_up_artifact_bundle( | ||
self, | ||
organization: Organization, | ||
project: Project, | ||
date_uploaded: datetime | None = None, | ||
): | ||
date_uploaded = date_uploaded or timezone.now() | ||
artifact_bundle = self.create_artifact_bundle(org=organization, date_uploaded=date_uploaded) | ||
project_artifact_bundle = ProjectArtifactBundle.objects.create( | ||
organization_id=organization.id, | ||
project_id=project.id, | ||
artifact_bundle_id=artifact_bundle.id, | ||
) | ||
|
||
release_artifact_bundle = ReleaseArtifactBundle.objects.create( | ||
organization_id=organization.id, | ||
artifact_bundle_id=artifact_bundle.id, | ||
dist_name="dist", | ||
release_name="release", | ||
) | ||
|
||
return artifact_bundle, project_artifact_bundle, release_artifact_bundle | ||
|
||
def test_sync_artifact_bundles_no_bundles(self): | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
assert not ArtifactBundle.objects.all().exists() | ||
|
||
def test_sync_artifact_bundles_with_differences(self): | ||
(source_artifact_bundle, _, __) = self.set_up_artifact_bundle( | ||
self.source_org, self.source_proj_foo | ||
) | ||
|
||
assert not ArtifactBundle.objects.filter(organization_id=self.target_org.id).exists() | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
target_artifact_bundles = ArtifactBundle.objects.get(organization_id=self.target_org.id) | ||
|
||
assert target_artifact_bundles.bundle_id == source_artifact_bundle.bundle_id | ||
|
||
def test_sync_artifact_bundles_does_not_touch_other_orgs(self): | ||
self.set_up_artifact_bundle(self.source_org, self.source_proj_foo) | ||
self.set_up_artifact_bundle(self.unrelated_org, self.unrelated_proj_foo) | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
unrelated_artifact_bundles = ArtifactBundle.objects.filter( | ||
organization_id=self.unrelated_org.id | ||
) | ||
|
||
assert unrelated_artifact_bundles.count() == 1 | ||
|
||
def test_sync_artifact_bundles_with_old_uploads(self): | ||
self.set_up_artifact_bundle( | ||
self.source_org, self.source_proj_foo, date_uploaded=timezone.now() - timedelta(days=2) | ||
) | ||
|
||
assert not ArtifactBundle.objects.filter(organization_id=self.target_org.id).exists() | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
assert not ArtifactBundle.objects.filter(organization_id=self.target_org.id).exists() | ||
|
||
def test_sync_artifact_bundles_only_once(self): | ||
(source_artifact_bundle, _, __) = self.set_up_artifact_bundle( | ||
self.source_org, self.source_proj_foo | ||
) | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
target_artifact_bundles = ArtifactBundle.objects.filter(organization_id=self.target_org.id) | ||
|
||
assert target_artifact_bundles.count() == 1 | ||
assert target_artifact_bundles[0].bundle_id == source_artifact_bundle.bundle_id | ||
|
||
def test_sync_project_artifact_bundles(self): | ||
self.set_up_artifact_bundle(self.source_org, self.source_proj_foo) | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
target_project_artifact_bundle = ProjectArtifactBundle.objects.get( | ||
organization_id=self.target_org.id, | ||
project_id=self.target_proj_foo.id, | ||
) | ||
|
||
assert target_project_artifact_bundle.project_id == self.target_proj_foo.id | ||
assert target_project_artifact_bundle.organization_id == self.target_org.id | ||
|
||
def test_sync_release_artifact_bundles(self): | ||
(_, __, source_release_bundle) = self.set_up_artifact_bundle( | ||
self.source_org, self.source_proj_foo | ||
) | ||
|
||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
target_release_bundle = ReleaseArtifactBundle.objects.get( | ||
organization_id=self.target_org.id, | ||
) | ||
|
||
assert target_release_bundle.dist_name == source_release_bundle.dist_name | ||
assert target_release_bundle.release_name == source_release_bundle.release_name | ||
assert target_release_bundle.organization_id == self.target_org.id | ||
|
||
def test_sync_artifact_bunles_rolls_back_on_error(self): | ||
self.set_up_artifact_bundle(self.source_org, self.source_proj_bar) | ||
|
||
with pytest.raises(Project.DoesNotExist): | ||
_sync_artifact_bundles(source_org=self.source_org, target_org=self.target_org) | ||
|
||
assert not ArtifactBundle.objects.filter(organization_id=self.target_org.id).exists() | ||
assert not ProjectArtifactBundle.objects.filter(organization_id=self.target_org.id).exists() | ||
assert not ReleaseArtifactBundle.objects.filter(organization_id=self.target_org.id).exists() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters