remote_billing: Remove code migrating Legacy plan from server to realms.

We no longer want to migrate Legacy plans from server to realms, since
Legacy plans are not really a thing in the original sense anymore, since
February 15th.

Now they're just a tool to give temporary extensions of access to the
push notification service for users, when needed. And as such, it makes
no sense to migrate like that.

The remaining code in this function is for migrating (any) plan from the
server object to the realm object, if the server has just a single
realm.
This commit is contained in:
Mateusz Mandera 2024-02-21 04:06:14 +01:00 committed by Tim Abbott
parent 2598596ad2
commit ea863bab5b
4 changed files with 61 additions and 173 deletions

View File

@ -23,7 +23,6 @@ from corporate.models import (
get_customer_by_remote_server, get_customer_by_remote_server,
) )
from corporate.views.remote_billing_page import generate_confirmation_link_for_server_deactivation from corporate.views.remote_billing_page import generate_confirmation_link_for_server_deactivation
from zerver.actions.realm_settings import do_deactivate_realm
from zerver.lib.exceptions import RemoteRealmServerMismatchError from zerver.lib.exceptions import RemoteRealmServerMismatchError
from zerver.lib.rate_limiter import RateLimitedIPAddr from zerver.lib.rate_limiter import RateLimitedIPAddr
from zerver.lib.remote_server import send_server_data_to_push_bouncer from zerver.lib.remote_server import send_server_data_to_push_bouncer
@ -662,98 +661,6 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
], ],
) )
@responses.activate
def test_transfer_legacy_plan_from_server_to_all_realms(self) -> None:
self.login("desdemona")
desdemona = self.example_user("desdemona")
# Assert current server is not on any plan.
self.assertIsNone(get_customer_by_remote_server(self.server))
start_date = timezone_now()
end_date = add_months(timezone_now(), 10)
# Migrate server to legacy to plan.
server_billing_session = RemoteServerBillingSession(self.server)
server_billing_session.migrate_customer_to_legacy_plan(start_date, end_date)
server_customer = server_billing_session.get_customer()
assert server_customer is not None
server_plan = get_current_plan_by_customer(server_customer)
assert server_plan is not None
self.assertEqual(self.server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY)
self.assertEqual(server_plan.tier, CustomerPlan.TIER_SELF_HOSTED_LEGACY)
self.assertEqual(server_plan.status, CustomerPlan.ACTIVE)
# There are four test realms on this server:
# <Realm: zulipinternal 1>, <Realm: zephyr 3>, <Realm: lear 4>, <Realm: zulip 2>
self.assert_length(Realm.objects.all(), 4)
# Make lear deactivated, to have verification for that case.
do_deactivate_realm(Realm.objects.get(string_id="lear"), acting_user=None)
# Delete any existing remote realms.
RemoteRealm.objects.all().delete()
# First, set a sponsorship as pending.
# TODO: Ideally, we'd submit a proper sponsorship request.
server_customer.sponsorship_pending = True
server_customer.save()
# Send server data to push bouncer.
self.add_mock_response()
send_server_data_to_push_bouncer(consider_usage_statistics=False)
# Login to plan management.
result = self.execute_remote_billing_authentication_flow(
desdemona, return_from_auth_url=True
)
self.assertEqual(result.status_code, 200)
self.assert_in_response("Plan management not available", result)
# RemoteRealm objects should be created for all realms on the server.
self.assert_length(RemoteRealm.objects.all(), 4)
# Server's plan should not have been migrated yet.
self.server.refresh_from_db()
self.assertEqual(self.server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY)
# Now clear sponsorship_pending.
# TODO: Ideally, this would approve the sponsorship.
server_customer.sponsorship_pending = False
server_customer.save()
# Login to plan management. Performs customer migration from server to realms.
result = self.execute_remote_billing_authentication_flow(
desdemona, return_from_auth_url=False
)
self.assertEqual(result.status_code, 302)
# Server plan status was reset
self.server.refresh_from_db()
self.assertEqual(self.server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED)
# Check no CustomerPlan exists for server.
self.assertIsNone(get_current_plan_by_customer(server_customer))
# Check legacy CustomerPlan exists for all realms except bot realm.
no_customer_plan_realms = set()
for remote_realm in RemoteRealm.objects.all():
if remote_realm.is_system_bot_realm or remote_realm.realm_deactivated:
self.assertIsNone(get_customer_by_remote_realm(remote_realm))
no_customer_plan_realms.add(remote_realm.host.split(".")[0])
continue
customer = get_customer_by_remote_realm(remote_realm)
assert customer is not None
plan = get_current_plan_by_customer(customer)
assert plan is not None
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY)
self.assertEqual(plan.tier, CustomerPlan.TIER_SELF_HOSTED_LEGACY)
self.assertEqual(plan.status, CustomerPlan.ACTIVE)
self.assertEqual(plan.billing_cycle_anchor, start_date)
self.assertEqual(plan.end_date, end_date)
self.assertEqual(no_customer_plan_realms, {"zulipinternal", "lear"})
@responses.activate @responses.activate
def test_transfer_legacy_plan_scheduled_for_upgrade_from_server_to_realm( def test_transfer_legacy_plan_scheduled_for_upgrade_from_server_to_realm(
self, self,

View File

@ -91,6 +91,7 @@ from corporate.models import (
get_current_plan_by_customer, get_current_plan_by_customer,
get_current_plan_by_realm, get_current_plan_by_realm,
get_customer_by_realm, get_customer_by_realm,
get_customer_by_remote_realm,
) )
from corporate.tests.test_remote_billing import RemoteRealmBillingTestCase, RemoteServerTestCase from corporate.tests.test_remote_billing import RemoteRealmBillingTestCase, RemoteServerTestCase
from corporate.views.remote_billing_page import generate_confirmation_link_for_server_deactivation from corporate.views.remote_billing_page import generate_confirmation_link_for_server_deactivation
@ -6699,33 +6700,27 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
@responses.activate @responses.activate
@mock_stripe() @mock_stripe()
def test_schedule_legacy_plan_upgrade_to_fixed_price_plan(self, *mocks: Mock) -> None: def test_schedule_legacy_plan_upgrade_to_fixed_price_plan(self, *mocks: Mock) -> None:
self.login("hamlet")
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
remote_server = RemoteZulipServer.objects.get(hostname="demo.example.com") remote_realm = RemoteRealm.objects.get(uuid=hamlet.realm.uuid)
server_billing_session = RemoteServerBillingSession(remote_server=remote_server) remote_realm_billing_session = RemoteRealmBillingSession(remote_realm=remote_realm)
# Migrate server to legacy plan. # Migrate realm to legacy plan.
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):
start_date = timezone_now() start_date = timezone_now()
end_date = add_months(start_date, months=3) end_date = add_months(start_date, months=3)
server_billing_session.migrate_customer_to_legacy_plan(start_date, end_date) remote_realm_billing_session.migrate_customer_to_legacy_plan(start_date, end_date)
self.add_mock_response() self.add_mock_response()
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):
send_server_data_to_push_bouncer(consider_usage_statistics=False) send_server_data_to_push_bouncer(consider_usage_statistics=False)
# Login. Performs customer migration from server to realms.
self.execute_remote_billing_authentication_flow(hamlet)
self.remote_realm.refresh_from_db()
customer = Customer.objects.get(remote_realm=self.remote_realm) customer = Customer.objects.get(remote_realm=self.remote_realm)
legacy_plan = get_current_plan_by_customer(customer) legacy_plan = get_current_plan_by_customer(customer)
assert legacy_plan is not None assert legacy_plan is not None
self.assertEqual(legacy_plan.tier, CustomerPlan.TIER_SELF_HOSTED_LEGACY) self.assertEqual(legacy_plan.tier, CustomerPlan.TIER_SELF_HOSTED_LEGACY)
self.assertEqual(legacy_plan.next_invoice_date, end_date) self.assertEqual(legacy_plan.next_invoice_date, end_date)
self.logout()
self.login("iago") self.login("iago")
# Schedule a fixed-price business plan at current plan end_date. # Schedule a fixed-price business plan at current plan end_date.
@ -6759,7 +6754,7 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
self.logout() self.logout()
self.login("hamlet") self.login("hamlet")
self.execute_remote_billing_authentication_flow( self.execute_remote_billing_authentication_flow(
hamlet, expect_tos=False, confirm_tos=False, first_time_login=False hamlet, expect_tos=False, confirm_tos=True, first_time_login=True
) )
# Schedule upgrade to business plan # Schedule upgrade to business plan
@ -7050,6 +7045,14 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
self.assertEqual(server_customer_plan.status, CustomerPlan.ACTIVE) self.assertEqual(server_customer_plan.status, CustomerPlan.ACTIVE)
self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY) self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY)
# The plan gets migrated if there's only a single human realm.
Realm.objects.exclude(string_id__in=["zulip", "zulipinternal"]).delete()
# First, set a sponsorship as pending.
# TODO: Ideally, we'd submit a proper sponsorship request.
server_customer.sponsorship_pending = True
server_customer.save()
# Upload data. # Upload data.
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):
self.add_mock_response() self.add_mock_response()
@ -7059,27 +7062,42 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
billing_base_url = self.billing_session.billing_base_url billing_base_url = self.billing_session.billing_base_url
# Login. Performs customer migration from server to realms. # Login. The server has a pending sponsorship, in which case migrating
# can't be done, as that'd be a fairly confusing process.
result = self.execute_remote_billing_authentication_flow(hamlet, return_from_auth_url=True)
self.assertEqual(result.status_code, 200)
self.assert_in_response("Plan management not available", result)
# Server's plan should not have been migrated yet.
self.server.refresh_from_db()
self.assertEqual(self.server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY)
# Now clear the pending sponsorship state, which will allow login
# and migration to proceed.
# TODO: Ideally, this would approve the sponsorship and then be testing
# the migration of the Community plan.
server_customer.sponsorship_pending = False
server_customer.save()
# Login. Performs customer migration from server to realm.
result = self.execute_remote_billing_authentication_flow(hamlet) result = self.execute_remote_billing_authentication_flow(hamlet)
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], f"{billing_base_url}/plans/") self.assertEqual(result["Location"], f"{billing_base_url}/plans/")
remote_server.refresh_from_db() remote_server.refresh_from_db()
server_customer_plan.refresh_from_db() remote_realm = RemoteRealm.objects.get(uuid=hamlet.realm.uuid)
self.assertEqual(server_customer_plan.status, CustomerPlan.ENDED) # The customer object was moved, together with the plan, from server to realm.
customer = get_customer_by_remote_realm(remote_realm)
assert customer is not None
self.assertEqual(server_customer, customer)
self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED) self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED)
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY)
remote_realms = RemoteRealm.objects.filter( customer_plan = get_current_plan_by_customer(customer)
server=remote_server, assert customer_plan is not None
plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY, self.assertEqual(customer_plan.tier, CustomerPlan.TIER_SELF_HOSTED_LEGACY)
is_system_bot_realm=False, self.assertEqual(customer_plan.status, CustomerPlan.ACTIVE)
)
for remote_realm in remote_realms:
customer = Customer.objects.get(remote_realm=remote_realm)
customer_plan = get_current_plan_by_customer(customer)
assert customer_plan is not None
self.assertEqual(customer_plan.tier, CustomerPlan.TIER_SELF_HOSTED_LEGACY)
self.assertEqual(customer_plan.status, CustomerPlan.ACTIVE)
# upgrade to business plan # upgrade to business plan
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):
@ -7090,17 +7108,15 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):
stripe_customer = self.add_card_and_upgrade() stripe_customer = self.add_card_and_upgrade()
zulip_realm_customer = Customer.objects.get(stripe_customer_id=stripe_customer.id) self.assertEqual(customer, Customer.objects.get(stripe_customer_id=stripe_customer.id))
zulip_realm_plan = CustomerPlan.objects.get( business_plan = CustomerPlan.objects.get(customer=customer, status=CustomerPlan.ACTIVE)
customer=zulip_realm_customer, status=CustomerPlan.ACTIVE self.assertEqual(business_plan.tier, CustomerPlan.TIER_SELF_HOSTED_BUSINESS)
)
self.assertEqual(zulip_realm_plan.tier, CustomerPlan.TIER_SELF_HOSTED_BUSINESS)
realm_user_count = UserProfile.objects.filter( realm_user_count = UserProfile.objects.filter(
realm=hamlet.realm, is_bot=False, is_active=True realm=hamlet.realm, is_bot=False, is_active=True
).count() ).count()
licenses = max( licenses = max(
realm_user_count, self.billing_session.min_licenses_for_plan(zulip_realm_plan.tier) realm_user_count, self.billing_session.min_licenses_for_plan(business_plan.tier)
) )
with time_machine.travel(self.now + timedelta(days=1), tick=False): with time_machine.travel(self.now + timedelta(days=1), tick=False):
response = self.client_get(f"{billing_base_url}/billing/", subdomain="selfhosting") response = self.client_get(f"{billing_base_url}/billing/", subdomain="selfhosting")
@ -7130,11 +7146,11 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
"/billing/plan", "/billing/plan",
{"status": CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE}, {"status": CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE},
) )
expected_log = f"INFO:corporate.stripe:Change plan status: Customer.id: {zulip_realm_customer.id}, CustomerPlan.id: {zulip_realm_plan.id}, status: {CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE}" expected_log = f"INFO:corporate.stripe:Change plan status: Customer.id: {customer.id}, CustomerPlan.id: {business_plan.id}, status: {CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE}"
self.assertEqual(m.output[0], expected_log) self.assertEqual(m.output[0], expected_log)
self.assert_json_success(response) self.assert_json_success(response)
zulip_realm_plan.refresh_from_db() business_plan.refresh_from_db()
self.assertEqual(zulip_realm_plan.licenses_at_next_renewal(), None) self.assertEqual(business_plan.licenses_at_next_renewal(), None)
@responses.activate @responses.activate
@mock_stripe() @mock_stripe()
@ -7277,14 +7293,16 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
@responses.activate @responses.activate
@mock_stripe() @mock_stripe()
def test_invoice_scheduled_upgrade_realm_legacy_plan(self, *mocks: Mock) -> None: def test_invoice_scheduled_upgrade_realm_legacy_plan(self, *mocks: Mock) -> None:
remote_server = RemoteZulipServer.objects.get(hostname="demo.example.com") hamlet = self.example_user("hamlet")
server_billing_session = RemoteServerBillingSession(remote_server=remote_server)
# Migrate server to legacy plan. remote_realm = RemoteRealm.objects.get(uuid=hamlet.realm.uuid)
remote_realm_billing_session = RemoteRealmBillingSession(remote_realm=remote_realm)
# Migrate realm to legacy plan.
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):
start_date = timezone_now() start_date = timezone_now()
end_date = add_months(start_date, months=3) end_date = add_months(start_date, months=3)
server_billing_session.migrate_customer_to_legacy_plan(start_date, end_date) remote_realm_billing_session.migrate_customer_to_legacy_plan(start_date, end_date)
# Upload data. # Upload data.
self.add_mock_response() self.add_mock_response()
@ -7292,11 +7310,9 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
send_server_data_to_push_bouncer(consider_usage_statistics=False) send_server_data_to_push_bouncer(consider_usage_statistics=False)
self.login("hamlet") self.login("hamlet")
hamlet = self.example_user("hamlet")
# Login. Performs customer migration from server to realms. # Login.
self.execute_remote_billing_authentication_flow(hamlet) self.execute_remote_billing_authentication_flow(hamlet)
remote_server.refresh_from_db()
# Schedule upgrade to business plan # Schedule upgrade to business plan
with time_machine.travel(self.now, tick=False): with time_machine.travel(self.now, tick=False):

