Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit ea20937

Browse files
authored
Add an admin API to run background jobs. (#11352)
Instead of having admins poke into the database directly. Can currently run jobs to populate stats and to populate the user directory.
1 parent 7ae5599 commit ea20937

File tree

9 files changed

+280
-43
lines changed

9 files changed

+280
-43
lines changed

changelog.d/11352.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add admin API to run background jobs.

docs/sample_config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,8 +2360,8 @@ user_directory:
23602360
# indexes were (re)built was before Synapse 1.44, you'll have to
23612361
# rebuild the indexes in order to search through all known users.
23622362
# These indexes are built the first time Synapse starts; admins can
2363-
# manually trigger a rebuild following the instructions at
2364-
# https://matrix-org.github.io/synapse/latest/user_directory.html
2363+
# manually trigger a rebuild via API following the instructions at
2364+
# https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/background_updates.html#run
23652365
#
23662366
# Uncomment to return search results containing all known users, even if that
23672367
# user does not share a room with the requester.

docs/usage/administration/admin_api/background_updates.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ For each update:
4242
`average_items_per_ms` how many items are processed per millisecond based on an exponential average.
4343

4444

45-
4645
## Enabled
4746

4847
This API allow pausing background updates.
@@ -82,3 +81,29 @@ The API returns the `enabled` param.
8281
```
8382

8483
There is also a `GET` version which returns the `enabled` state.
84+
85+
86+
## Run
87+
88+
This API schedules a specific background update to run. The job starts immediately after calling the API.
89+
90+
91+
The API is:
92+
93+
```
94+
POST /_synapse/admin/v1/background_updates/start_job
95+
```
96+
97+
with the following body:
98+
99+
```json
100+
{
101+
"job_name": "populate_stats_process_rooms"
102+
}
103+
```
104+
105+
The following JSON body parameters are available:
106+
107+
- `job_name` - A string which job to run. Valid values are:
108+
- `populate_stats_process_rooms` - Recalculate the stats for all rooms.
109+
- `regenerate_directory` - Recalculate the [user directory](../../../user_directory.md) if it is stale or out of sync.

docs/user_directory.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ on this particular server - i.e. ones which your account shares a room with, or
66
who are present in a publicly viewable room present on the server.
77

88
The directory info is stored in various tables, which can (typically after
9-
DB corruption) get stale or out of sync. If this happens, for now the
10-
solution to fix it is to execute the SQL [here](https://github.com/matrix-org/synapse/blob/master/synapse/storage/schema/main/delta/53/user_dir_populate.sql)
11-
and then restart synapse. This should then start a background task to
9+
DB corruption) get stale or out of sync. If this happens, for now the
10+
solution to fix it is to use the [admin API](usage/administration/admin_api/background_updates.md#run)
11+
and execute the job `regenerate_directory`. This should then start a background task to
1212
flush the current tables and regenerate the directory.
1313

1414
Data model

synapse/config/user_directory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
5353
# indexes were (re)built was before Synapse 1.44, you'll have to
5454
# rebuild the indexes in order to search through all known users.
5555
# These indexes are built the first time Synapse starts; admins can
56-
# manually trigger a rebuild following the instructions at
57-
# https://matrix-org.github.io/synapse/latest/user_directory.html
56+
# manually trigger a rebuild via API following the instructions at
57+
# https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/background_updates.html#run
5858
#
5959
# Uncomment to return search results containing all known users, even if that
6060
# user does not share a room with the requester.

synapse/rest/admin/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from synapse.rest.admin.background_updates import (
2929
BackgroundUpdateEnabledRestServlet,
3030
BackgroundUpdateRestServlet,
31+
BackgroundUpdateStartJobRestServlet,
3132
)
3233
from synapse.rest.admin.devices import (
3334
DeleteDevicesRestServlet,
@@ -261,6 +262,7 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
261262
SendServerNoticeServlet(hs).register(http_server)
262263
BackgroundUpdateEnabledRestServlet(hs).register(http_server)
263264
BackgroundUpdateRestServlet(hs).register(http_server)
265+
BackgroundUpdateStartJobRestServlet(hs).register(http_server)
264266

265267

266268
def register_servlets_for_client_rest_resource(

synapse/rest/admin/background_updates.py

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import logging
15+
from http import HTTPStatus
1516
from typing import TYPE_CHECKING, Tuple
1617

1718
from synapse.api.errors import SynapseError
18-
from synapse.http.servlet import RestServlet, parse_json_object_from_request
19+
from synapse.http.servlet import (
20+
RestServlet,
21+
assert_params_in_dict,
22+
parse_json_object_from_request,
23+
)
1924
from synapse.http.site import SynapseRequest
2025
from synapse.rest.admin._base import admin_patterns, assert_user_is_admin
2126
from synapse.types import JsonDict
@@ -29,70 +34,66 @@
2934
class BackgroundUpdateEnabledRestServlet(RestServlet):
3035
"""Allows temporarily disabling background updates"""
3136

32-
PATTERNS = admin_patterns("/background_updates/enabled")
37+
PATTERNS = admin_patterns("/background_updates/enabled$")
3338

3439
def __init__(self, hs: "HomeServer"):
35-
self.group_server = hs.get_groups_server_handler()
36-
self.is_mine_id = hs.is_mine_id
37-
self.auth = hs.get_auth()
38-
39-
self.data_stores = hs.get_datastores()
40+
self._auth = hs.get_auth()
41+
self._data_stores = hs.get_datastores()
4042

4143
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
42-
requester = await self.auth.get_user_by_req(request)
43-
await assert_user_is_admin(self.auth, requester.user)
44+
requester = await self._auth.get_user_by_req(request)
45+
await assert_user_is_admin(self._auth, requester.user)
4446

4547
# We need to check that all configured databases have updates enabled.
4648
# (They *should* all be in sync.)
47-
enabled = all(db.updates.enabled for db in self.data_stores.databases)
49+
enabled = all(db.updates.enabled for db in self._data_stores.databases)
4850

49-
return 200, {"enabled": enabled}
51+
return HTTPStatus.OK, {"enabled": enabled}
5052

5153
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
52-
requester = await self.auth.get_user_by_req(request)
53-
await assert_user_is_admin(self.auth, requester.user)
54+
requester = await self._auth.get_user_by_req(request)
55+
await assert_user_is_admin(self._auth, requester.user)
5456

5557
body = parse_json_object_from_request(request)
5658

5759
enabled = body.get("enabled", True)
5860

5961
if not isinstance(enabled, bool):
60-
raise SynapseError(400, "'enabled' parameter must be a boolean")
62+
raise SynapseError(
63+
HTTPStatus.BAD_REQUEST, "'enabled' parameter must be a boolean"
64+
)
6165

62-
for db in self.data_stores.databases:
66+
for db in self._data_stores.databases:
6367
db.updates.enabled = enabled
6468

6569
# If we're re-enabling them ensure that we start the background
6670
# process again.
6771
if enabled:
6872
db.updates.start_doing_background_updates()
6973

70-
return 200, {"enabled": enabled}
74+
return HTTPStatus.OK, {"enabled": enabled}
7175

7276

7377
class BackgroundUpdateRestServlet(RestServlet):
7478
"""Fetch information about background updates"""
7579

76-
PATTERNS = admin_patterns("/background_updates/status")
80+
PATTERNS = admin_patterns("/background_updates/status$")
7781

7882
def __init__(self, hs: "HomeServer"):
79-
self.group_server = hs.get_groups_server_handler()
80-
self.is_mine_id = hs.is_mine_id
81-
self.auth = hs.get_auth()
82-
83-
self.data_stores = hs.get_datastores()
83+
self._auth = hs.get_auth()
84+
self._data_stores = hs.get_datastores()
8485

8586
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
86-
requester = await self.auth.get_user_by_req(request)
87-
await assert_user_is_admin(self.auth, requester.user)
87+
requester = await self._auth.get_user_by_req(request)
88+
await assert_user_is_admin(self._auth, requester.user)
8889

8990
# We need to check that all configured databases have updates enabled.
9091
# (They *should* all be in sync.)
91-
enabled = all(db.updates.enabled for db in self.data_stores.databases)
92+
enabled = all(db.updates.enabled for db in self._data_stores.databases)
9293

9394
current_updates = {}
9495

95-
for db in self.data_stores.databases:
96+
for db in self._data_stores.databases:
9697
update = db.updates.get_current_update()
9798
if not update:
9899
continue
@@ -104,4 +105,72 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
104105
"average_items_per_ms": update.average_items_per_ms(),
105106
}
106107

107-
return 200, {"enabled": enabled, "current_updates": current_updates}
108+
return HTTPStatus.OK, {"enabled": enabled, "current_updates": current_updates}
109+
110+
111+
class BackgroundUpdateStartJobRestServlet(RestServlet):
112+
"""Allows to start specific background updates"""
113+
114+
PATTERNS = admin_patterns("/background_updates/start_job")
115+
116+
def __init__(self, hs: "HomeServer"):
117+
self._auth = hs.get_auth()
118+
self._store = hs.get_datastore()
119+
120+
async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
121+
requester = await self._auth.get_user_by_req(request)
122+
await assert_user_is_admin(self._auth, requester.user)
123+
124+
body = parse_json_object_from_request(request)
125+
assert_params_in_dict(body, ["job_name"])
126+
127+
job_name = body["job_name"]
128+
129+
if job_name == "populate_stats_process_rooms":
130+
jobs = [
131+
{
132+
"update_name": "populate_stats_process_rooms",
133+
"progress_json": "{}",
134+
},
135+
]
136+
elif job_name == "regenerate_directory":
137+
jobs = [
138+
{
139+
"update_name": "populate_user_directory_createtables",
140+
"progress_json": "{}",
141+
"depends_on": "",
142+
},
143+
{
144+
"update_name": "populate_user_directory_process_rooms",
145+
"progress_json": "{}",
146+
"depends_on": "populate_user_directory_createtables",
147+
},
148+
{
149+
"update_name": "populate_user_directory_process_users",
150+
"progress_json": "{}",
151+
"depends_on": "populate_user_directory_process_rooms",
152+
},
153+
{
154+
"update_name": "populate_user_directory_cleanup",
155+
"progress_json": "{}",
156+
"depends_on": "populate_user_directory_process_users",
157+
},
158+
]
159+
else:
160+
raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid job_name")
161+
162+
try:
163+
await self._store.db_pool.simple_insert_many(
164+
table="background_updates",
165+
values=jobs,
166+
desc=f"admin_api_run_{job_name}",
167+
)
168+
except self._store.db_pool.engine.module.IntegrityError:
169+
raise SynapseError(
170+
HTTPStatus.BAD_REQUEST,
171+
"Job %s is already in queue of background updates." % (job_name,),
172+
)
173+
174+
self._store.db_pool.updates.start_doing_background_updates()
175+
176+
return HTTPStatus.OK, {}

synapse/storage/background_updates.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ def get_current_update(self) -> Optional[BackgroundUpdatePerformance]:
122122

123123
def start_doing_background_updates(self) -> None:
124124
if self.enabled:
125+
# if we start a new background update, not all updates are done.
126+
self._all_done = False
125127
run_as_background_process("background_updates", self.run_background_updates)
126128

127129
async def run_background_updates(self, sleep: bool = True) -> None:

0 commit comments

Comments
 (0)