-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(aci): Enforce quota limits when creating metric detectors #97445
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
base: master
Are you sure you want to change the base?
Changes from all commits
04f3894
7be9184
f3b7f8d
3608e60
ab551d4
589733e
00e05a5
5a8a5a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ | |
|
||
from rest_framework import serializers | ||
|
||
from sentry import features, quotas | ||
from sentry.constants import ObjectStatus | ||
from sentry.incidents.logic import enable_disable_subscriptions | ||
from sentry.snuba.models import QuerySubscription, SnubaQuery, SnubaQueryEventType | ||
from sentry.snuba.snuba_query_validator import SnubaQueryValidator | ||
|
@@ -88,6 +90,32 @@ def validate(self, attrs): | |
|
||
return attrs | ||
|
||
def enforce_quota_on_create(self) -> None: | ||
""" | ||
Enforce quota limits for metric detector creation. | ||
Raises ValidationError if quota limits are exceeded. | ||
""" | ||
organization = self.context.get("organization") | ||
request = self.context.get("request") | ||
|
||
if not organization or not request: | ||
raise serializers.ValidationError() | ||
|
||
if features.has( | ||
"organizations:workflow-engine-metric-detector-limit", organization, actor=request.user | ||
): | ||
detector_limit = quotas.backend.get_metric_detector_limit(organization.id) | ||
detector_count = Detector.objects.filter( | ||
project__organization=organization, | ||
type="metric_issue", # Avoided circular import. TODO: move magic strings to constant file | ||
status=ObjectStatus.ACTIVE, | ||
).count() | ||
|
||
if detector_limit >= 0 and detector_count >= detector_limit: | ||
raise serializers.ValidationError( | ||
f"You may not exceed {detector_limit} metric detectors on your current plan." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Quota Error Grammar & Hardcoded StringThe new metric detector quota enforcement has two issues. The error message for exceeding the limit uses incorrect grammar when the limit is 1 (e.g., "1 metric detectors" instead of "1 metric detector"). Additionally, the detector type filter uses a hardcoded string "metric_issue" instead of a constant, which makes the code less robust. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Inconsistent Quota Handling Across Detectors and AlertsQuota enforcement for metric detectors and alert rules is inconsistent: detectors count only Additional Locations (2)There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Quota Enforcement IssuesThe |
||
|
||
def update_data_source(self, instance: Detector, data_source: SnubaQueryDataSourceType): | ||
try: | ||
source_instance = DataSource.objects.get(detector=instance) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,6 +64,15 @@ def data_source(self) -> BaseDataSourceValidator: | |
def data_conditions(self) -> BaseDataConditionValidator: | ||
raise NotImplementedError | ||
|
||
def enforce_quota_on_create(self) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you decorate this with |
||
""" | ||
Enforce quota limits for detector creation. | ||
Raise ValidationError if quota limits are exceeded. | ||
|
||
Only Metric Issues Detector has quota limits. | ||
""" | ||
pass | ||
|
||
def update(self, instance: Detector, validated_data: dict[str, Any]): | ||
with transaction.atomic(router.db_for_write(Detector)): | ||
if "name" in validated_data: | ||
|
@@ -110,6 +119,10 @@ def update(self, instance: Detector, validated_data: dict[str, Any]): | |
return instance | ||
|
||
def create(self, validated_data): | ||
# If quotas are exceeded, we will prevent creation of new detectors. | ||
# Do not disable or prevent the users from updating existing detectors. | ||
self.enforce_quota_on_create() | ||
Comment on lines
+122
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it might be a cleaner abstraction to make # If quotas are exceeded, we will prevent creation of new detectors.
# Do not disable or prevent the users from updating existing detectors.
if not self.enforce_quota():
serializers.ValidationError(f"Cannot create {validated_data.type} monitor, exceeds quota limit.") we may also want to have the result include information about the quota itself to keep the nice error message per detector type. in that case, we could do something like: @dataclass(frozen=True)
class DetectorQuota():
limit: int | False # or something close for the typing
count: int
class BaseDetectorValidator():
@abc.abstractmethod
def get_quota(self) -> DetectorQuota:
pass
def enforce_quota(self) -> None:
quota = self.get_quota()
if quota.limit and quota.limit >= quota.count:
raise serializers.ValidationError(
f"Cannot create {validated_data.type} monitor, exceeds quota of {quota.limit}."
)
def create(self, validated_data):
self.enforce_quota()
# ... |
||
|
||
with transaction.atomic(router.db_for_write(Detector)): | ||
condition_group = DataConditionGroup.objects.create( | ||
logic_type=DataConditionGroup.Type.ANY, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Quota Enforcement Mismatch Across Paths
The metric detector quota is inconsistently enforced. The AlertRule creation endpoint counts all AlertRule objects (excluding snapshots), while the Detector creation path counts only active Detector objects. Both paths use the same quota limit, which allows users to exceed the intended limit.