Skip to content

Commit ca09f37

Browse files
authored
Merge pull request #632 from element-hq/bbz/fix-routing-to-initial-sync
Synapse: fix requests being routed to initial-synchrotron incorrectly
2 parents 2975fa1 + 1bef171 commit ca09f37

7 files changed

Lines changed: 96 additions & 12 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ jobs:
105105
106106
- name: On upgrade, Synapse can restart, expect 429 to occur
107107
run: |
108-
export PYTEST_EXPECTED_HTTP_STATUS_CODES="429"
108+
echo "PYTEST_EXPECTED_HTTP_STATUS_CODES=429" >> "$GITHUB_ENV"
109109
if: ${{ matrix.test-from-ref != github.event.pull_request.head.sha }}
110110

111111
- name: Test with pytest (upgrade or idempotent setup)
@@ -136,7 +136,7 @@ jobs:
136136
if: ${{ failure() }}
137137
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
138138
with:
139-
name: ess-helm-logs-${{ matrix.test-component }}
139+
name: ess-helm-logs-${{ matrix.test-component }}-${{ matrix.test-from-ref }}
140140
path: ess-helm-logs
141141
retention-days: 1
142142

charts/matrix-stack/ci/fragments/synapse-pytest-self-extras.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ synapse:
2020
# A non-HTTP worker & a stream writer
2121
event-persister:
2222
enabled: true
23+
# initial-synchrotron & synchrotron have non-trivial routing behaviour
24+
initial-synchrotron:
25+
enabled: true
2326
# A standard HTTP worker
2427
sliding-sync:
2528
replicas: 2
2629
enabled: true
30+
# initial-synchrotron & synchrotron have non-trivial routing behaviour
31+
synchrotron:
32+
enabled: true
2733
# Media repo is fairly distinct from other workers
2834
media-repository:
2935
enabled: true

charts/matrix-stack/ci/pytest-synapse-values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ synapse:
5858
# A non-HTTP worker & a stream writer
5959
event-persister:
6060
enabled: true
61+
# initial-synchrotron & synchrotron have non-trivial routing behaviour
62+
initial-synchrotron:
63+
enabled: true
6164
# Media repo is fairly distinct from other workers
6265
media-repository:
6366
enabled: true
6467
# A standard HTTP worker
6568
sliding-sync:
6669
enabled: true
6770
replicas: 2
71+
# initial-synchrotron & synchrotron have non-trivial routing behaviour
72+
synchrotron:
73+
enabled: true
6874
wellKnownDelegation:
6975
enabled: false

charts/matrix-stack/configs/synapse/partial-haproxy.cfg.tpl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,18 @@ frontend synapse-http-in
7474
http-request set-var(req.backend) path,map_reg(/synapse/path_map_file,main) unless { var(req.backend) -m found }
7575
{{- if dig "initial-synchrotron" "enabled" false .workers }}
7676

77-
acl is_initial_sync path -m reg ^/_matrix/client/(api/v1|r0|v3)/initialSync$
78-
acl is_initial_sync path -m reg ^/_matrix/client/(api/v1|r0|v3)/rooms/[^/]+/initialSync$
79-
# https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv3sync
80-
acl is_initial_sync path -m reg ^/_matrix/client/(r0|v3)/sync$ { urlp("full_state") -m str true }
81-
acl is_initial_sync path -m reg ^/_matrix/client/(r0|v3)/sync$ !{ urlp("since") -m found }
82-
# https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3events
83-
acl is_initial_sync path -m reg ^/_matrix/client/(api/v1|r0|v3)/events$ !{ urlp("from") -m found }
77+
acl has_available_initial_syncs nbsrv('synapse-initial-synchrotron') ge 1
8478

8579
# Set to the initial-synchrotron backend if it is one of these magic paths AND we have workers in the initial-synchrotron backend
8680
# This means that we don't update the backend from synchrotron if that's configured but there's no initial-synchrotron servers available
8781
# And then can it fallback to main if there are no synchrotron servers either
88-
http-request set-var(req.backend) str('initial-synchrotron') if is_initial_sync { nbsrv('synapse-initial-synchrotron') ge 1 }
82+
http-request set-var(req.backend) str('initial-synchrotron') if has_available_initial_syncs { path -m reg ^/_matrix/client/(api/v1|r0|v3)/initialSync$ }
83+
http-request set-var(req.backend) str('initial-synchrotron') if has_available_initial_syncs { path -m reg ^/_matrix/client/(api/v1|r0|v3)/rooms/[^/]+/initialSync$ }
84+
# https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv3sync
85+
http-request set-var(req.backend) str('initial-synchrotron') if has_available_initial_syncs { path -m reg ^/_matrix/client/(r0|v3)/sync$ } { urlp("full_state") -m str true }
86+
http-request set-var(req.backend) str('initial-synchrotron') if has_available_initial_syncs { path -m reg ^/_matrix/client/(r0|v3)/sync$ } !{ urlp("since") -m found }
87+
# https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3events
88+
http-request set-var(req.backend) str('initial-synchrotron') if has_available_initial_syncs { path -m reg ^/_matrix/client/(api/v1|r0|v3)/events$ } !{ urlp("from") -m found }
8989
{{- end }}
9090

9191
{{- if include "element-io.matrix-authentication-service.readyToHandleAuth" (dict "root" $root) }}

newsfragments/632.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Synapse: fix requests being routed to initial-synchrotron incorrectly.

