diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index d3680ec616..e0a7852dde 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -633,6 +633,24 @@ def do_change_remote_server_plan_type(remote_server: RemoteZulipServer, plan_typ ) +@transaction.atomic +def do_deactivate_remote_server(remote_server: RemoteZulipServer) -> None: + if remote_server.deactivated: + billing_logger.warning( + f"Cannot deactivate remote server with ID {remote_server.id}, " + "server has already been deactivated." + ) + return + + remote_server.deactivated = True + remote_server.save(update_fields=["deactivated"]) + RemoteZulipServerAuditLog.objects.create( + event_type=RealmAuditLog.REMOTE_SERVER_DEACTIVATED, + server=remote_server, + event_time=timezone_now(), + ) + + # Only used for cloud signups @catch_stripe_errors def process_initial_upgrade( diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index 974928428c..5674e37565 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -39,6 +39,7 @@ from corporate.lib.stripe import ( customer_has_credit_card_as_default_payment_method, do_change_remote_server_plan_type, do_create_stripe_customer, + do_deactivate_remote_server, downgrade_small_realms_behind_on_payments_as_needed, get_discount_for_realm, get_latest_seat_count, @@ -4448,6 +4449,36 @@ class BillingHelpersTest(ZulipTestCase): self.assertEqual(remote_realm_audit_log.extra_data, str(expected_extra_data)) self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_STANDARD) + def test_deactivate_remote_server(self) -> None: + server_uuid = str(uuid.uuid4()) + remote_server = RemoteZulipServer.objects.create( + uuid=server_uuid, + api_key="magic_secret_api_key", + hostname="demo.example.com", + contact_email="email@example.com", + ) + self.assertFalse(remote_server.deactivated) + + do_deactivate_remote_server(remote_server) + + remote_server = RemoteZulipServer.objects.get(uuid=server_uuid) + remote_realm_audit_log = RemoteZulipServerAuditLog.objects.filter( + event_type=RealmAuditLog.REMOTE_SERVER_DEACTIVATED + ).last() + assert remote_realm_audit_log is not None + self.assertTrue(remote_server.deactivated) + + # Try to deactivate a remote server that is already deactivated + with self.assertLogs("corporate.stripe", "WARN") as warning_log: + do_deactivate_remote_server(remote_server) + self.assertEqual( + warning_log.output, + [ + "WARNING:corporate.stripe:Cannot deactivate remote server with ID " + f"{remote_server.id}, server has already been deactivated." + ], + ) + class LicenseLedgerTest(StripeTestCase): def test_add_plan_renewal_if_needed(self) -> None: diff --git a/zerver/models.py b/zerver/models.py index 90149601e0..71f646a992 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -3916,9 +3916,12 @@ class AbstractRealmAuditLog(models.Model): STREAM_MESSAGE_RETENTION_DAYS_CHANGED = 605 # The following values are only for RemoteZulipServerAuditLog - # Values are chosen to be 10000 greater than the value in RealmAuditLog. + # Values should be exactly 10000 greater than the corresponding + # value used for the same purpose in RealmAuditLog (e.g. + # REALM_DEACTIVATED = 201, and REMOTE_SERVER_DEACTIVATED = 10201). REMOTE_SERVER_CREATED = 10215 REMOTE_SERVER_PLAN_TYPE_CHANGED = 10204 + REMOTE_SERVER_DEACTIVATED = 10201 event_type: int = models.PositiveSmallIntegerField() diff --git a/zilencer/migrations/0023_remotezulipserver_deactivated.py b/zilencer/migrations/0023_remotezulipserver_deactivated.py new file mode 100644 index 0000000000..ada8ae05e0 --- /dev/null +++ b/zilencer/migrations/0023_remotezulipserver_deactivated.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-15 17:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("zilencer", "0022_remotezulipserver_create_audit_log_backfill"), + ] + + operations = [ + migrations.AddField( + model_name="remotezulipserver", + name="deactivated", + field=models.BooleanField(default=False), + ), + ] diff --git a/zilencer/models.py b/zilencer/models.py index 9e3e1ddca3..0cc0089f92 100644 --- a/zilencer/models.py +++ b/zilencer/models.py @@ -37,6 +37,9 @@ class RemoteZulipServer(models.Model): contact_email: str = models.EmailField(blank=True, null=False) last_updated: datetime.datetime = models.DateTimeField("last updated", auto_now=True) + # Whether the server registration has been deactivated. + deactivated: bool = models.BooleanField(default=False) + # Plan types for self-hosted customers PLAN_TYPE_SELF_HOSTED = 1 PLAN_TYPE_STANDARD = 102