Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions src/sentry/uptime/endpoints/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
MaxManualUptimeSubscriptionsReached,
MaxUrlsForDomainReachedException,
UptimeMonitorNoSeatAvailable,
check_uptime_subscription_limit,
check_url_limits,
create_uptime_detector,
create_uptime_subscription,
Expand Down Expand Up @@ -202,6 +203,16 @@ class UptimeMonitorValidator(CamelSnakeSerializer):
)

def validate(self, attrs):
# When creating a new uptime monitor, check if we would exceed the organization limit
if not self.instance:
organization = self.context["organization"]
try:
check_uptime_subscription_limit(organization.id)
except MaxManualUptimeSubscriptionsReached:
raise serializers.ValidationError(
f"You may have at most {MAX_MANUAL_SUBSCRIPTIONS_PER_ORG} uptime monitors per organization"
)

headers = []
method = "GET"
body = None
Expand Down Expand Up @@ -246,34 +257,29 @@ def create(self, validated_data):
method_headers_body = {
k: v for k, v in validated_data.items() if k in {"method", "headers", "body"}
}
try:
detector = create_uptime_detector(
project=self.context["project"],
environment=environment,
url=validated_data["url"],
interval_seconds=validated_data["interval_seconds"],
timeout_ms=validated_data["timeout_ms"],
name=validated_data["name"],
status=validated_data.get("status"),
mode=validated_data.get("mode", UptimeMonitorMode.MANUAL),
owner=validated_data.get("owner"),
trace_sampling=validated_data.get("trace_sampling", False),
recovery_threshold=validated_data["recovery_threshold"],
downtime_threshold=validated_data["downtime_threshold"],
**method_headers_body,
)
detector = create_uptime_detector(
project=self.context["project"],
environment=environment,
url=validated_data["url"],
interval_seconds=validated_data["interval_seconds"],
timeout_ms=validated_data["timeout_ms"],
name=validated_data["name"],
status=validated_data.get("status"),
mode=validated_data.get("mode", UptimeMonitorMode.MANUAL),
owner=validated_data.get("owner"),
trace_sampling=validated_data.get("trace_sampling", False),
recovery_threshold=validated_data["recovery_threshold"],
downtime_threshold=validated_data["downtime_threshold"],
**method_headers_body,
)

create_audit_entry(
request=self.context["request"],
organization=self.context["organization"],
target_object=detector.id,
event=audit_log.get_event_id("UPTIME_MONITOR_ADD"),
data=get_audit_log_data(detector),
)
except MaxManualUptimeSubscriptionsReached:
raise serializers.ValidationError(
f"You may have at most {MAX_MANUAL_SUBSCRIPTIONS_PER_ORG} uptime monitors per organization"
)
create_audit_entry(
request=self.context["request"],
organization=self.context["organization"],
target_object=detector.id,
event=audit_log.get_event_id("UPTIME_MONITOR_ADD"),
data=get_audit_log_data(detector),
)

return detector

Expand Down
30 changes: 18 additions & 12 deletions src/sentry/uptime/subscriptions/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ class MaxManualUptimeSubscriptionsReached(ValueError):
pass


def check_uptime_subscription_limit(organization_id: int) -> None:
"""
Check if adding a new manual uptime monitor would exceed the organization's limit.
Raises MaxManualUptimeSubscriptionsReached if the limit would be exceeded.
"""
manual_subscription_count = Detector.objects.filter(
status=ObjectStatus.ACTIVE,
type=GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
project__organization_id=organization_id,
config__mode=UptimeMonitorMode.MANUAL,
).count()

if manual_subscription_count >= MAX_MANUAL_SUBSCRIPTIONS_PER_ORG:
raise MaxManualUptimeSubscriptionsReached


class UptimeMonitorNoSeatAvailable(Exception):
"""
Indicates that the quotes system is unable to allocate a seat for the new
Expand Down Expand Up @@ -234,13 +250,6 @@ def create_uptime_detector(
Creates an UptimeSubscription and associated Detector
"""
if mode == UptimeMonitorMode.MANUAL:
manual_subscription_count = Detector.objects.filter(
status=ObjectStatus.ACTIVE,
type=GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE,
project__organization=project.organization,
config__mode=UptimeMonitorMode.MANUAL,
).count()

# Once a user has created a subscription manually, make sure we disable all autodetection, and remove any
# onboarding monitors
if project.organization.get_option("sentry:uptime_autodetection", False):
Expand All @@ -250,11 +259,8 @@ def create_uptime_detector(
):
delete_uptime_detector(detector)

if (
not override_manual_org_limit
and manual_subscription_count >= MAX_MANUAL_SUBSCRIPTIONS_PER_ORG
):
raise MaxManualUptimeSubscriptionsReached
if not override_manual_org_limit:
check_uptime_subscription_limit(project.organization_id)

with atomic_transaction(
using=(
Expand Down