0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-12-01 04:12:23 +01:00
posthog/ee/billing/test/test_quota_limiting.py
Eric Duong 820e6a78a0
chore(data-warehouse): dw billing (#18168)
* create airbyte source api

* comment test

* rename and add connection

* comment out test

* frontend updates and additional API calls on airbyte

* some more UI and retrieve endpoint

* restore lock file

* connecting the dots

* add destination logic

* make destinatino parquet

* ui updates

* add task to billing usage report

* update task

* rename data

* more renaming

* migration

* remove test

* rename

* Update UI snapshots for `chromium` (2)

* missing file

* typing

* remove unneeded field

* remove 'billable' and unnecessary field

* add rollback deletions if one fo the related resources fails

* fix types

* change type

* remove fluff

* remove migration

* Update UI snapshots for `chromium` (1)

* new attempt

* add tests

* add comment

* move around ph_client

* Update query snapshots

* Update query snapshots

* Update query snapshots

* Update query snapshots

* Update UI snapshots for `chromium` (2)

* Update UI snapshots for `chromium` (2)

* Update UI snapshots for `chromium` (2)

* Update UI snapshots for `chromium` (2)

* Update query snapshots

* Update UI snapshots for `chromium` (2)

* Update UI snapshots for `chromium` (2)

* add job

* usage report task and tests

* test

* add limit checker task

* add conversion

* add tests

* address comments

* Update query snapshots

* Update query snapshots

* update test

* revised

* fix tests

* Update query snapshots

* Update query snapshots

* Update query snapshots

* Update query snapshots

* add typing

* update tests

* rename

* typing

* fix tests

* Update query snapshots

* Update query snapshots

* Update query snapshots

* fix tests and types

* fix typo

* naming

* verbose

* remove verbose

* typo

* restore main

* adjust timing

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-16 14:40:31 -05:00

264 lines
11 KiB
Python

import time
from uuid import uuid4
from dateutil.relativedelta import relativedelta
from django.utils import timezone
from django.utils.timezone import now
from freezegun import freeze_time
from ee.billing.quota_limiting import (
QUOTA_LIMITER_CACHE_KEY,
QuotaResource,
list_limited_team_attributes,
org_quota_limited_until,
replace_limited_team_tokens,
set_org_usage_summary,
sync_org_quota_limits,
update_all_org_billing_quotas,
)
from posthog.api.test.test_team import create_team
from posthog.redis import get_client
from posthog.test.base import BaseTest, _create_event
class TestQuotaLimiting(BaseTest):
def setUp(self) -> None:
super().setUp()
self.redis_client = get_client()
def test_billing_rate_limit_not_set_if_missing_org_usage(self) -> None:
with self.settings(USE_TZ=False):
self.organization.usage = {}
self.organization.save()
distinct_id = str(uuid4())
# we add a bunch of events, so that the organization is over the limit, for events only, but not for recordings
# however, since the organization has no usage set, we should not rate limit
for _ in range(0, 10):
_create_event(
distinct_id=distinct_id,
event="$event1",
properties={"$lib": "$web"},
timestamp=now() - relativedelta(hours=1), # current day
team=self.team,
)
result = update_all_org_billing_quotas()
assert result["events"] == {}
assert result["recordings"] == {}
assert result["rows_synced"] == {}
assert self.redis_client.zrange(f"{QUOTA_LIMITER_CACHE_KEY}events", 0, -1) == []
assert self.redis_client.zrange(f"{QUOTA_LIMITER_CACHE_KEY}recordings", 0, -1) == []
assert self.redis_client.zrange(f"{QUOTA_LIMITER_CACHE_KEY}rows_synced", 0, -1) == []
def test_billing_rate_limit(self) -> None:
with self.settings(USE_TZ=False):
self.organization.usage = {
"events": {"usage": 99, "limit": 100},
"recordings": {"usage": 1, "limit": 100},
"rows_synced": {"usage": 5, "limit": 100},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
self.organization.save()
distinct_id = str(uuid4())
# we add a bunch of events, so that the organization is over the limit
# in this case the org has usage limits, so we want to rate limit for events, but not for recordings
for _ in range(0, 10):
_create_event(
distinct_id=distinct_id,
event="$event1",
properties={"$lib": "$web"},
timestamp=now() - relativedelta(hours=1),
team=self.team,
)
time.sleep(1)
result = update_all_org_billing_quotas()
org_id = str(self.organization.id)
assert result["events"] == {org_id: 1612137599}
assert result["recordings"] == {}
assert result["rows_synced"] == {}
assert self.redis_client.zrange(f"{QUOTA_LIMITER_CACHE_KEY}events", 0, -1) == [
self.team.api_token.encode("UTF-8")
]
assert self.redis_client.zrange(f"{QUOTA_LIMITER_CACHE_KEY}recordings", 0, -1) == []
assert self.redis_client.zrange(f"{QUOTA_LIMITER_CACHE_KEY}rows_synced", 0, -1) == []
self.organization.refresh_from_db()
assert self.organization.usage == {
"events": {"usage": 99, "limit": 100, "todays_usage": 10},
"recordings": {"usage": 1, "limit": 100, "todays_usage": 0},
"rows_synced": {"usage": 5, "limit": 100, "todays_usage": 0},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
def test_set_org_usage_summary_updates_correctly(self):
self.organization.usage = {
"events": {"usage": 99, "limit": 100},
"recordings": {"usage": 1, "limit": 100},
"rows_synced": {"usage": 5, "limit": 100},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
self.organization.save()
new_usage = dict(
events={"usage": 100, "limit": 100},
recordings={"usage": 2, "limit": 100},
rows_synced={"usage": 6, "limit": 100},
period=[
"2021-01-01T00:00:00Z",
"2021-01-31T23:59:59Z",
],
)
assert set_org_usage_summary(self.organization, new_usage=new_usage)
assert self.organization.usage == {
"events": {"usage": 100, "limit": 100, "todays_usage": 0},
"recordings": {"usage": 2, "limit": 100, "todays_usage": 0},
"rows_synced": {"usage": 6, "limit": 100, "todays_usage": 0},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
def test_set_org_usage_summary_does_nothing_if_the_same(self):
self.organization.usage = {
"events": {"usage": 99, "limit": 100, "todays_usage": 10},
"recordings": {"usage": 1, "limit": 100, "todays_usage": 11},
"rows_synced": {"usage": 5, "limit": 100, "todays_usage": 11},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
self.organization.save()
new_usage = dict(
events={"usage": 99, "limit": 100},
recordings={"usage": 1, "limit": 100},
rows_synced={"usage": 5, "limit": 100},
period=[
"2021-01-01T00:00:00Z",
"2021-01-31T23:59:59Z",
],
)
assert not set_org_usage_summary(self.organization, new_usage=new_usage)
assert self.organization.usage == {
"events": {"usage": 99, "limit": 100, "todays_usage": 10},
"recordings": {"usage": 1, "limit": 100, "todays_usage": 11},
"rows_synced": {"usage": 5, "limit": 100, "todays_usage": 11},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
def test_set_org_usage_summary_updates_todays_usage(self):
self.organization.usage = {
"events": {"usage": 99, "limit": 100, "todays_usage": 10},
"recordings": {"usage": 1, "limit": 100, "todays_usage": 11},
"rows_synced": {"usage": 5, "limit": 100, "todays_usage": 11},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
self.organization.save()
assert set_org_usage_summary(
self.organization, todays_usage={"events": 20, "recordings": 21, "rows_synced": 21}
)
assert self.organization.usage == {
"events": {"usage": 99, "limit": 100, "todays_usage": 20},
"recordings": {"usage": 1, "limit": 100, "todays_usage": 21},
"rows_synced": {"usage": 5, "limit": 100, "todays_usage": 21},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
def test_org_quota_limited_until(self):
self.organization.usage = None
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) is None
self.organization.usage = {
"events": {"usage": 99, "limit": 100},
"recordings": {"usage": 1, "limit": 100},
"rows_synced": {"usage": 99, "limit": 100},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) is None
self.organization.usage["events"]["usage"] = 120
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) == 1612137599
self.organization.usage["events"]["usage"] = 90
self.organization.usage["events"]["todays_usage"] = 10
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) == 1612137599
self.organization.usage["events"]["limit"] = None
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) is None
self.organization.usage["recordings"]["usage"] = 1099 # Under limit + buffer
assert org_quota_limited_until(self.organization, QuotaResource.RECORDINGS) is None
self.organization.usage["recordings"]["usage"] = 1100 # Over limit + buffer
assert org_quota_limited_until(self.organization, QuotaResource.RECORDINGS) == 1612137599
assert org_quota_limited_until(self.organization, QuotaResource.ROWS_SYNCED) is None
self.organization.usage["rows_synced"]["usage"] = 101
assert org_quota_limited_until(self.organization, QuotaResource.ROWS_SYNCED) == 1612137599
def test_over_quota_but_not_dropped_org(self):
self.organization.usage = None
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) is None
self.organization.usage = {
"events": {"usage": 100, "limit": 90},
"recordings": {"usage": 100, "limit": 90},
"rows_synced": {"usage": 100, "limit": 90},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
self.organization.never_drop_data = True
assert org_quota_limited_until(self.organization, QuotaResource.EVENTS) is None
assert org_quota_limited_until(self.organization, QuotaResource.RECORDINGS) is None
assert org_quota_limited_until(self.organization, QuotaResource.ROWS_SYNCED) is None
# reset for subsequent tests
self.organization.never_drop_data = False
def test_sync_org_quota_limits(self):
with freeze_time("2021-01-01T12:59:59Z"):
other_team = create_team(organization=self.organization)
now = timezone.now().timestamp()
replace_limited_team_tokens(QuotaResource.EVENTS, {"1234": now + 10000})
replace_limited_team_tokens(QuotaResource.ROWS_SYNCED, {"1337": now + 10000})
self.organization.usage = {
"events": {"usage": 99, "limit": 100},
"recordings": {"usage": 1, "limit": 100},
"rows_synced": {"usage": 35, "limit": 100},
"period": ["2021-01-01T00:00:00Z", "2021-01-31T23:59:59Z"],
}
sync_org_quota_limits(self.organization)
assert list_limited_team_attributes(QuotaResource.EVENTS) == ["1234"]
assert list_limited_team_attributes(QuotaResource.ROWS_SYNCED) == ["1337"]
self.organization.usage["events"]["usage"] = 120
self.organization.usage["rows_synced"]["usage"] = 120
sync_org_quota_limits(self.organization)
assert sorted(list_limited_team_attributes(QuotaResource.EVENTS)) == sorted(
["1234", self.team.api_token, other_team.api_token]
)
# rows_synced uses teams, not tokens
assert sorted(list_limited_team_attributes(QuotaResource.ROWS_SYNCED)) == sorted(
["1337", str(self.team.pk), str(other_team.pk)]
)
self.organization.usage["events"]["usage"] = 80
self.organization.usage["rows_synced"]["usage"] = 36
sync_org_quota_limits(self.organization)
assert sorted(list_limited_team_attributes(QuotaResource.EVENTS)) == sorted(["1234"])
assert sorted(list_limited_team_attributes(QuotaResource.ROWS_SYNCED)) == sorted(["1337"])