View File

@ -65,7 +65,7 @@ from zilencer.models import (
RemoteZulipServer, RemoteZulipServer,
get_remote_server_by_uuid, get_remote_server_by_uuid,
) )
from zilencer.views import handle_customer_migration_from_server_to_realms from zilencer.views import handle_customer_migration_from_server_to_realm
billing_logger = logging.getLogger("corporate.stripe") billing_logger = logging.getLogger("corporate.stripe")
@ -200,7 +200,7 @@ def remote_realm_billing_finalize_login(
raise AssertionError raise AssertionError
try: try:
handle_customer_migration_from_server_to_realms(server=remote_server) handle_customer_migration_from_server_to_realm(server=remote_server)
except JsonableError: except JsonableError:
# JsonableError should be propagated up, as they are meant to convey # JsonableError should be propagated up, as they are meant to convey
# a json error response to be returned. # a json error response to be returned.

View File

@ -935,7 +935,7 @@ def get_human_user_realm_uuids(
@transaction.atomic @transaction.atomic
def handle_customer_migration_from_server_to_realms( def handle_customer_migration_from_server_to_realm(
server: RemoteZulipServer, server: RemoteZulipServer,
) -> None: ) -> None:
server_billing_session = RemoteServerBillingSession(server) server_billing_session = RemoteServerBillingSession(server)
@ -967,42 +967,7 @@ def handle_customer_migration_from_server_to_realms(
event_time = timezone_now() event_time = timezone_now()
remote_realm_audit_logs = [] remote_realm_audit_logs = []
if ( if len(realm_uuids) == 1:
server_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY
and server_plan.status == CustomerPlan.ACTIVE
):
assert server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY
assert server_plan.end_date is not None
remote_realms = RemoteRealm.objects.filter(
uuid__in=realm_uuids, server=server, plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED
)
# Verify that all the realms are on self hosted plan.
assert remote_realms.count() == len(realm_uuids)
# End existing plan for server.
server_plan.status = CustomerPlan.ENDED
server_plan.save(update_fields=["status"])
server.plan_type = RemoteZulipServer.PLAN_TYPE_SELF_MANAGED
server.save(update_fields=["plan_type"])
# Create new legacy plan for each remote realm.
for remote_realm in remote_realms:
RemoteRealmBillingSession(remote_realm).migrate_customer_to_legacy_plan(
server_plan.billing_cycle_anchor, server_plan.end_date
)
remote_realm_audit_logs.append(
RemoteRealmAuditLog(
server=server,
remote_realm=remote_realm,
event_type=RemoteRealmAuditLog.REMOTE_PLAN_TRANSFERRED_SERVER_TO_REALM,
event_time=event_time,
# No extra_data since there was no real change in any RemoteRealm attribute.
)
)
elif len(realm_uuids) == 1:
# Here, we have exactly one non-system-bot realm, and some # Here, we have exactly one non-system-bot realm, and some
# sort of plan on the server; move it to the realm. # sort of plan on the server; move it to the realm.
remote_realm = RemoteRealm.objects.get(uuid=realm_uuids[0], server=server) remote_realm = RemoteRealm.objects.get(uuid=realm_uuids[0], server=server)