Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Worker: Migrate to Plan / Tier Tables #1007

Merged
merged 20 commits into from
Jan 30, 2025
Merged

Conversation

ajay-sentry
Copy link
Contributor

@ajay-sentry ajay-sentry commented Jan 16, 2025

The main objective of this PR is to migrate all Backend Service logic to use the new plan and tiers tables instead of the Plan constants. You can read more about milestone 3 here.

A core change introduced in this PR is the creation of mocks for plans and tiers, which are used in most test setup functions. For tests requiring usage in all nested functions, we use a fixture with autouse enabled. Additionally, we ensure django_db decorators are included to allow access to the mock database.

The rest of the changes should be fairly straightforward.

Closes codecov/engineering-team#3253

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. In 2022 this entity acquired Codecov and as result Sentry is going to need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

@codecov-staging
Copy link

codecov-staging bot commented Jan 16, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@codecov-qa
Copy link

codecov-qa bot commented Jan 16, 2025

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
1793 2 1791 9
View the top 2 failed tests by shortest run time
services/notification/notifiers/tests/unit/test_comment.py::TestCommentNotifier::test_send_actual_notification_spammy
Stack Traces | 0.027s run time
self = <test_comment.TestCommentNotifier object at 0x7f64d85532a0>

    @pytest.fixture(autouse=True)
    def setup(self):
>       mock_all_plans_and_tiers()

.../tests/unit/test_comment.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/helpers.py:7: in mock_all_plans_and_tiers
    trial_tier = TierFactory(tier_name=TierName.TRIAL.value)
.../local/lib/python3.13............/site-packages/factory/base.py:40: in __call__
    return cls.create(**kwargs)
.../local/lib/python3.13............/site-packages/factory/base.py:528: in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
.../local/lib/python3.13....../site-packages/factory/django.py:117: in _generate
    return super()._generate(strategy, params)
.../local/lib/python3.13............/site-packages/factory/base.py:465: in _generate
    return step.build()