tests/integration/lib/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
}
3030
| set(int(code) for code in os.environ.get("PYTEST_EXPECTED_HTTP_STATUS_CODES", "").split(",") if code),
3131
retry_all_server_errors=False,
32-
exceptions={aiohttp.client_exceptions.ClientResponseError},
3332
)
3433

3534

tests/integration/test_synapse.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
import pyhelm3
99
import pytest
10+
from aiohttp.client_exceptions import ClientResponseError
11+
from lightkube import AsyncClient
12+
from lightkube.resources.core_v1 import Pod
1013

1114
from .fixtures import ESSData, User
1215
from .lib.helpers import deploy_with_values_patch, get_deployment_marker
@@ -35,7 +38,9 @@ async def test_synapse_can_access_client_api(
3538
@pytest.mark.skipif(value_file_has("synapse.enabled", False), reason="Synapse not deployed")
3639
@pytest.mark.parametrize("users", [(User(name="sliding-sync-user"),)], indirect=True)
3740
@pytest.mark.asyncio_cooperative
38-
async def test_simplified_sliding_sync_syncs(ssl_context, users, generated_data: ESSData):
41+
async def test_simplified_sliding_sync_syncs(ingress_ready, ssl_context, users, generated_data: ESSData):
42+
await ingress_ready("synapse")
43+
3944
access_token = users[0].access_token
4045

4146
sync_result = await aiohttp_post_json(
@@ -48,6 +53,73 @@ async def test_simplified_sliding_sync_syncs(ssl_context, users, generated_data:
4853
assert "pos" in sync_result
4954

5055

56+
@pytest.mark.skipif(value_file_has("synapse.enabled", False), reason="Synapse not deployed")
57+
@pytest.mark.asyncio_cooperative
58+
async def test_routes_to_synapse_workers_correctly(
59+
ingress_ready, kube_client: AsyncClient, ssl_context, generated_data: ESSData
60+
):
61+
await ingress_ready("synapse")
62+
63+
main_backend = "main/main"
64+
if value_file_has("synapse.workers.sliding-sync.enabled", True):
65+
sliding_sync_backend = "sliding-sync/sliding-sync"
66+
else:
67+
sliding_sync_backend = main_backend
68+
69+
if value_file_has("synapse.workers.synchrotron.enabled", True):
70+
synchrotron_backend = "synchrotron/synchrotron"
71+
else:
72+
synchrotron_backend = main_backend
73+
74+
if value_file_has("synapse.workers.initial-synchrotron.enabled", True):
75+
initial_synchrotron_backend = "initial-synchrotron/initial-sync"
76+
else:
77+
initial_synchrotron_backend = synchrotron_backend
78+
79+
# We don't care about any of these succeeding, only that the requests are made and HAProxy dispatches correctly
80+
# So no auth required and parameters can be made up
81+
paths_to_backends = {
82+
# initial-synchrotron
83+
"/_matrix/client/v3/initialSync": initial_synchrotron_backend,
84+
"/_matrix/client/v3/rooms/aroomid/initialSync": initial_synchrotron_backend,
85+
"/_matrix/client/v3/sync?full_state=true": initial_synchrotron_backend,
86+
"/_matrix/client/v3/sync": initial_synchrotron_backend,
87+
"/_matrix/client/v3/events": initial_synchrotron_backend,
88+
# synchrotron
89+
"/_matrix/client/v3/sync?since=recently": synchrotron_backend,
90+
"/_matrix/client/v3/events?from=recently": synchrotron_backend,
91+
# sliding-sync
92+
"/_matrix/client/unstable/org.matrix.simplified_msc3575/sync": sliding_sync_backend,
93+
# Would be client-reader but not configured in tests
94+
"/_matrix/client/versions": main_backend,
95+
}
96+
97+
for path in paths_to_backends:
98+
try:
99+
await aiohttp_get_json(f"https://synapse.{generated_data.server_name}{path}", ssl_context)
100+
except ClientResponseError as e:
101+
# We can't use pytest.raises as no exception (200) is valid
102+
assert e.status in [401, 405], f"{path} had an unexpected status. {e=}" # noqa P1017
103+
104+
http_log_lines = []
105+
async for haproxy_pod in kube_client.list(
106+
Pod, namespace=generated_data.ess_namespace, labels={"app.kubernetes.io/name": "haproxy"}
107+
):
108+
assert haproxy_pod.metadata
109+
assert haproxy_pod.metadata.name
110+
assert haproxy_pod.metadata.namespace
111+
async for log_line in kube_client.log(haproxy_pod.metadata.name, namespace=haproxy_pod.metadata.namespace):
112+
if "HTTP/1.1" in log_line:
113+
http_log_lines.append(log_line)
114+
115+
for path, backend in paths_to_backends.items():
116+
matching_lines = [line for line in http_log_lines if f"GET {path} HTTP/1.1" in line]
117+
118+
assert len(matching_lines) > 0, f"Requests for {path} did not appear in the HAProxy logs"
119+
for matching_line in matching_lines:
120+
assert f"synapse-http-in synapse-{backend}" in matching_line, f"{path} was routed unexpectedly"
121+
122+
51123
@pytest.mark.skipif(value_file_has("synapse.enabled", False), reason="Synapse not deployed")
52124
@pytest.mark.parametrize("users", [(User(name="media-upload-unauth"),)], indirect=True)
53125
@pytest.mark.asyncio_cooperative

0 commit comments

Comments
 (0)