mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
Installation quota: pick up on settings-changes
like 7b7cd66dfb but at the installation-level
This commit is contained in:
@@ -498,8 +498,9 @@ class BaseIngestAPIView(View):
|
||||
# Copy/pasted from count_project_periods_and_act_on_it and adapted. Explaining comments from over there were not
|
||||
# kept; the comments below are specific to the adapations.
|
||||
thresholds = [(p, n, get_settings()[key]) for (p, n, key) in QUOTA_THRESHOLDS["Installation"]]
|
||||
min_threshold = min([gte_threshold for (_, _, gte_threshold) in thresholds])
|
||||
|
||||
if installation.quota_exceeded_until is not None and now < installation.quota_exceeded_until:
|
||||
if cls.is_quota_still_exceeded(installation, now):
|
||||
return False
|
||||
|
||||
# We don't do per-event-digest bookkeeping on the installation because doing so would tie us in further into
|
||||
@@ -508,14 +509,19 @@ class BaseIngestAPIView(View):
|
||||
# +1 because about-to-add and the installation-wide call precedes the per-project bookkeeping.
|
||||
digested_event_count = (Project.objects.aggregate(total=Sum("digested_event_count"))["total"] or 0) + 1
|
||||
|
||||
if digested_event_count >= installation.next_quota_check:
|
||||
if ((digested_event_count >= installation.next_quota_check) or
|
||||
(installation.next_quota_check - digested_event_count > min_threshold)):
|
||||
|
||||
states = check_for_thresholds(Event.objects.all(), now, thresholds, 1)
|
||||
|
||||
until = max([below_from for (is_exceeded, below_from, _, _) in states if is_exceeded], default=None)
|
||||
until, threshold_info = max(
|
||||
[(below_from, ti) for (is_exceeded, below_from, _, ti) in states if is_exceeded],
|
||||
default=(None, None))
|
||||
|
||||
check_again_after = max(1, min([check_after for (_, _, check_after, _) in states], default=1))
|
||||
|
||||
installation.quota_exceeded_until = until # note: never reset to None, but the `now <` will still just work
|
||||
installation.quota_exceeded_reason = json.dumps(threshold_info)
|
||||
installation.next_quota_check = digested_event_count + check_again_after
|
||||
installation.save() # conditional in the if-statement because no per-digest bookkeeping on the installation
|
||||
|
||||
@@ -637,7 +643,7 @@ class IngestEventAPIView(BaseIngestAPIView):
|
||||
ingested_at = datetime.now(timezone.utc)
|
||||
|
||||
installation = Installation.objects.get()
|
||||
if installation.quota_exceeded_until is not None and ingested_at < installation.quota_exceeded_until:
|
||||
if self.is_quota_still_exceeded(installation, ingested_at):
|
||||
return HttpResponse(status=HTTP_429_TOO_MANY_REQUESTS)
|
||||
|
||||
project = self.get_project_for_request(project_pk, request)
|
||||
@@ -712,7 +718,7 @@ class IngestEnvelopeAPIView(BaseIngestAPIView):
|
||||
# added complexity (conditional transactions both here and in digest_event) is not worth it for modes that are
|
||||
# non-production anyway.
|
||||
installation = Installation.objects.get()
|
||||
if installation.quota_exceeded_until is not None and ingested_at < installation.quota_exceeded_until:
|
||||
if self.is_quota_still_exceeded(installation, ingested_at):
|
||||
return HttpResponse(status=HTTP_429_TOO_MANY_REQUESTS)
|
||||
|
||||
if "dsn" in envelope_headers:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("phonehome", "0003_installation_ingest_quotas"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="installation",
|
||||
name="quota_exceeded_reason",
|
||||
field=models.CharField(default="null", max_length=255),
|
||||
),
|
||||
]
|
||||
22
phonehome/migrations/0005_reset_quota_exceeded_until.py
Normal file
22
phonehome/migrations/0005_reset_quota_exceeded_until.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def reset_quota_exceeded_until(apps, schema_editor):
|
||||
# Reset the quota_exceeded_until field for all Installation records. Since `quota_exceeded_until` is an optimization
|
||||
# (saves checkes) doing this is never "incorrect" (at the cost of one ingestion per project).
|
||||
# We do it here to ensure that there are no records with a value of `quota_exceeded_until` but without a value for
|
||||
# the new field `quota_exceeded_reason`. (from now on, the 2 will always be set together, but the field is new)
|
||||
|
||||
Installation = apps.get_model("phonehome", "Installation")
|
||||
Installation.objects.filter(quota_exceeded_until__isnull=False).update(quota_exceeded_until=None)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("phonehome", "0004_installation_quota_exceeded_reason"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(reset_quota_exceeded_until, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -19,6 +19,7 @@ class Installation(models.Model):
|
||||
# ingestion/digestion quota
|
||||
email_quota_usage = models.TextField(null=False, default='{"per_month": {}}')
|
||||
quota_exceeded_until = models.DateTimeField(null=True, blank=True)
|
||||
quota_exceeded_reason = models.CharField(max_length=255, null=False, default="null")
|
||||
next_quota_check = models.PositiveIntegerField(null=False, default=0)
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user