|
1 | | -import hashlib |
2 | 1 | import logging |
3 | 2 | from collections.abc import Sequence |
4 | 3 |
|
5 | | -from django.db import IntegrityError |
6 | | -from django.db.models import TextField |
7 | | -from django.db.models.expressions import Value |
8 | | -from django.db.models.functions import MD5, Coalesce |
9 | | - |
10 | 4 | from sentry import quotas |
11 | 5 | from sentry.constants import DataCategory, ObjectStatus |
12 | 6 | from sentry.models.environment import Environment |
|
19 | 13 | ProjectUptimeSubscriptionMode, |
20 | 14 | UptimeSubscription, |
21 | 15 | UptimeSubscriptionRegion, |
22 | | - headers_json_encoder, |
23 | 16 | load_regions_for_uptime_subscription, |
24 | 17 | ) |
25 | 18 | from sentry.uptime.rdap.tasks import fetch_subscription_rdap_info |
@@ -62,189 +55,6 @@ def __init__(self, result: SeatAssignmentResult | None) -> None: |
62 | 55 | self.result = result |
63 | 56 |
|
64 | 57 |
|
65 | | -def retrieve_uptime_subscription( |
66 | | - url: str, |
67 | | - interval_seconds: int, |
68 | | - timeout_ms: int, |
69 | | - method: str, |
70 | | - headers: Sequence[tuple[str, str]], |
71 | | - body: str | None, |
72 | | - trace_sampling: bool, |
73 | | -) -> UptimeSubscription | None: |
74 | | - try: |
75 | | - subscription = ( |
76 | | - UptimeSubscription.objects.filter( |
77 | | - url=url, |
78 | | - interval_seconds=interval_seconds, |
79 | | - timeout_ms=timeout_ms, |
80 | | - method=method, |
81 | | - trace_sampling=trace_sampling, |
82 | | - ) |
83 | | - .annotate( |
84 | | - headers_md5=MD5("headers", output_field=TextField()), |
85 | | - body_md5=Coalesce(MD5("body"), Value(""), output_field=TextField()), |
86 | | - ) |
87 | | - .filter( |
88 | | - headers_md5=hashlib.md5(headers_json_encoder(headers).encode("utf-8")).hexdigest(), |
89 | | - body_md5=hashlib.md5(body.encode("utf-8")).hexdigest() if body else "", |
90 | | - ) |
91 | | - .get() |
92 | | - ) |
93 | | - except UptimeSubscription.DoesNotExist: |
94 | | - subscription = None |
95 | | - return subscription |
96 | | - |
97 | | - |
98 | | -def get_or_create_uptime_subscription( |
99 | | - url: str, |
100 | | - interval_seconds: int, |
101 | | - timeout_ms: int, |
102 | | - method: str = "GET", |
103 | | - headers: Sequence[tuple[str, str]] | None = None, |
104 | | - body: str | None = None, |
105 | | - trace_sampling: bool = False, |
106 | | -) -> UptimeSubscription: |
107 | | - # XXX: Remove this, keeping it around for getsentry backwards compat |
108 | | - if headers is None: |
109 | | - headers = [] |
110 | | - # We extract the domain and suffix of the url here. This is used to prevent there being too many checks to a single |
111 | | - # domain. |
112 | | - result = extract_domain_parts(url) |
113 | | - |
114 | | - subscription = retrieve_uptime_subscription( |
115 | | - url, interval_seconds, timeout_ms, method, headers, body, trace_sampling |
116 | | - ) |
117 | | - created = False |
118 | | - |
119 | | - if subscription is None: |
120 | | - try: |
121 | | - subscription = UptimeSubscription.objects.create( |
122 | | - url=url, |
123 | | - url_domain=result.domain, |
124 | | - url_domain_suffix=result.suffix, |
125 | | - interval_seconds=interval_seconds, |
126 | | - timeout_ms=timeout_ms, |
127 | | - status=UptimeSubscription.Status.CREATING.value, |
128 | | - type=UPTIME_SUBSCRIPTION_TYPE, |
129 | | - method=method, |
130 | | - headers=headers, # type: ignore[misc] |
131 | | - body=body, |
132 | | - trace_sampling=trace_sampling, |
133 | | - ) |
134 | | - created = True |
135 | | - except IntegrityError: |
136 | | - # Handle race condition where we tried to retrieve an existing subscription while it was being created |
137 | | - subscription = retrieve_uptime_subscription( |
138 | | - url, interval_seconds, timeout_ms, method, headers, body, trace_sampling |
139 | | - ) |
140 | | - |
141 | | - if subscription is None: |
142 | | - # This shouldn't happen, since we should always be able to fetch or create the subscription. |
143 | | - logger.error( |
144 | | - "Unable to create uptime subscription", |
145 | | - extra={ |
146 | | - "url": url, |
147 | | - "interval_seconds": interval_seconds, |
148 | | - "timeout_ms": timeout_ms, |
149 | | - "method": method, |
150 | | - "headers": headers, |
151 | | - "body": body, |
152 | | - }, |
153 | | - ) |
154 | | - raise ValueError("Unable to create uptime subscription") |
155 | | - |
156 | | - if subscription.status == UptimeSubscription.Status.DELETING.value: |
157 | | - # This is pretty unlikely to happen, but we should avoid deleting the subscription here and just confirm it |
158 | | - # exists in the checker. |
159 | | - created = True |
160 | | - |
161 | | - # Associate active regions with this subscription |
162 | | - for region in get_active_regions(): |
163 | | - # If we add a region here we need to resend the subscriptions |
164 | | - created |= UptimeSubscriptionRegion.objects.update_or_create( |
165 | | - uptime_subscription=subscription, |
166 | | - region_slug=region.slug, |
167 | | - defaults={"mode": region.mode}, |
168 | | - )[1] |
169 | | - |
170 | | - if created: |
171 | | - subscription.update(status=UptimeSubscription.Status.CREATING.value) |
172 | | - create_remote_uptime_subscription.delay(subscription.id) |
173 | | - fetch_subscription_rdap_info.delay(subscription.id) |
174 | | - return subscription |
175 | | - |
176 | | - |
177 | | -def get_or_create_project_uptime_subscription( |
178 | | - project: Project, |
179 | | - environment: Environment | None, |
180 | | - url: str, |
181 | | - interval_seconds: int, |
182 | | - timeout_ms: int, |
183 | | - method: str = "GET", |
184 | | - headers: Sequence[tuple[str, str]] | None = None, |
185 | | - body: str | None = None, |
186 | | - mode: ProjectUptimeSubscriptionMode = ProjectUptimeSubscriptionMode.MANUAL, |
187 | | - status: int = ObjectStatus.ACTIVE, |
188 | | - name: str = "", |
189 | | - owner: Actor | None = None, |
190 | | - trace_sampling: bool = False, |
191 | | - override_manual_org_limit: bool = False, |
192 | | -) -> tuple[ProjectUptimeSubscription, bool]: |
193 | | - # XXX: Remove this function after getsentry is compat |
194 | | - if mode == ProjectUptimeSubscriptionMode.MANUAL: |
195 | | - manual_subscription_count = ProjectUptimeSubscription.objects.filter( |
196 | | - project__organization=project.organization, mode=ProjectUptimeSubscriptionMode.MANUAL |
197 | | - ).count() |
198 | | - if ( |
199 | | - not override_manual_org_limit |
200 | | - and manual_subscription_count >= MAX_MANUAL_SUBSCRIPTIONS_PER_ORG |
201 | | - ): |
202 | | - raise MaxManualUptimeSubscriptionsReached |
203 | | - |
204 | | - uptime_subscription = get_or_create_uptime_subscription( |
205 | | - url=url, |
206 | | - interval_seconds=interval_seconds, |
207 | | - timeout_ms=timeout_ms, |
208 | | - method=method, |
209 | | - headers=headers, |
210 | | - body=body, |
211 | | - trace_sampling=trace_sampling, |
212 | | - ) |
213 | | - owner_user_id = None |
214 | | - owner_team_id = None |
215 | | - if owner: |
216 | | - if owner.is_user: |
217 | | - owner_user_id = owner.id |
218 | | - if owner.is_team: |
219 | | - owner_team_id = owner.id |
220 | | - uptime_monitor, created = ProjectUptimeSubscription.objects.get_or_create( |
221 | | - project=project, |
222 | | - environment=environment, |
223 | | - uptime_subscription=uptime_subscription, |
224 | | - mode=mode.value, |
225 | | - name=name, |
226 | | - owner_user_id=owner_user_id, |
227 | | - owner_team_id=owner_team_id, |
228 | | - ) |
229 | | - |
230 | | - # Update status. This may have the side effect of removing or creating a |
231 | | - # remote subscription. When a new monitor is created we will ensure seat |
232 | | - # assignment, which may cause the monitor to be disabled if there are no |
233 | | - # available seat assignments. |
234 | | - match status: |
235 | | - case ObjectStatus.ACTIVE: |
236 | | - try: |
237 | | - enable_project_uptime_subscription(uptime_monitor, ensure_assignment=created) |
238 | | - except UptimeMonitorNoSeatAvailable: |
239 | | - # No need to do anything if we failed to handle seat |
240 | | - # assignment. The monitor will be created, but not enabled |
241 | | - pass |
242 | | - case ObjectStatus.DISABLED: |
243 | | - disable_project_uptime_subscription(uptime_monitor) |
244 | | - |
245 | | - return uptime_monitor, created |
246 | | - |
247 | | - |
248 | 58 | def create_uptime_subscription( |
249 | 59 | url: str, |
250 | 60 | interval_seconds: int, |
|
0 commit comments