api: Add zulip_version and zulip_feature_level in restart event.

This help mobile and terminal clients understand whether a server
restart changed API feature levels or not, which in turn determines
whether they will need to resynchronize their data.

Also add tests and documentation for this previously undocumented
event type.

Fixes: #18205.
This commit is contained in:
m-e-l-u-h-a-n 2021-04-18 14:58:39 +05:30 committed by Tim Abbott
parent d2c18e28a4
commit 65c400e06d
7 changed files with 97 additions and 3 deletions

View File

@ -517,6 +517,8 @@ exports.fixtures = {
restart: { restart: {
type: "restart", type: "restart",
zulip_version: "4.0-dev+git",
zulip_feature_level: 55,
server_generation: 2, server_generation: 2,
immediate: true, immediate: true,
}, },

View File

@ -10,6 +10,11 @@ below features are supported.
## Changes in Zulip 4.0 ## Changes in Zulip 4.0
**Feature level 59**
* [`GET /events`](/api/get-events): Added new `zulip_version` and
`zulip_feature_level` fields to the `restart` event.
**Feature level 58** **Feature level 58**
* [`POST /register`](/api/register-queue): Added the new * [`POST /register`](/api/register-queue): Added the new

View File

@ -1142,7 +1142,13 @@ def check_realm_user_update(
restart_event = event_dict_type( restart_event = event_dict_type(
required_keys=[("type", Equals("restart")), ("server_generation", int), ("immediate", bool)] required_keys=[
("type", Equals("restart")),
("zulip_version", str),
("zulip_feature_level", int),
("server_generation", int),
("immediate", bool),
]
) )
check_restart_event = make_checker(restart_event) check_restart_event = make_checker(restart_event)

View File

@ -3114,6 +3114,68 @@ paths:
"realm_id": 2, "realm_id": 2,
"id": 0, "id": 0,
} }
- type: object
description: |
Event sent to all the users whenever the Zulip server restarts.
Specifically, this event is sent whenever the Tornado process
for the user is restarted; in particular, this will always happen
when the Zulip server is upgraded.
Clients can use this event to know when they should get a new
event queue after a server upgrade. Clients doing so must implement
a random delay strategy to spread such restarts over 10 minutes or
more to avoid creating a synchronized thundering herd effect.
properties:
id:
$ref: "#/components/schemas/EventIdSchema"
type:
allOf:
- $ref: "#/components/schemas/EventTypeSchema"
- enum:
- restart
zulip_version:
type: string
description: |
The Zulip version number, in the format where this appears
in the [server_settings](/api/get-server-settings) and
[register](/api/register-queue) responses.
**Changes**: New in Zulip 4.0 (feature level 59).
zulip_feature_level:
type: integer
description: |
The [Zulip feature level](/api/changelog) of the server
after the restart.
Clients can safely avoid refetching their state and
creating a new event queue when the API feature level has not
changed, or when they know the specific feature level change
is not relevant to the client (E.g. it just adds a new endpoint
that the client doesn't use).
**Changes**: New in Zulip 4.0 (feature level 59).
immediate:
type: boolean
description: |
Whether the client should fetch a new event queue immediately,
rather than using a backoff strategy to avoid thundering herds.
A Zulip development server uses this parameter to reload
clients immediately.
server_generation:
type: integer
description: |
The timestamp at which the server started.
additionalProperties: false
example:
{
"id": 0,
"immediate": True,
"server_generation": 1619334181,
"type": "restart",
"zulip_feature_level": 57,
"zulip_version": "4.0-dev+git",
}
- type: object - type: object
additionalProperties: false additionalProperties: false
description: | description: |

View File

@ -6,6 +6,7 @@ import orjson
from django.conf import settings from django.conf import settings
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from version import API_FEATURE_LEVEL, ZULIP_VERSION
from zerver.lib.actions import check_send_message, do_change_user_role, do_set_realm_property from zerver.lib.actions import check_send_message, do_change_user_role, do_set_realm_property
from zerver.lib.event_schema import check_restart_event from zerver.lib.event_schema import check_restart_event
from zerver.lib.events import fetch_initial_state_data, get_raw_user_data from zerver.lib.events import fetch_initial_state_data, get_raw_user_data
@ -877,6 +878,8 @@ class RestartEventsTest(ZulipTestCase):
restart_event, restart_event,
dict( dict(
type="restart", type="restart",
zulip_version=ZULIP_VERSION,
zulip_feature_level=API_FEATURE_LEVEL,
server_generation=settings.SERVER_GENERATION, server_generation=settings.SERVER_GENERATION,
immediate=True, immediate=True,
id=0, id=0,

View File

@ -161,7 +161,12 @@ from zerver.lib.event_schema import (
check_user_group_update, check_user_group_update,
check_user_status, check_user_status,
) )
from zerver.lib.events import apply_events, fetch_initial_state_data, post_process_state from zerver.lib.events import (
RestartEventException,
apply_events,
fetch_initial_state_data,
post_process_state,
)
from zerver.lib.markdown import MentionData from zerver.lib.markdown import MentionData
from zerver.lib.message import render_markdown from zerver.lib.message import render_markdown
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
@ -198,6 +203,7 @@ from zerver.openapi.openapi import validate_against_openapi_schema
from zerver.tornado.event_queue import ( from zerver.tornado.event_queue import (
allocate_client_descriptor, allocate_client_descriptor,
clear_client_event_queues_for_testing, clear_client_event_queues_for_testing,
send_restart_events,
) )
from zerver.views.realm_playgrounds import access_playground_by_id from zerver.views.realm_playgrounds import access_playground_by_id
@ -1938,6 +1944,10 @@ class NormalActionsTest(BaseAction):
events = self.verify_action(lambda: do_set_zoom_token(self.user_profile, None)) events = self.verify_action(lambda: do_set_zoom_token(self.user_profile, None))
check_has_zoom_token("events[0]", events[0], value=False) check_has_zoom_token("events[0]", events[0], value=False)
def test_restart_event(self) -> None:
with self.assertRaises(RestartEventException):
self.verify_action(lambda: send_restart_events(immediate=True))
class RealmPropertyActionTest(BaseAction): class RealmPropertyActionTest(BaseAction):
def do_set_realm_property_test(self, name: str) -> None: def do_set_realm_property_test(self, name: str) -> None:

View File

@ -33,6 +33,7 @@ from django.conf import settings
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from typing_extensions import TypedDict from typing_extensions import TypedDict
from version import API_FEATURE_LEVEL, ZULIP_VERSION
from zerver.decorator import cachify from zerver.decorator import cachify
from zerver.lib.message import MessageDict from zerver.lib.message import MessageDict
from zerver.lib.narrow import build_narrow_filter from zerver.lib.narrow import build_narrow_filter
@ -572,7 +573,12 @@ def load_event_queues(port: int) -> None:
def send_restart_events(immediate: bool = False) -> None: def send_restart_events(immediate: bool = False) -> None:
event: Dict[str, Any] = dict(type="restart", server_generation=settings.SERVER_GENERATION) event: Dict[str, Any] = dict(
type="restart",
zulip_version=ZULIP_VERSION,
zulip_feature_level=API_FEATURE_LEVEL,
server_generation=settings.SERVER_GENERATION,
)
if immediate: if immediate:
event["immediate"] = True event["immediate"] = True
for client in clients.values(): for client in clients.values():