mirror of https://github.com/zulip/zulip.git
zilencer: Handle deleted realms nicely at server/analytics.
This commit is contained in:
parent
8102519242
commit
fb5137f8b5
|
@ -4861,6 +4861,7 @@ class AbstractRealmAuditLog(models.Model):
|
||||||
# via send_server_data_to_push_bouncer.
|
# via send_server_data_to_push_bouncer.
|
||||||
REMOTE_REALM_VALUE_UPDATED = 20001
|
REMOTE_REALM_VALUE_UPDATED = 20001
|
||||||
REMOTE_PLAN_TRANSFERRED_SERVER_TO_REALM = 20002
|
REMOTE_PLAN_TRANSFERRED_SERVER_TO_REALM = 20002
|
||||||
|
REMOTE_REALM_LOCALLY_DELETED = 20003
|
||||||
|
|
||||||
event_type = models.PositiveSmallIntegerField()
|
event_type = models.PositiveSmallIntegerField()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
@ -1998,19 +1999,25 @@ class AnalyticsBouncerTest(BouncerTestCase):
|
||||||
|
|
||||||
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
|
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_non_existent_realm_uuid(self) -> None:
|
def test_deleted_realm(self) -> None:
|
||||||
self.add_mock_response()
|
self.add_mock_response()
|
||||||
|
logger = logging.getLogger("zulip.analytics")
|
||||||
|
|
||||||
realm_info = get_realms_info_for_push_bouncer()
|
realm_info = get_realms_info_for_push_bouncer()
|
||||||
|
|
||||||
# Hard-delete a realm to test the non existent realm uuid case.
|
# Hard-delete a realm to test the non existent realm uuid case.
|
||||||
realm = Realm.objects.order_by("id").first()
|
realm = get_realm("zephyr")
|
||||||
assert realm is not None
|
assert realm is not None
|
||||||
deleted_realm_uuid = realm.uuid
|
deleted_realm_uuid = realm.uuid
|
||||||
realm.delete()
|
realm.delete()
|
||||||
|
|
||||||
|
# This mock causes us to still send data to the bouncer as if the realm existed,
|
||||||
|
# causing the bouncer to include its corresponding info in the response. Through
|
||||||
|
# that, we're testing our graceful handling of seeing a non-existent realm uuid
|
||||||
|
# in that response.
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"zerver.lib.remote_server.get_realms_info_for_push_bouncer", return_value=realm_info
|
"zerver.lib.remote_server.get_realms_info_for_push_bouncer", return_value=realm_info
|
||||||
) as m, self.assertLogs("zulip.analytics", level="WARNING") as analytics_logger:
|
) as m, self.assertLogs(logger, level="WARNING") as analytics_logger:
|
||||||
send_server_data_to_push_bouncer(consider_usage_statistics=False)
|
send_server_data_to_push_bouncer(consider_usage_statistics=False)
|
||||||
m.assert_called()
|
m.assert_called()
|
||||||
realms = Realm.objects.all()
|
realms = Realm.objects.all()
|
||||||
|
@ -2026,6 +2033,26 @@ class AnalyticsBouncerTest(BouncerTestCase):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Now we want to test the other side of this - bouncer's handling
|
||||||
|
# of a deleted realm.
|
||||||
|
with self.assertLogs(logger, level="WARNING") as analytics_logger:
|
||||||
|
# This time the logger shouldn't get triggered - because the bouncer doesn't
|
||||||
|
# include .realm_locally_deleted realms in its response.
|
||||||
|
# Note: This is hacky, because until Python 3.10 we don't have access to
|
||||||
|
# assertNoLogs - and regular assertLogs demands that the logger gets triggered.
|
||||||
|
# So we do a dummy warning ourselves here, to satisfy it.
|
||||||
|
# TODO: Replace this with assertNoLogs once we fully upgrade to Python 3.10.
|
||||||
|
logger.warning("Dummy warning")
|
||||||
|
send_server_data_to_push_bouncer(consider_usage_statistics=False)
|
||||||
|
remote_realm_for_deleted_realm = RemoteRealm.objects.get(uuid=deleted_realm_uuid)
|
||||||
|
self.assertEqual(remote_realm_for_deleted_realm.registration_deactivated, True)
|
||||||
|
self.assertEqual(remote_realm_for_deleted_realm.realm_locally_deleted, True)
|
||||||
|
self.assertEqual(analytics_logger.output, ["WARNING:zulip.analytics:Dummy warning"])
|
||||||
|
|
||||||
|
audit_log = RemoteRealmAuditLog.objects.latest("id")
|
||||||
|
self.assertEqual(audit_log.event_type, RemoteRealmAuditLog.REMOTE_REALM_LOCALLY_DELETED)
|
||||||
|
self.assertEqual(audit_log.remote_realm, remote_realm_for_deleted_realm)
|
||||||
|
|
||||||
|
|
||||||
class PushNotificationTest(BouncerTestCase):
|
class PushNotificationTest(BouncerTestCase):
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.8 on 2023-12-15 13:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zilencer", "0055_remoteserverbillinguser_tos_version"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="remoterealm",
|
||||||
|
name="realm_locally_deleted",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -147,6 +147,9 @@ class RemoteRealm(models.Model):
|
||||||
registration_deactivated = models.BooleanField(default=False)
|
registration_deactivated = models.BooleanField(default=False)
|
||||||
# Whether the realm has been deactivated on the remote server.
|
# Whether the realm has been deactivated on the remote server.
|
||||||
realm_deactivated = models.BooleanField(default=False)
|
realm_deactivated = models.BooleanField(default=False)
|
||||||
|
# Whether we believe the remote server deleted this realm
|
||||||
|
# from the database.
|
||||||
|
realm_locally_deleted = models.BooleanField(default=False)
|
||||||
|
|
||||||
# When the realm was created on the remote server.
|
# When the realm was created on the remote server.
|
||||||
realm_date_created = models.DateTimeField()
|
realm_date_created = models.DateTimeField()
|
||||||
|
|
|
@ -654,7 +654,21 @@ def update_remote_realm_data_for_server(
|
||||||
server: RemoteZulipServer, server_realms_info: List[RealmDataForAnalytics]
|
server: RemoteZulipServer, server_realms_info: List[RealmDataForAnalytics]
|
||||||
) -> None:
|
) -> None:
|
||||||
uuids = [realm.uuid for realm in server_realms_info]
|
uuids = [realm.uuid for realm in server_realms_info]
|
||||||
already_registered_remote_realms = RemoteRealm.objects.filter(uuid__in=uuids, server=server)
|
all_registered_remote_realms_for_server = list(RemoteRealm.objects.filter(server=server))
|
||||||
|
already_registered_remote_realms = [
|
||||||
|
remote_realm
|
||||||
|
for remote_realm in all_registered_remote_realms_for_server
|
||||||
|
if remote_realm.uuid in uuids
|
||||||
|
]
|
||||||
|
# RemoteRealm registrations that we have for this server, but aren't
|
||||||
|
# present in the data sent to us. We assume this to mean the server
|
||||||
|
# must have deleted those realms from the database.
|
||||||
|
remote_realms_missing_from_server_data = [
|
||||||
|
remote_realm
|
||||||
|
for remote_realm in all_registered_remote_realms_for_server
|
||||||
|
if remote_realm.uuid not in uuids
|
||||||
|
]
|
||||||
|
|
||||||
already_registered_uuids = {
|
already_registered_uuids = {
|
||||||
remote_realm.uuid for remote_realm in already_registered_remote_realms
|
remote_realm.uuid for remote_realm in already_registered_remote_realms
|
||||||
}
|
}
|
||||||
|
@ -689,6 +703,10 @@ def update_remote_realm_data_for_server(
|
||||||
# Update RemoteRealm entries, for which the corresponding realm's info has changed
|
# Update RemoteRealm entries, for which the corresponding realm's info has changed
|
||||||
# (for the attributes that make sense to sync like this).
|
# (for the attributes that make sense to sync like this).
|
||||||
for remote_realm in already_registered_remote_realms:
|
for remote_realm in already_registered_remote_realms:
|
||||||
|
# TODO: We'll also want to check if .realm_locally_deleted is True, and if so,
|
||||||
|
# toggle it off (and potentially restore registration_deactivated=True too),
|
||||||
|
# since the server is now sending us data for this realm again.
|
||||||
|
|
||||||
modified = False
|
modified = False
|
||||||
realm = uuid_to_realm_dict[str(remote_realm.uuid)]
|
realm = uuid_to_realm_dict[str(remote_realm.uuid)]
|
||||||
for remote_realm_attr, realm_dict_key in [
|
for remote_realm_attr, realm_dict_key in [
|
||||||
|
@ -739,6 +757,32 @@ def update_remote_realm_data_for_server(
|
||||||
)
|
)
|
||||||
RemoteRealmAuditLog.objects.bulk_create(remote_realm_audit_logs)
|
RemoteRealmAuditLog.objects.bulk_create(remote_realm_audit_logs)
|
||||||
|
|
||||||
|
remote_realms_to_update = []
|
||||||
|
remote_realm_audit_logs = []
|
||||||
|
for remote_realm in remote_realms_missing_from_server_data:
|
||||||
|
if not remote_realm.realm_locally_deleted:
|
||||||
|
# Otherwise we already knew about this, so nothing to do.
|
||||||
|
remote_realm.realm_locally_deleted = True
|
||||||
|
remote_realm.registration_deactivated = True
|
||||||
|
|
||||||
|
remote_realm_audit_logs.append(
|
||||||
|
RemoteRealmAuditLog(
|
||||||
|
server=server,
|
||||||
|
remote_id=None,
|
||||||
|
remote_realm=remote_realm,
|
||||||
|
realm_id=None,
|
||||||
|
event_type=RemoteRealmAuditLog.REMOTE_REALM_LOCALLY_DELETED,
|
||||||
|
event_time=now,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
remote_realms_to_update.append(remote_realm)
|
||||||
|
|
||||||
|
RemoteRealm.objects.bulk_update(
|
||||||
|
remote_realms_to_update,
|
||||||
|
["realm_locally_deleted", "registration_deactivated"],
|
||||||
|
)
|
||||||
|
RemoteRealmAuditLog.objects.bulk_create(remote_realm_audit_logs)
|
||||||
|
|
||||||
|
|
||||||
def get_human_user_realm_uuids(realms: List[RealmDataForAnalytics]) -> List[UUID]: # nocoverage
|
def get_human_user_realm_uuids(realms: List[RealmDataForAnalytics]) -> List[UUID]: # nocoverage
|
||||||
billable_realm_uuids = []
|
billable_realm_uuids = []
|
||||||
|
@ -998,7 +1042,7 @@ def remote_server_post_analytics(
|
||||||
remote_server_billing_session.sync_license_ledger_if_needed()
|
remote_server_billing_session.sync_license_ledger_if_needed()
|
||||||
|
|
||||||
remote_realm_dict: Dict[str, RemoteRealmDictValue] = {}
|
remote_realm_dict: Dict[str, RemoteRealmDictValue] = {}
|
||||||
remote_realms = RemoteRealm.objects.filter(server=server)
|
remote_realms = RemoteRealm.objects.filter(server=server, realm_locally_deleted=False)
|
||||||
for remote_realm in remote_realms:
|
for remote_realm in remote_realms:
|
||||||
uuid = str(remote_realm.uuid)
|
uuid = str(remote_realm.uuid)
|
||||||
billing_session = RemoteRealmBillingSession(remote_realm)
|
billing_session = RemoteRealmBillingSession(remote_realm)
|
||||||
|
|
Loading…
Reference in New Issue