.../local/lib/python3.13.../site-packages/factory/builder.py:262: in build
    instance = self.factory_meta.instantiate(
.../local/lib/python3.13............/site-packages/factory/base.py:317: in instantiate
    return self.factory._create(model, *args, **kwargs)
.../local/lib/python3.13....../site-packages/factory/django.py:166: in _create
    return manager.create(*args, **kwargs)
.../local/lib/python3.13.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
.../local/lib/python3.13.../db/models/query.py:658: in create
    obj.save(force_insert=True, using=self.db)
.../local/lib/python3.13.../db/models/base.py:814: in save
    self.save_base(
.../local/lib/python3.13.../db/models/base.py:877: in save_base
    updated = self._save_table(
.../local/lib/python3.13.../db/models/base.py:1020: in _save_table
    results = self._do_insert(
.../local/lib/python3.13.../db/models/base.py:1061: in _do_insert
    return manager._insert(
.../local/lib/python3.13.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
.../local/lib/python3.13.../db/models/query.py:1805: in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
.../local/lib/python3.13.../models/sql/compiler.py:1820: in execute_sql
    with self.connection.cursor() as cursor:
.../local/lib/python3.13.../django/utils/asyncio.py:26: in inner
    return func(*args, **kwargs)
.../local/lib/python3.13.../backends/base/base.py:330: in cursor
    return self._cursor()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>, name = None

    def _cursor(self, name=None):
        self.close_if_health_check_failed()
>       self.ensure_connection()
E       RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.

.../local/lib/python3.13.../backends/base/base.py:306: RuntimeError
services/notification/notifiers/tests/unit/test_comment.py::TestCommentNotifier::test_build_message_default_layout
Stack Traces | 0.034s run time
self = <test_comment.TestCommentNotifier object at 0x7f64d8550500>

    @pytest.fixture(autouse=True)
    def setup(self):
>       mock_all_plans_and_tiers()

.../tests/unit/test_comment.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/helpers.py:7: in mock_all_plans_and_tiers
    trial_tier = TierFactory(tier_name=TierName.TRIAL.value)
.../local/lib/python3.13............/site-packages/factory/base.py:40: in __call__
    return cls.create(**kwargs)
.../local/lib/python3.13............/site-packages/factory/base.py:528: in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
.../local/lib/python3.13....../site-packages/factory/django.py:117: in _generate
    return super()._generate(strategy, params)
.../local/lib/python3.13............/site-packages/factory/base.py:465: in _generate
    return step.build()
.../local/lib/python3.13.../site-packages/factory/builder.py:262: in build
    instance = self.factory_meta.instantiate(
.../local/lib/python3.13............/site-packages/factory/base.py:317: in instantiate
    return self.factory._create(model, *args, **kwargs)
.../local/lib/python3.13....../site-packages/factory/django.py:166: in _create
    return manager.create(*args, **kwargs)
.../local/lib/python3.13.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
.../local/lib/python3.13.../db/models/query.py:658: in create
    obj.save(force_insert=True, using=self.db)
.../local/lib/python3.13.../db/models/base.py:814: in save
    self.save_base(
.../local/lib/python3.13.../db/models/base.py:877: in save_base
    updated = self._save_table(
.../local/lib/python3.13.../db/models/base.py:1020: in _save_table
    results = self._do_insert(
.../local/lib/python3.13.../db/models/base.py:1061: in _do_insert
    return manager._insert(
.../local/lib/python3.13.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
.../local/lib/python3.13.../db/models/query.py:1805: in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
.../local/lib/python3.13.../models/sql/compiler.py:1820: in execute_sql
    with self.connection.cursor() as cursor:
.../local/lib/python3.13.../django/utils/asyncio.py:26: in inner
    return func(*args, **kwargs)
.../local/lib/python3.13.../backends/base/base.py:330: in cursor
    return self._cursor()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>, name = None

    def _cursor(self, name=None):
        self.close_if_health_check_failed()
>       self.ensure_connection()
E       RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.

.../local/lib/python3.13.../backends/base/base.py:306: RuntimeError

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

Copy link

codecov-public-qa bot commented Jan 16, 2025

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
1793 2 1791 9
View the top 2 failed tests by shortest run time
services/notification/notifiers/tests/unit/test_comment.py::TestCommentNotifier::test_send_actual_notification_spammy
Stack Traces | 0.027s run time
No failure message available
services/notification/notifiers/tests/unit/test_comment.py::TestCommentNotifier::test_build_message_default_layout
Stack Traces | 0.034s run time
No failure message available

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

Copy link

github-actions bot commented Jan 16, 2025

✅ All tests successful. No failed tests were found.

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

Copy link

codecov bot commented Jan 16, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (main@5e92d54). Learn more about missing BASE report.
Report is 11 commits behind head on main.

✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1007   +/-   ##
=======================================
  Coverage        ?   97.52%           
=======================================
  Files           ?      462           
  Lines           ?    37899           
  Branches        ?        0           
=======================================
  Hits            ?    36960           
  Misses          ?      939           
  Partials        ?        0           
Flag Coverage Δ
integration 42.89% <90.34%> (?)
unit 90.17% <99.06%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

⚠️ Impact Analysis from Codecov is deprecated and will be sunset on Jan 31 2025. See more

types.String(50), nullable=False, default=PlanName.BASIC_PLAN_NAME.value
types.String(50),
nullable=False,
default=PlanName.BASIC_PLAN_NAME.value, # TODO: UPDATE WITH NEW FREE PLAN NAME
Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!

requirements.txt Outdated Show resolved Hide resolved
@ajay-sentry ajay-sentry changed the title [WIP] Milestone 3 Plan Migration Worker: Migrate to Plan / Tier Tables Jan 23, 2025
Copy link

This PR includes changes to shared. Please review them here: codecov/shared@d1762e3...906e7bc

@@ -491,12 +500,14 @@ def test_get_decoration_type_not_pr_plan(self, dbsession, mocker, enriched_pull)
assert decoration_details.reason == "Org not on PR plan"
assert decoration_details.should_attempt_author_auto_activation is False

@pytest.mark.django_db
# what is a users plan?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

users plan is the one from GHM, which we actually will need to add to the plan table in DB as well now that I think about it

Copy link
Contributor

Choose a reason for hiding this comment

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

oohhh cool cool

Copy link
Contributor

@Swatinem Swatinem left a comment

Choose a reason for hiding this comment

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

Thanks for cleaning up all that print noise in test code (and in production code, lol). If its not too much effort, can you extract those changes as a separate PR?

Otherwise, my comments related to having this mock data around globally instead of pulling it in explicitly. As well as consuming these objects from the DB rather than by mocking function calls would be nice.
Mixing django/sqlalchemy is also something to be cautious about, as I believe it could cause massive problems.

Comment on lines 128 to 133
@patch("services.notification.Plan.objects.get")
def test_should_use_checks_notifier_deprecated_flow(
self, repo_data, outcome, dbsession
self, plan_objects_get, repo_data, outcome, dbsession
):
plan = PlanFactory.create()
plan_objects_get.return_value = plan
Copy link
Contributor

Choose a reason for hiding this comment

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

I have some questions, and see some problems with this:

  • first of all, you are creating the entity based on the Factory, so you might as well instert this into the database instead of using a mock for it.
  • but judging by the mock, this looks like you are using django .objects.get somewhere internally. This does not mix well with sqlalchemy (judging by both the dbsession fixture, and the .create test factory.

I believe that also in production, we should not mix these two ORMs, as that can potentially cause deadlocks.

tasks/upload.py Outdated Show resolved Hide resolved
from shared.plan.constants import PlanName, PlanPrice, TierName


def mock_all_plans_and_tiers():
Copy link
Contributor

Choose a reason for hiding this comment

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

you are calling this explicitly in a ton of tests.

How is this being handled in production? Were these things created manually, or using a data migration?

Maybe it makes sense to introduce these kinds of "constants" using a data migration in the first place.
Otherwise, is it possible to auto-load these fixtures in all the tests that are using the django DB somehow?
Its a bit annoying that you have to call this manually in all those places. Better to just "have this data around" within your tests.

Copy link

This PR includes changes to shared. Please review them here: codecov/shared@e854f50...7804571

Copy link

github-actions bot commented Jan 29, 2025

This PR includes changes to shared. Please review them here: codecov/shared@ce6e1e9...74c0888

@RulaKhaled RulaKhaled added this pull request to the merge queue Jan 30, 2025
Merged via the queue into main with commit 5ee50a8 Jan 30, 2025
26 of 27 checks passed
@RulaKhaled RulaKhaled deleted the Ajay/milestone-3-migration branch January 30, 2025 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Migrate Plan service logic in Worker
4 participants