mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-20 02:24:54 +03:00
SECURITY UPDATE: Prevent unauthorised users from setting state events in a room
when there is no `m.room.power_levels` event in force in the room. (PR #3397) Discussion around the Matrix Spec change proposal for this change can be followed at https://github.com/matrix-org/matrix-doc/issues/1304. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJbIop9AAoJEIofk9V1tejV9lsIAJVH0l5dXROmy1KH/zt16AUA CXa6Vv4Vyo6hKad/fZ81OZVRr5ChK/TvbIJVn/SA/muCfdoIFdxhT8eo/pXzO2UW zReuLsDhAg+gSvpNus37oWj2FVsAE1HYDZ60lfaapAdZnkFit68d5DQZjO6nZHHA YUXcU3GUwj0ZYuUzFzYKMLu6uNNasNkN8h6SS2lF7Bm4JaKDW+mFMfCyJwdIVSEh BGhHoVpXdxFysD9s6Mwxqrz3KKg1Jtp7idDkk0x2S2Eh+gxyiDQQokv0oQ3+0+HG sgy5Iz2t2CkpS02/j+LOvAZljTmnD0bXu3srGR+25StsoDFP038Am3bfQwtD190= =9jsT -----END PGP SIGNATURE----- Merge tag 'v0.31.2' into dinsic
This commit is contained in:
commit
fe265fe990
215 changed files with 6216 additions and 2887 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Dockerfile
|
||||||
|
.travis.yml
|
||||||
|
.gitignore
|
||||||
|
demo/etc
|
||||||
|
tox.ini
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
.*.swp
|
.*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
_trial_temp/
|
_trial_temp/
|
||||||
|
@ -13,6 +14,7 @@ docs/build/
|
||||||
cmdclient_config.json
|
cmdclient_config.json
|
||||||
homeserver*.db
|
homeserver*.db
|
||||||
homeserver*.log
|
homeserver*.log
|
||||||
|
homeserver*.log.*
|
||||||
homeserver*.pid
|
homeserver*.pid
|
||||||
homeserver*.yaml
|
homeserver*.yaml
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ demo/media_store.*
|
||||||
demo/etc
|
demo/etc
|
||||||
|
|
||||||
uploads
|
uploads
|
||||||
|
cache
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
media_store/
|
media_store/
|
||||||
|
@ -39,6 +42,7 @@ media_store/
|
||||||
*.tac
|
*.tac
|
||||||
|
|
||||||
build/
|
build/
|
||||||
|
venv/
|
||||||
|
|
||||||
localhost-800*/
|
localhost-800*/
|
||||||
static/client/register/register_config.js
|
static/client/register/register_config.js
|
||||||
|
@ -48,3 +52,4 @@ env/
|
||||||
*.config
|
*.config
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.ropeproject/
|
||||||
|
|
18
.travis.yml
18
.travis.yml
|
@ -1,14 +1,22 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
python: 2.7
|
|
||||||
|
|
||||||
# tell travis to cache ~/.cache/pip
|
# tell travis to cache ~/.cache/pip
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
env:
|
matrix:
|
||||||
- TOX_ENV=packaging
|
include:
|
||||||
- TOX_ENV=pep8
|
- python: 2.7
|
||||||
- TOX_ENV=py27
|
env: TOX_ENV=packaging
|
||||||
|
|
||||||
|
- python: 2.7
|
||||||
|
env: TOX_ENV=pep8
|
||||||
|
|
||||||
|
- python: 2.7
|
||||||
|
env: TOX_ENV=py27
|
||||||
|
|
||||||
|
- python: 3.6
|
||||||
|
env: TOX_ENV=py36
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
|
|
|
@ -60,3 +60,6 @@ Niklas Riekenbrauck <nikriek at gmail dot.com>
|
||||||
|
|
||||||
Christoph Witzany <christoph at web.crofting.com>
|
Christoph Witzany <christoph at web.crofting.com>
|
||||||
* Add LDAP support for authentication
|
* Add LDAP support for authentication
|
||||||
|
|
||||||
|
Pierre Jaury <pierre at jaury.eu>
|
||||||
|
* Docker packaging
|
223
CHANGES.rst
223
CHANGES.rst
|
@ -1,3 +1,226 @@
|
||||||
|
Changes in synapse v0.31.2 (2018-06-14)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
SECURITY UPDATE: Prevent unauthorised users from setting state events in a room
|
||||||
|
when there is no ``m.room.power_levels`` event in force in the room. (PR #3397)
|
||||||
|
|
||||||
|
Discussion around the Matrix Spec change proposal for this change can be
|
||||||
|
followed at https://github.com/matrix-org/matrix-doc/issues/1304.
|
||||||
|
|
||||||
|
Changes in synapse v0.31.1 (2018-06-08)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
v0.31.1 fixes a security bug in the ``get_missing_events`` federation API
|
||||||
|
where event visibility rules were not applied correctly.
|
||||||
|
|
||||||
|
We are not aware of it being actively exploited but please upgrade asap.
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* Fix event filtering in get_missing_events handler (PR #3371)
|
||||||
|
|
||||||
|
Changes in synapse v0.31.0 (2018-06-06)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Most notable change from v0.30.0 is to switch to the python prometheus library to improve system
|
||||||
|
stats reporting. WARNING: this changes a number of prometheus metrics in a
|
||||||
|
backwards-incompatible manner. For more details, see
|
||||||
|
`docs/metrics-howto.rst <docs/metrics-howto.rst#removal-of-deprecated-metrics--time-based-counters-becoming-histograms-in-0310>`_.
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* Fix metric documentation tables (PR #3341)
|
||||||
|
* Fix LaterGauge error handling (694968f)
|
||||||
|
* Fix replication metrics (b7e7fd2)
|
||||||
|
|
||||||
|
Changes in synapse v0.31.0-rc1 (2018-06-04)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Switch to the Python Prometheus library (PR #3256, #3274)
|
||||||
|
* Let users leave the server notice room after joining (PR #3287)
|
||||||
|
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* daily user type phone home stats (PR #3264)
|
||||||
|
* Use iter* methods for _filter_events_for_server (PR #3267)
|
||||||
|
* Docs on consent bits (PR #3268)
|
||||||
|
* Remove users from user directory on deactivate (PR #3277)
|
||||||
|
* Avoid sending consent notice to guest users (PR #3288)
|
||||||
|
* disable CPUMetrics if no /proc/self/stat (PR #3299)
|
||||||
|
* Consistently use six's iteritems and wrap lazy keys/values in list() if they're not meant to be lazy (PR #3307)
|
||||||
|
* Add private IPv6 addresses to example config for url preview blacklist (PR #3317) Thanks to @thegcat!
|
||||||
|
* Reduce stuck read-receipts: ignore depth when updating (PR #3318)
|
||||||
|
* Put python's logs into Trial when running unit tests (PR #3319)
|
||||||
|
|
||||||
|
Changes, python 3 migration:
|
||||||
|
|
||||||
|
* Replace some more comparisons with six (PR #3243) Thanks to @NotAFile!
|
||||||
|
* replace some iteritems with six (PR #3244) Thanks to @NotAFile!
|
||||||
|
* Add batch_iter to utils (PR #3245) Thanks to @NotAFile!
|
||||||
|
* use repr, not str (PR #3246) Thanks to @NotAFile!
|
||||||
|
* Misc Python3 fixes (PR #3247) Thanks to @NotAFile!
|
||||||
|
* Py3 storage/_base.py (PR #3278) Thanks to @NotAFile!
|
||||||
|
* more six iteritems (PR #3279) Thanks to @NotAFile!
|
||||||
|
* More Misc. py3 fixes (PR #3280) Thanks to @NotAFile!
|
||||||
|
* remaining isintance fixes (PR #3281) Thanks to @NotAFile!
|
||||||
|
* py3-ize state.py (PR #3283) Thanks to @NotAFile!
|
||||||
|
* extend tox testing for py3 to avoid regressions (PR #3302) Thanks to @krombel!
|
||||||
|
* use memoryview in py3 (PR #3303) Thanks to @NotAFile!
|
||||||
|
|
||||||
|
Bugs:
|
||||||
|
|
||||||
|
* Fix federation backfill bugs (PR #3261)
|
||||||
|
* federation: fix LaterGauge usage (PR #3328) Thanks to @intelfx!
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.30.0 (2018-05-24)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
'Server Notices' are a new feature introduced in Synapse 0.30. They provide a
|
||||||
|
channel whereby server administrators can send messages to users on the server.
|
||||||
|
|
||||||
|
They are used as part of communication of the server policies (see ``docs/consent_tracking.md``),
|
||||||
|
however the intention is that they may also find a use for features such
|
||||||
|
as "Message of the day".
|
||||||
|
|
||||||
|
This feature is specific to Synapse, but uses standard Matrix communication mechanisms,
|
||||||
|
so should work with any Matrix client. For more details see ``docs/server_notices.md``
|
||||||
|
|
||||||
|
Further Server Notices/Consent Tracking Support:
|
||||||
|
|
||||||
|
* Allow overriding the server_notices user's avatar (PR #3273)
|
||||||
|
* Use the localpart in the consent uri (PR #3272)
|
||||||
|
* Support for putting %(consent_uri)s in messages (PR #3271)
|
||||||
|
* Block attempts to send server notices to remote users (PR #3270)
|
||||||
|
* Docs on consent bits (PR #3268)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.30.0-rc1 (2018-05-23)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
Server Notices/Consent Tracking Support:
|
||||||
|
|
||||||
|
* ConsentResource to gather policy consent from users (PR #3213)
|
||||||
|
* Move RoomCreationHandler out of synapse.handlers.Handlers (PR #3225)
|
||||||
|
* Infrastructure for a server notices room (PR #3232)
|
||||||
|
* Send users a server notice about consent (PR #3236)
|
||||||
|
* Reject attempts to send event before privacy consent is given (PR #3257)
|
||||||
|
* Add a 'has_consented' template var to consent forms (PR #3262)
|
||||||
|
* Fix dependency on jinja2 (PR #3263)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Cohort analytics (PR #3163, #3241, #3251)
|
||||||
|
* Add lxml to docker image for web previews (PR #3239) Thanks to @ptman!
|
||||||
|
* Add in flight request metrics (PR #3252)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Remove unused `update_external_syncs` (PR #3233)
|
||||||
|
* Use stream rather depth ordering for push actions (PR #3212)
|
||||||
|
* Make purge_history operate on tokens (PR #3221)
|
||||||
|
* Don't support limitless pagination (PR #3265)
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* Fix logcontext resource usage tracking (PR #3258)
|
||||||
|
* Fix error in handling receipts (PR #3235)
|
||||||
|
* Stop the transaction cache caching failures (PR #3255)
|
||||||
|
|
||||||
|
|
||||||
|
Changes in synapse v0.29.1 (2018-05-17)
|
||||||
|
==========================================
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* Update docker documentation (PR #3222)
|
||||||
|
|
||||||
|
Changes in synapse v0.29.0 (2018-05-16)
|
||||||
|
===========================================
|
||||||
|
Not changes since v0.29.0-rc1
|
||||||
|
|
||||||
|
Changes in synapse v0.29.0-rc1 (2018-05-14)
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
Notable changes, a docker file for running Synapse (Thanks to @kaiyou!) and a
|
||||||
|
closed spec bug in the Client Server API. Additionally further prep for Python 3
|
||||||
|
migration.
|
||||||
|
|
||||||
|
Potentially breaking change:
|
||||||
|
|
||||||
|
* Make Client-Server API return 401 for invalid token (PR #3161).
|
||||||
|
|
||||||
|
This changes the Client-server spec to return a 401 error code instead of 403
|
||||||
|
when the access token is unrecognised. This is the behaviour required by the
|
||||||
|
specification, but some clients may be relying on the old, incorrect
|
||||||
|
behaviour.
|
||||||
|
|
||||||
|
Thanks to @NotAFile for fixing this.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add a Dockerfile for synapse (PR #2846) Thanks to @kaiyou!
|
||||||
|
|
||||||
|
Changes - General:
|
||||||
|
|
||||||
|
* nuke-room-from-db.sh: added postgresql option and help (PR #2337) Thanks to @rubo77!
|
||||||
|
* Part user from rooms on account deactivate (PR #3201)
|
||||||
|
* Make 'unexpected logging context' into warnings (PR #3007)
|
||||||
|
* Set Server header in SynapseRequest (PR #3208)
|
||||||
|
* remove duplicates from groups tables (PR #3129)
|
||||||
|
* Improve exception handling for background processes (PR #3138)
|
||||||
|
* Add missing consumeErrors to improve exception handling (PR #3139)
|
||||||
|
* reraise exceptions more carefully (PR #3142)
|
||||||
|
* Remove redundant call to preserve_fn (PR #3143)
|
||||||
|
* Trap exceptions thrown within run_in_background (PR #3144)
|
||||||
|
|
||||||
|
Changes - Refactors:
|
||||||
|
|
||||||
|
* Refactor /context to reuse pagination storage functions (PR #3193)
|
||||||
|
* Refactor recent events func to use pagination func (PR #3195)
|
||||||
|
* Refactor pagination DB API to return concrete type (PR #3196)
|
||||||
|
* Refactor get_recent_events_for_room return type (PR #3198)
|
||||||
|
* Refactor sync APIs to reuse pagination API (PR #3199)
|
||||||
|
* Remove unused code path from member change DB func (PR #3200)
|
||||||
|
* Refactor request handling wrappers (PR #3203)
|
||||||
|
* transaction_id, destination defined twice (PR #3209) Thanks to @damir-manapov!
|
||||||
|
* Refactor event storage to prepare for changes in state calculations (PR #3141)
|
||||||
|
* Set Server header in SynapseRequest (PR #3208)
|
||||||
|
* Use deferred.addTimeout instead of time_bound_deferred (PR #3127, #3178)
|
||||||
|
* Use run_in_background in preference to preserve_fn (PR #3140)
|
||||||
|
|
||||||
|
Changes - Python 3 migration:
|
||||||
|
|
||||||
|
* Construct HMAC as bytes on py3 (PR #3156) Thanks to @NotAFile!
|
||||||
|
* run config tests on py3 (PR #3159) Thanks to @NotAFile!
|
||||||
|
* Open certificate files as bytes (PR #3084) Thanks to @NotAFile!
|
||||||
|
* Open config file in non-bytes mode (PR #3085) Thanks to @NotAFile!
|
||||||
|
* Make event properties raise AttributeError instead (PR #3102) Thanks to @NotAFile!
|
||||||
|
* Use six.moves.urlparse (PR #3108) Thanks to @NotAFile!
|
||||||
|
* Add py3 tests to tox with folders that work (PR #3145) Thanks to @NotAFile!
|
||||||
|
* Don't yield in list comprehensions (PR #3150) Thanks to @NotAFile!
|
||||||
|
* Move more xrange to six (PR #3151) Thanks to @NotAFile!
|
||||||
|
* make imports local (PR #3152) Thanks to @NotAFile!
|
||||||
|
* move httplib import to six (PR #3153) Thanks to @NotAFile!
|
||||||
|
* Replace stringIO imports with six (PR #3154, #3168) Thanks to @NotAFile!
|
||||||
|
* more bytes strings (PR #3155) Thanks to @NotAFile!
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
* synapse fails to start under Twisted >= 18.4 (PR #3157)
|
||||||
|
* Fix a class of logcontext leaks (PR #3170)
|
||||||
|
* Fix a couple of logcontext leaks in unit tests (PR #3172)
|
||||||
|
* Fix logcontext leak in media repo (PR #3174)
|
||||||
|
* Escape label values in prometheus metrics (PR #3175, #3186)
|
||||||
|
* Fix 'Unhandled Error' logs with Twisted 18.4 (PR #3182) Thanks to @Half-Shot!
|
||||||
|
* Fix logcontext leaks in rate limiter (PR #3183)
|
||||||
|
* notifications: Convert next_token to string according to the spec (PR #3190) Thanks to @mujx!
|
||||||
|
* nuke-room-from-db.sh: fix deletion from search table (PR #3194) Thanks to @rubo77!
|
||||||
|
* add guard for None on purge_history api (PR #3160) Thanks to @krombel!
|
||||||
|
|
||||||
Changes in synapse v0.28.1 (2018-05-01)
|
Changes in synapse v0.28.1 (2018-05-01)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
|
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
FROM docker.io/python:2-alpine3.7
|
||||||
|
|
||||||
|
RUN apk add --no-cache --virtual .nacl_deps su-exec build-base libffi-dev zlib-dev libressl-dev libjpeg-turbo-dev linux-headers postgresql-dev libxslt-dev
|
||||||
|
|
||||||
|
COPY . /synapse
|
||||||
|
|
||||||
|
# A wheel cache may be provided in ./cache for faster build
|
||||||
|
RUN cd /synapse \
|
||||||
|
&& pip install --upgrade pip setuptools psycopg2 lxml \
|
||||||
|
&& mkdir -p /synapse/cache \
|
||||||
|
&& pip install -f /synapse/cache --upgrade --process-dependency-links . \
|
||||||
|
&& mv /synapse/contrib/docker/start.py /synapse/contrib/docker/conf / \
|
||||||
|
&& rm -rf setup.py setup.cfg synapse
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
EXPOSE 8008/tcp 8448/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["/start.py"]
|
|
@ -25,6 +25,8 @@ recursive-include synapse/static *.js
|
||||||
exclude jenkins.sh
|
exclude jenkins.sh
|
||||||
exclude jenkins*.sh
|
exclude jenkins*.sh
|
||||||
exclude jenkins*
|
exclude jenkins*
|
||||||
|
exclude Dockerfile
|
||||||
|
exclude .dockerignore
|
||||||
recursive-exclude jenkins *.sh
|
recursive-exclude jenkins *.sh
|
||||||
|
|
||||||
prune .github
|
prune .github
|
||||||
|
|
|
@ -157,8 +157,9 @@ if you prefer.
|
||||||
|
|
||||||
In case of problems, please see the _`Troubleshooting` section below.
|
In case of problems, please see the _`Troubleshooting` section below.
|
||||||
|
|
||||||
Alternatively, Andreas Peters (previously Silvio Fricke) has contributed a Dockerfile to automate the
|
There is an offical synapse image available at https://hub.docker.com/r/matrixdotorg/synapse/tags/ which can be used with the docker-compose file available at `contrib/docker`. Further information on this including configuration options is available in `contrib/docker/README.md`.
|
||||||
above in Docker at https://hub.docker.com/r/avhost/docker-matrix/tags/
|
|
||||||
|
Alternatively, Andreas Peters (previously Silvio Fricke) has contributed a Dockerfile to automate a synapse server in a single Docker image, at https://hub.docker.com/r/avhost/docker-matrix/tags/
|
||||||
|
|
||||||
Also, Martin Giess has created an auto-deployment process with vagrant/ansible,
|
Also, Martin Giess has created an auto-deployment process with vagrant/ansible,
|
||||||
tested with VirtualBox/AWS/DigitalOcean - see https://github.com/EMnify/matrix-synapse-auto-deploy
|
tested with VirtualBox/AWS/DigitalOcean - see https://github.com/EMnify/matrix-synapse-auto-deploy
|
||||||
|
|
153
contrib/docker/README.md
Normal file
153
contrib/docker/README.md
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
# Synapse Docker
|
||||||
|
|
||||||
|
The `matrixdotorg/synapse` Docker image will run Synapse as a single process. It does not provide a
|
||||||
|
database server or a TURN server, you should run these separately.
|
||||||
|
|
||||||
|
If you run a Postgres server, you should simply include it in the same Compose
|
||||||
|
project or set the proper environment variables and the image will automatically
|
||||||
|
use that server.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Build the docker image with the `docker build` command from the root of the synapse repository.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t docker.io/matrixdotorg/synapse .
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-t` option sets the image tag. Official images are tagged `matrixdotorg/synapse:<version>` where `<version>` is the same as the release tag in the synapse git repository.
|
||||||
|
|
||||||
|
You may have a local Python wheel cache available, in which case copy the relevant packages in the ``cache/`` directory at the root of the project.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
This image is designed to run either with an automatically generated configuration
|
||||||
|
file or with a custom configuration that requires manual edition.
|
||||||
|
|
||||||
|
### Automated configuration
|
||||||
|
|
||||||
|
It is recommended that you use Docker Compose to run your containers, including
|
||||||
|
this image and a Postgres server. A sample ``docker-compose.yml`` is provided,
|
||||||
|
including example labels for reverse proxying and other artifacts.
|
||||||
|
|
||||||
|
Read the section about environment variables and set at least mandatory variables,
|
||||||
|
then run the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
If secrets are not specified in the environment variables, they will be generated
|
||||||
|
as part of the startup. Please ensure these secrets are kept between launches of the
|
||||||
|
Docker container, as their loss may require users to log in again.
|
||||||
|
|
||||||
|
### Manual configuration
|
||||||
|
|
||||||
|
A sample ``docker-compose.yml`` is provided, including example labels for
|
||||||
|
reverse proxying and other artifacts. The docker-compose file is an example,
|
||||||
|
please comment/uncomment sections that are not suitable for your usecase.
|
||||||
|
|
||||||
|
Specify a ``SYNAPSE_CONFIG_PATH``, preferably to a persistent path,
|
||||||
|
to use manual configuration. To generate a fresh ``homeserver.yaml``, simply run:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose run --rm -e SYNAPSE_SERVER_NAME=my.matrix.host synapse generate
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, customize your configuration and run the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Without Compose
|
||||||
|
|
||||||
|
If you do not wish to use Compose, you may still run this image using plain
|
||||||
|
Docker commands. Note that the following is just a guideline and you may need
|
||||||
|
to add parameters to the docker run command to account for the network situation
|
||||||
|
with your postgres database.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run \
|
||||||
|
-d \
|
||||||
|
--name synapse \
|
||||||
|
-v ${DATA_PATH}:/data \
|
||||||
|
-e SYNAPSE_SERVER_NAME=my.matrix.host \
|
||||||
|
-e SYNAPSE_REPORT_STATS=yes \
|
||||||
|
docker.io/matrixdotorg/synapse:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Volumes
|
||||||
|
|
||||||
|
The image expects a single volume, located at ``/data``, that will hold:
|
||||||
|
|
||||||
|
* temporary files during uploads;
|
||||||
|
* uploaded media and thumbnails;
|
||||||
|
* the SQLite database if you do not configure postgres;
|
||||||
|
* the appservices configuration.
|
||||||
|
|
||||||
|
You are free to use separate volumes depending on storage endpoints at your
|
||||||
|
disposal. For instance, ``/data/media`` coud be stored on a large but low
|
||||||
|
performance hdd storage while other files could be stored on high performance
|
||||||
|
endpoints.
|
||||||
|
|
||||||
|
In order to setup an application service, simply create an ``appservices``
|
||||||
|
directory in the data volume and write the application service Yaml
|
||||||
|
configuration file there. Multiple application services are supported.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Unless you specify a custom path for the configuration file, a very generic
|
||||||
|
file will be generated, based on the following environment settings.
|
||||||
|
These are a good starting point for setting up your own deployment.
|
||||||
|
|
||||||
|
Global settings:
|
||||||
|
|
||||||
|
* ``UID``, the user id Synapse will run as [default 991]
|
||||||
|
* ``GID``, the group id Synapse will run as [default 991]
|
||||||
|
* ``SYNAPSE_CONFIG_PATH``, path to a custom config file
|
||||||
|
|
||||||
|
If ``SYNAPSE_CONFIG_PATH`` is set, you should generate a configuration file
|
||||||
|
then customize it manually. No other environment variable is required.
|
||||||
|
|
||||||
|
Otherwise, a dynamic configuration file will be used. The following environment
|
||||||
|
variables are available for configuration:
|
||||||
|
|
||||||
|
* ``SYNAPSE_SERVER_NAME`` (mandatory), the current server public hostname.
|
||||||
|
* ``SYNAPSE_REPORT_STATS``, (mandatory, ``yes`` or ``no``), enable anonymous
|
||||||
|
statistics reporting back to the Matrix project which helps us to get funding.
|
||||||
|
* ``SYNAPSE_NO_TLS``, set this variable to disable TLS in Synapse (use this if
|
||||||
|
you run your own TLS-capable reverse proxy).
|
||||||
|
* ``SYNAPSE_ENABLE_REGISTRATION``, set this variable to enable registration on
|
||||||
|
the Synapse instance.
|
||||||
|
* ``SYNAPSE_ALLOW_GUEST``, set this variable to allow guest joining this server.
|
||||||
|
* ``SYNAPSE_EVENT_CACHE_SIZE``, the event cache size [default `10K`].
|
||||||
|
* ``SYNAPSE_CACHE_FACTOR``, the cache factor [default `0.5`].
|
||||||
|
* ``SYNAPSE_RECAPTCHA_PUBLIC_KEY``, set this variable to the recaptcha public
|
||||||
|
key in order to enable recaptcha upon registration.
|
||||||
|
* ``SYNAPSE_RECAPTCHA_PRIVATE_KEY``, set this variable to the recaptcha private
|
||||||
|
key in order to enable recaptcha upon registration.
|
||||||
|
* ``SYNAPSE_TURN_URIS``, set this variable to the coma-separated list of TURN
|
||||||
|
uris to enable TURN for this homeserver.
|
||||||
|
* ``SYNAPSE_TURN_SECRET``, set this to the TURN shared secret if required.
|
||||||
|
|
||||||
|
Shared secrets, that will be initialized to random values if not set:
|
||||||
|
|
||||||
|
* ``SYNAPSE_REGISTRATION_SHARED_SECRET``, secret for registrering users if
|
||||||
|
registration is disable.
|
||||||
|
* ``SYNAPSE_MACAROON_SECRET_KEY`` secret for signing access tokens
|
||||||
|
to the server.
|
||||||
|
|
||||||
|
Database specific values (will use SQLite if not set):
|
||||||
|
|
||||||
|
* `POSTGRES_DB` - The database name for the synapse postgres database. [default: `synapse`]
|
||||||
|
* `POSTGRES_HOST` - The host of the postgres database if you wish to use postgresql instead of sqlite3. [default: `db` which is useful when using a container on the same docker network in a compose file where the postgres service is called `db`]
|
||||||
|
* `POSTGRES_PASSWORD` - The password for the synapse postgres database. **If this is set then postgres will be used instead of sqlite3.** [default: none] **NOTE**: You are highly encouraged to use postgresql! Please use the compose file to make it easier to deploy.
|
||||||
|
* `POSTGRES_USER` - The user for the synapse postgres database. [default: `matrix`]
|
||||||
|
|
||||||
|
Mail server specific values (will not send emails if not set):
|
||||||
|
|
||||||
|
* ``SYNAPSE_SMTP_HOST``, hostname to the mail server.
|
||||||
|
* ``SYNAPSE_SMTP_PORT``, TCP port for accessing the mail server [default ``25``].
|
||||||
|
* ``SYNAPSE_SMTP_USER``, username for authenticating against the mail server if any.
|
||||||
|
* ``SYNAPSE_SMTP_PASSWORD``, password for authenticating against the mail server if any.
|
219
contrib/docker/conf/homeserver.yaml
Normal file
219
contrib/docker/conf/homeserver.yaml
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
# vim:ft=yaml
|
||||||
|
|
||||||
|
## TLS ##
|
||||||
|
|
||||||
|
tls_certificate_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.crt"
|
||||||
|
tls_private_key_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.key"
|
||||||
|
tls_dh_params_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.dh"
|
||||||
|
no_tls: {{ "True" if SYNAPSE_NO_TLS else "False" }}
|
||||||
|
tls_fingerprints: []
|
||||||
|
|
||||||
|
## Server ##
|
||||||
|
|
||||||
|
server_name: "{{ SYNAPSE_SERVER_NAME }}"
|
||||||
|
pid_file: /homeserver.pid
|
||||||
|
web_client: False
|
||||||
|
soft_file_limit: 0
|
||||||
|
|
||||||
|
## Ports ##
|
||||||
|
|
||||||
|
listeners:
|
||||||
|
{% if not SYNAPSE_NO_TLS %}
|
||||||
|
-
|
||||||
|
port: 8448
|
||||||
|
bind_addresses: ['0.0.0.0']
|
||||||
|
type: http
|
||||||
|
tls: true
|
||||||
|
x_forwarded: false
|
||||||
|
resources:
|
||||||
|
- names: [client]
|
||||||
|
compress: true
|
||||||
|
- names: [federation] # Federation APIs
|
||||||
|
compress: false
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
bind_addresses: ['0.0.0.0']
|
||||||
|
type: http
|
||||||
|
x_forwarded: false
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- names: [client]
|
||||||
|
compress: true
|
||||||
|
- names: [federation]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
## Database ##
|
||||||
|
|
||||||
|
{% if POSTGRES_PASSWORD %}
|
||||||
|
database:
|
||||||
|
name: "psycopg2"
|
||||||
|
args:
|
||||||
|
user: "{{ POSTGRES_USER or "synapse" }}"
|
||||||
|
password: "{{ POSTGRES_PASSWORD }}"
|
||||||
|
database: "{{ POSTGRES_DB or "synapse" }}"
|
||||||
|
host: "{{ POSTGRES_HOST or "db" }}"
|
||||||
|
port: "{{ POSTGRES_PORT or "5432" }}"
|
||||||
|
cp_min: 5
|
||||||
|
cp_max: 10
|
||||||
|
{% else %}
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: "/data/homeserver.db"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Performance ##
|
||||||
|
|
||||||
|
event_cache_size: "{{ SYNAPSE_EVENT_CACHE_SIZE or "10K" }}"
|
||||||
|
verbose: 0
|
||||||
|
log_file: "/data/homeserver.log"
|
||||||
|
log_config: "/compiled/log.config"
|
||||||
|
|
||||||
|
## Ratelimiting ##
|
||||||
|
|
||||||
|
rc_messages_per_second: 0.2
|
||||||
|
rc_message_burst_count: 10.0
|
||||||
|
federation_rc_window_size: 1000
|
||||||
|
federation_rc_sleep_limit: 10
|
||||||
|
federation_rc_sleep_delay: 500
|
||||||
|
federation_rc_reject_limit: 50
|
||||||
|
federation_rc_concurrent: 3
|
||||||
|
|
||||||
|
## Files ##
|
||||||
|
|
||||||
|
media_store_path: "/data/media"
|
||||||
|
uploads_path: "/data/uploads"
|
||||||
|
max_upload_size: "10M"
|
||||||
|
max_image_pixels: "32M"
|
||||||
|
dynamic_thumbnails: false
|
||||||
|
|
||||||
|
# List of thumbnail to precalculate when an image is uploaded.
|
||||||
|
thumbnail_sizes:
|
||||||
|
- width: 32
|
||||||
|
height: 32
|
||||||
|
method: crop
|
||||||
|
- width: 96
|
||||||
|
height: 96
|
||||||
|
method: crop
|
||||||
|
- width: 320
|
||||||
|
height: 240
|
||||||
|
method: scale
|
||||||
|
- width: 640
|
||||||
|
height: 480
|
||||||
|
method: scale
|
||||||
|
- width: 800
|
||||||
|
height: 600
|
||||||
|
method: scale
|
||||||
|
|
||||||
|
url_preview_enabled: False
|
||||||
|
max_spider_size: "10M"
|
||||||
|
|
||||||
|
## Captcha ##
|
||||||
|
|
||||||
|
{% if SYNAPSE_RECAPTCHA_PUBLIC_KEY %}
|
||||||
|
recaptcha_public_key: "{{ SYNAPSE_RECAPTCHA_PUBLIC_KEY }}"
|
||||||
|
recaptcha_private_key: "{{ SYNAPSE_RECAPTCHA_PRIVATE_KEY }}"
|
||||||
|
enable_registration_captcha: True
|
||||||
|
recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
{% else %}
|
||||||
|
recaptcha_public_key: "YOUR_PUBLIC_KEY"
|
||||||
|
recaptcha_private_key: "YOUR_PRIVATE_KEY"
|
||||||
|
enable_registration_captcha: False
|
||||||
|
recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Turn ##
|
||||||
|
|
||||||
|
{% if SYNAPSE_TURN_URIS %}
|
||||||
|
turn_uris:
|
||||||
|
{% for uri in SYNAPSE_TURN_URIS.split(',') %} - "{{ uri }}"
|
||||||
|
{% endfor %}
|
||||||
|
turn_shared_secret: "{{ SYNAPSE_TURN_SECRET }}"
|
||||||
|
turn_user_lifetime: "1h"
|
||||||
|
turn_allow_guests: True
|
||||||
|
{% else %}
|
||||||
|
turn_uris: []
|
||||||
|
turn_shared_secret: "YOUR_SHARED_SECRET"
|
||||||
|
turn_user_lifetime: "1h"
|
||||||
|
turn_allow_guests: True
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## Registration ##
|
||||||
|
|
||||||
|
enable_registration: {{ "True" if SYNAPSE_ENABLE_REGISTRATION else "False" }}
|
||||||
|
registration_shared_secret: "{{ SYNAPSE_REGISTRATION_SHARED_SECRET }}"
|
||||||
|
bcrypt_rounds: 12
|
||||||
|
allow_guest_access: {{ "True" if SYNAPSE_ALLOW_GUEST else "False" }}
|
||||||
|
enable_group_creation: true
|
||||||
|
|
||||||
|
# The list of identity servers trusted to verify third party
|
||||||
|
# identifiers by this server.
|
||||||
|
trusted_third_party_id_servers:
|
||||||
|
- matrix.org
|
||||||
|
- vector.im
|
||||||
|
- riot.im
|
||||||
|
|
||||||
|
## Metrics ###
|
||||||
|
|
||||||
|
{% if SYNAPSE_REPORT_STATS.lower() == "yes" %}
|
||||||
|
enable_metrics: True
|
||||||
|
report_stats: True
|
||||||
|
{% else %}
|
||||||
|
enable_metrics: False
|
||||||
|
report_stats: False
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
## API Configuration ##
|
||||||
|
|
||||||
|
room_invite_state_types:
|
||||||
|
- "m.room.join_rules"
|
||||||
|
- "m.room.canonical_alias"
|
||||||
|
- "m.room.avatar"
|
||||||
|
- "m.room.name"
|
||||||
|
|
||||||
|
{% if SYNAPSE_APPSERVICES %}
|
||||||
|
app_service_config_files:
|
||||||
|
{% for appservice in SYNAPSE_APPSERVICES %} - "{{ appservice }}"
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
app_service_config_files: []
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
macaroon_secret_key: "{{ SYNAPSE_MACAROON_SECRET_KEY }}"
|
||||||
|
expire_access_token: False
|
||||||
|
|
||||||
|
## Signing Keys ##
|
||||||
|
|
||||||
|
signing_key_path: "/data/{{ SYNAPSE_SERVER_NAME }}.signing.key"
|
||||||
|
old_signing_keys: {}
|
||||||
|
key_refresh_interval: "1d" # 1 Day.
|
||||||
|
|
||||||
|
# The trusted servers to download signing keys from.
|
||||||
|
perspectives:
|
||||||
|
servers:
|
||||||
|
"matrix.org":
|
||||||
|
verify_keys:
|
||||||
|
"ed25519:auto":
|
||||||
|
key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
|
||||||
|
|
||||||
|
password_config:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
{% if SYNAPSE_SMTP_HOST %}
|
||||||
|
email:
|
||||||
|
enable_notifs: false
|
||||||
|
smtp_host: "{{ SYNAPSE_SMTP_HOST }}"
|
||||||
|
smtp_port: {{ SYNAPSE_SMTP_PORT or "25" }}
|
||||||
|
smtp_user: "{{ SYNAPSE_SMTP_USER }}"
|
||||||
|
smtp_pass: "{{ SYNAPSE_SMTP_PASSWORD }}"
|
||||||
|
require_transport_security: False
|
||||||
|
notif_from: "{{ SYNAPSE_SMTP_FROM or "hostmaster@" + SYNAPSE_SERVER_NAME }}"
|
||||||
|
app_name: Matrix
|
||||||
|
template_dir: res/templates
|
||||||
|
notif_template_html: notif_mail.html
|
||||||
|
notif_template_text: notif_mail.txt
|
||||||
|
notif_for_new_users: True
|
||||||
|
riot_base_url: "https://{{ SYNAPSE_SERVER_NAME }}"
|
||||||
|
{% endif %}
|
29
contrib/docker/conf/log.config
Normal file
29
contrib/docker/conf/log.config
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s- %(message)s'
|
||||||
|
|
||||||
|
filters:
|
||||||
|
context:
|
||||||
|
(): synapse.util.logcontext.LoggingContextFilter
|
||||||
|
request: ""
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
filters: [context]
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
synapse:
|
||||||
|
level: {{ SYNAPSE_LOG_LEVEL or "WARNING" }}
|
||||||
|
|
||||||
|
synapse.storage.SQL:
|
||||||
|
# beware: increasing this to DEBUG will make synapse log sensitive
|
||||||
|
# information such as access tokens.
|
||||||
|
level: {{ SYNAPSE_LOG_LEVEL or "WARNING" }}
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: {{ SYNAPSE_LOG_LEVEL or "WARNING" }}
|
||||||
|
handlers: [console]
|
49
contrib/docker/docker-compose.yml
Normal file
49
contrib/docker/docker-compose.yml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# This compose file is compatible with Compose itself, it might need some
|
||||||
|
# adjustments to run properly with stack.
|
||||||
|
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
synapse:
|
||||||
|
image: docker.io/matrixdotorg/synapse:latest
|
||||||
|
# Since snyapse does not retry to connect to the database, restart upon
|
||||||
|
# failure
|
||||||
|
restart: unless-stopped
|
||||||
|
# See the readme for a full documentation of the environment settings
|
||||||
|
environment:
|
||||||
|
- SYNAPSE_SERVER_NAME=my.matrix.host
|
||||||
|
- SYNAPSE_REPORT_STATS=no
|
||||||
|
- SYNAPSE_ENABLE_REGISTRATION=yes
|
||||||
|
- SYNAPSE_LOG_LEVEL=INFO
|
||||||
|
- POSTGRES_PASSWORD=changeme
|
||||||
|
volumes:
|
||||||
|
# You may either store all the files in a local folder
|
||||||
|
- ./files:/data
|
||||||
|
# .. or you may split this between different storage points
|
||||||
|
# - ./files:/data
|
||||||
|
# - /path/to/ssd:/data/uploads
|
||||||
|
# - /path/to/large_hdd:/data/media
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
# In order to expose Synapse, remove one of the following, you might for
|
||||||
|
# instance expose the TLS port directly:
|
||||||
|
ports:
|
||||||
|
- 8448:8448/tcp
|
||||||
|
# ... or use a reverse proxy, here is an example for traefik:
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.frontend.rule=Host:my.matrix.Host
|
||||||
|
- traefik.port=8448
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/postgres:10-alpine
|
||||||
|
# Change that password, of course!
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=synapse
|
||||||
|
- POSTGRES_PASSWORD=changeme
|
||||||
|
volumes:
|
||||||
|
# You may store the database tables in a local folder..
|
||||||
|
- ./schemas:/var/lib/postgresql/data
|
||||||
|
# .. or store them on some high performance storage for better results
|
||||||
|
# - /path/to/ssd/storage:/var/lib/postfesql/data
|
66
contrib/docker/start.py
Executable file
66
contrib/docker/start.py
Executable file
|
@ -0,0 +1,66 @@
|
||||||
|
#!/usr/local/bin/python
|
||||||
|
|
||||||
|
import jinja2
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import glob
|
||||||
|
|
||||||
|
# Utility functions
|
||||||
|
convert = lambda src, dst, environ: open(dst, "w").write(jinja2.Template(open(src).read()).render(**environ))
|
||||||
|
|
||||||
|
def check_arguments(environ, args):
|
||||||
|
for argument in args:
|
||||||
|
if argument not in environ:
|
||||||
|
print("Environment variable %s is mandatory, exiting." % argument)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
def generate_secrets(environ, secrets):
|
||||||
|
for name, secret in secrets.items():
|
||||||
|
if secret not in environ:
|
||||||
|
filename = "/data/%s.%s.key" % (environ["SYNAPSE_SERVER_NAME"], name)
|
||||||
|
if os.path.exists(filename):
|
||||||
|
with open(filename) as handle: value = handle.read()
|
||||||
|
else:
|
||||||
|
print("Generating a random secret for {}".format(name))
|
||||||
|
value = os.urandom(32).encode("hex")
|
||||||
|
with open(filename, "w") as handle: handle.write(value)
|
||||||
|
environ[secret] = value
|
||||||
|
|
||||||
|
# Prepare the configuration
|
||||||
|
mode = sys.argv[1] if len(sys.argv) > 1 else None
|
||||||
|
environ = os.environ.copy()
|
||||||
|
ownership = "{}:{}".format(environ.get("UID", 991), environ.get("GID", 991))
|
||||||
|
args = ["python", "-m", "synapse.app.homeserver"]
|
||||||
|
|
||||||
|
# In generate mode, generate a configuration, missing keys, then exit
|
||||||
|
if mode == "generate":
|
||||||
|
check_arguments(environ, ("SYNAPSE_SERVER_NAME", "SYNAPSE_REPORT_STATS", "SYNAPSE_CONFIG_PATH"))
|
||||||
|
args += [
|
||||||
|
"--server-name", environ["SYNAPSE_SERVER_NAME"],
|
||||||
|
"--report-stats", environ["SYNAPSE_REPORT_STATS"],
|
||||||
|
"--config-path", environ["SYNAPSE_CONFIG_PATH"],
|
||||||
|
"--generate-config"
|
||||||
|
]
|
||||||
|
os.execv("/usr/local/bin/python", args)
|
||||||
|
|
||||||
|
# In normal mode, generate missing keys if any, then run synapse
|
||||||
|
else:
|
||||||
|
# Parse the configuration file
|
||||||
|
if "SYNAPSE_CONFIG_PATH" in environ:
|
||||||
|
args += ["--config-path", environ["SYNAPSE_CONFIG_PATH"]]
|
||||||
|
else:
|
||||||
|
check_arguments(environ, ("SYNAPSE_SERVER_NAME", "SYNAPSE_REPORT_STATS"))
|
||||||
|
generate_secrets(environ, {
|
||||||
|
"registration": "SYNAPSE_REGISTRATION_SHARED_SECRET",
|
||||||
|
"macaroon": "SYNAPSE_MACAROON_SECRET_KEY"
|
||||||
|
})
|
||||||
|
environ["SYNAPSE_APPSERVICES"] = glob.glob("/data/appservices/*.yaml")
|
||||||
|
if not os.path.exists("/compiled"): os.mkdir("/compiled")
|
||||||
|
convert("/conf/homeserver.yaml", "/compiled/homeserver.yaml", environ)
|
||||||
|
convert("/conf/log.config", "/compiled/log.config", environ)
|
||||||
|
subprocess.check_output(["chown", "-R", ownership, "/data"])
|
||||||
|
args += ["--config-path", "/compiled/homeserver.yaml"]
|
||||||
|
# Generate missing keys and start synapse
|
||||||
|
subprocess.check_output(args + ["--generate-keys"])
|
||||||
|
os.execv("/sbin/su-exec", ["su-exec", ownership] + args)
|
160
docs/consent_tracking.md
Normal file
160
docs/consent_tracking.md
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
Support in Synapse for tracking agreement to server terms and conditions
|
||||||
|
========================================================================
|
||||||
|
|
||||||
|
Synapse 0.30 introduces support for tracking whether users have agreed to the
|
||||||
|
terms and conditions set by the administrator of a server - and blocking access
|
||||||
|
to the server until they have.
|
||||||
|
|
||||||
|
There are several parts to this functionality; each requires some specific
|
||||||
|
configuration in `homeserver.yaml` to be enabled.
|
||||||
|
|
||||||
|
Note that various parts of the configuation and this document refer to the
|
||||||
|
"privacy policy": agreement with a privacy policy is one particular use of this
|
||||||
|
feature, but of course adminstrators can specify other terms and conditions
|
||||||
|
unrelated to "privacy" per se.
|
||||||
|
|
||||||
|
Collecting policy agreement from a user
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
Synapse can be configured to serve the user a simple policy form with an
|
||||||
|
"accept" button. Clicking "Accept" records the user's acceptance in the
|
||||||
|
database and shows a success page.
|
||||||
|
|
||||||
|
To enable this, first create templates for the policy and success pages.
|
||||||
|
These should be stored on the local filesystem.
|
||||||
|
|
||||||
|
These templates use the [Jinja2](http://jinja.pocoo.org) templating language,
|
||||||
|
and [docs/privacy_policy_templates](privacy_policy_templates) gives
|
||||||
|
examples of the sort of thing that can be done.
|
||||||
|
|
||||||
|
Note that the templates must be stored under a name giving the language of the
|
||||||
|
template - currently this must always be `en` (for "English");
|
||||||
|
internationalisation support is intended for the future.
|
||||||
|
|
||||||
|
The template for the policy itself should be versioned and named according to
|
||||||
|
the version: for example `1.0.html`. The version of the policy which the user
|
||||||
|
has agreed to is stored in the database.
|
||||||
|
|
||||||
|
Once the templates are in place, make the following changes to `homeserver.yaml`:
|
||||||
|
|
||||||
|
1. Add a `user_consent` section, which should look like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
user_consent:
|
||||||
|
template_dir: privacy_policy_templates
|
||||||
|
version: 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
`template_dir` points to the directory containing the policy
|
||||||
|
templates. `version` defines the version of the policy which will be served
|
||||||
|
to the user. In the example above, Synapse will serve
|
||||||
|
`privacy_policy_templates/en/1.0.html`.
|
||||||
|
|
||||||
|
|
||||||
|
2. Add a `form_secret` setting at the top level:
|
||||||
|
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
form_secret: "<unique secret>"
|
||||||
|
```
|
||||||
|
|
||||||
|
This should be set to an arbitrary secret string (try `pwgen -y 30` to
|
||||||
|
generate suitable secrets).
|
||||||
|
|
||||||
|
More on what this is used for below.
|
||||||
|
|
||||||
|
3. Add `consent` wherever the `client` resource is currently enabled in the
|
||||||
|
`listeners` configuration. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
resources:
|
||||||
|
- names:
|
||||||
|
- client
|
||||||
|
- consent
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Finally, ensure that `jinja2` is installed. If you are using a virtualenv, this
|
||||||
|
should be a matter of `pip install Jinja2`. On debian, try `apt-get install
|
||||||
|
python-jinja2`.
|
||||||
|
|
||||||
|
Once this is complete, and the server has been restarted, try visiting
|
||||||
|
`https://<server>/_matrix/consent`. If correctly configured, this should give
|
||||||
|
an error "Missing string query parameter 'u'". It is now possible to manually
|
||||||
|
construct URIs where users can give their consent.
|
||||||
|
|
||||||
|
### Constructing the consent URI
|
||||||
|
|
||||||
|
It may be useful to manually construct the "consent URI" for a given user - for
|
||||||
|
instance, in order to send them an email asking them to consent. To do this,
|
||||||
|
take the base `https://<server>/_matrix/consent` URL and add the following
|
||||||
|
query parameters:
|
||||||
|
|
||||||
|
* `u`: the user id of the user. This can either be a full MXID
|
||||||
|
(`@user:server.com`) or just the localpart (`user`).
|
||||||
|
|
||||||
|
* `h`: hex-encoded HMAC-SHA256 of `u` using the `form_secret` as a key. It is
|
||||||
|
possible to calculate this on the commandline with something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo -n '<user>' | openssl sha256 -hmac '<form_secret>'
|
||||||
|
```
|
||||||
|
|
||||||
|
This should result in a URI which looks something like:
|
||||||
|
`https://<server>/_matrix/consent?u=<user>&h=68a152465a4d...`.
|
||||||
|
|
||||||
|
|
||||||
|
Sending users a server notice asking them to agree to the policy
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
It is possible to configure Synapse to send a [server
|
||||||
|
notice](server_notices.md) to anybody who has not yet agreed to the current
|
||||||
|
version of the policy. To do so:
|
||||||
|
|
||||||
|
* ensure that the consent resource is configured, as in the previous section
|
||||||
|
|
||||||
|
* ensure that server notices are configured, as in [server_notices.md](server_notices.md).
|
||||||
|
|
||||||
|
* Add `server_notice_content` under `user_consent` in `homeserver.yaml`. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
user_consent:
|
||||||
|
server_notice_content:
|
||||||
|
msgtype: m.text
|
||||||
|
body: >-
|
||||||
|
Please give your consent to the privacy policy at %(consent_uri)s.
|
||||||
|
```
|
||||||
|
|
||||||
|
Synapse automatically replaces the placeholder `%(consent_uri)s` with the
|
||||||
|
consent uri for that user.
|
||||||
|
|
||||||
|
* ensure that `public_baseurl` is set in `homeserver.yaml`, and gives the base
|
||||||
|
URI that clients use to connect to the server. (It is used to construct
|
||||||
|
`consent_uri` in the server notice.)
|
||||||
|
|
||||||
|
|
||||||
|
Blocking users from using the server until they agree to the policy
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
|
Synapse can be configured to block any attempts to join rooms or send messages
|
||||||
|
until the user has given their agreement to the policy. (Joining the server
|
||||||
|
notices room is exempted from this).
|
||||||
|
|
||||||
|
To enable this, add `block_events_error` under `user_consent`. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
user_consent:
|
||||||
|
block_events_error: >-
|
||||||
|
You can't send any messages until you consent to the privacy policy at
|
||||||
|
%(consent_uri)s.
|
||||||
|
```
|
||||||
|
|
||||||
|
Synapse automatically replaces the placeholder `%(consent_uri)s` with the
|
||||||
|
consent uri for that user.
|
||||||
|
|
||||||
|
ensure that `public_baseurl` is set in `homeserver.yaml`, and gives the base
|
||||||
|
URI that clients use to connect to the server. (It is used to construct
|
||||||
|
`consent_uri` in the error.)
|
43
docs/manhole.md
Normal file
43
docs/manhole.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
Using the synapse manhole
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The "manhole" allows server administrators to access a Python shell on a running
|
||||||
|
Synapse installation. This is a very powerful mechanism for administration and
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
To enable it, first uncomment the `manhole` listener configuration in
|
||||||
|
`homeserver.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
listeners:
|
||||||
|
- port: 9000
|
||||||
|
bind_addresses: ['::1', '127.0.0.1']
|
||||||
|
type: manhole
|
||||||
|
```
|
||||||
|
|
||||||
|
(`bind_addresses` in the above is important: it ensures that access to the
|
||||||
|
manhole is only possible for local users).
|
||||||
|
|
||||||
|
Note that this will give administrative access to synapse to **all users** with
|
||||||
|
shell access to the server. It should therefore **not** be enabled in
|
||||||
|
environments where untrusted users have shell access.
|
||||||
|
|
||||||
|
Then restart synapse, and point an ssh client at port 9000 on localhost, using
|
||||||
|
the username `matrix`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p9000 matrix@localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
The password is `rabbithole`.
|
||||||
|
|
||||||
|
This gives a Python REPL in which `hs` gives access to the
|
||||||
|
`synapse.server.HomeServer` object - which in turn gives access to many other
|
||||||
|
parts of the process.
|
||||||
|
|
||||||
|
As a simple example, retrieving an event from the database:
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> hs.get_datastore().get_event('$1416420717069yeQaw:matrix.org')
|
||||||
|
<Deferred at 0x7ff253fc6998 current result: <FrozenEvent event_id='$1416420717069yeQaw:matrix.org', type='m.room.create', state_key=''>>
|
||||||
|
```
|
|
@ -1,25 +1,47 @@
|
||||||
How to monitor Synapse metrics using Prometheus
|
How to monitor Synapse metrics using Prometheus
|
||||||
===============================================
|
===============================================
|
||||||
|
|
||||||
1. Install prometheus:
|
1. Install Prometheus:
|
||||||
|
|
||||||
Follow instructions at http://prometheus.io/docs/introduction/install/
|
Follow instructions at http://prometheus.io/docs/introduction/install/
|
||||||
|
|
||||||
2. Enable synapse metrics:
|
2. Enable Synapse metrics:
|
||||||
|
|
||||||
Simply setting a (local) port number will enable it. Pick a port.
|
There are two methods of enabling metrics in Synapse.
|
||||||
prometheus itself defaults to 9090, so starting just above that for
|
|
||||||
locally monitored services seems reasonable. E.g. 9092:
|
|
||||||
|
|
||||||
Add to homeserver.yaml::
|
The first serves the metrics as a part of the usual web server and can be
|
||||||
|
enabled by adding the "metrics" resource to the existing listener as such::
|
||||||
|
|
||||||
metrics_port: 9092
|
resources:
|
||||||
|
- names:
|
||||||
|
- client
|
||||||
|
- metrics
|
||||||
|
|
||||||
Also ensure that ``enable_metrics`` is set to ``True``.
|
This provides a simple way of adding metrics to your Synapse installation,
|
||||||
|
and serves under ``/_synapse/metrics``. If you do not wish your metrics be
|
||||||
|
publicly exposed, you will need to either filter it out at your load
|
||||||
|
balancer, or use the second method.
|
||||||
|
|
||||||
Restart synapse.
|
The second method runs the metrics server on a different port, in a
|
||||||
|
different thread to Synapse. This can make it more resilient to heavy load
|
||||||
|
meaning metrics cannot be retrieved, and can be exposed to just internal
|
||||||
|
networks easier. The served metrics are available over HTTP only, and will
|
||||||
|
be available at ``/``.
|
||||||
|
|
||||||
3. Add a prometheus target for synapse.
|
Add a new listener to homeserver.yaml::
|
||||||
|
|
||||||
|
listeners:
|
||||||
|
- type: metrics
|
||||||
|
port: 9000
|
||||||
|
bind_addresses:
|
||||||
|
- '0.0.0.0'
|
||||||
|
|
||||||
|
For both options, you will need to ensure that ``enable_metrics`` is set to
|
||||||
|
``True``.
|
||||||
|
|
||||||
|
Restart Synapse.
|
||||||
|
|
||||||
|
3. Add a Prometheus target for Synapse.
|
||||||
|
|
||||||
It needs to set the ``metrics_path`` to a non-default value (under ``scrape_configs``)::
|
It needs to set the ``metrics_path`` to a non-default value (under ``scrape_configs``)::
|
||||||
|
|
||||||
|
@ -31,7 +53,50 @@ How to monitor Synapse metrics using Prometheus
|
||||||
If your prometheus is older than 1.5.2, you will need to replace
|
If your prometheus is older than 1.5.2, you will need to replace
|
||||||
``static_configs`` in the above with ``target_groups``.
|
``static_configs`` in the above with ``target_groups``.
|
||||||
|
|
||||||
Restart prometheus.
|
Restart Prometheus.
|
||||||
|
|
||||||
|
|
||||||
|
Removal of deprecated metrics & time based counters becoming histograms in 0.31.0
|
||||||
|
---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
The duplicated metrics deprecated in Synapse 0.27.0 have been removed.
|
||||||
|
|
||||||
|
All time duration-based metrics have been changed to be seconds. This affects:
|
||||||
|
|
||||||
|
+----------------------------------+
|
||||||
|
| msec -> sec metrics |
|
||||||
|
+==================================+
|
||||||
|
| python_gc_time |
|
||||||
|
+----------------------------------+
|
||||||
|
| python_twisted_reactor_tick_time |
|
||||||
|
+----------------------------------+
|
||||||
|
| synapse_storage_query_time |
|
||||||
|
+----------------------------------+
|
||||||
|
| synapse_storage_schedule_time |
|
||||||
|
+----------------------------------+
|
||||||
|
| synapse_storage_transaction_time |
|
||||||
|
+----------------------------------+
|
||||||
|
|
||||||
|
Several metrics have been changed to be histograms, which sort entries into
|
||||||
|
buckets and allow better analysis. The following metrics are now histograms:
|
||||||
|
|
||||||
|
+-------------------------------------------+
|
||||||
|
| Altered metrics |
|
||||||
|
+===========================================+
|
||||||
|
| python_gc_time |
|
||||||
|
+-------------------------------------------+
|
||||||
|
| python_twisted_reactor_pending_calls |
|
||||||
|
+-------------------------------------------+
|
||||||
|
| python_twisted_reactor_tick_time |
|
||||||
|
+-------------------------------------------+
|
||||||
|
| synapse_http_server_response_time_seconds |
|
||||||
|
+-------------------------------------------+
|
||||||
|
| synapse_storage_query_time |
|
||||||
|
+-------------------------------------------+
|
||||||
|
| synapse_storage_schedule_time |
|
||||||
|
+-------------------------------------------+
|
||||||
|
| synapse_storage_transaction_time |
|
||||||
|
+-------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
Block and response metrics renamed for 0.27.0
|
Block and response metrics renamed for 0.27.0
|
||||||
|
|
|
@ -6,7 +6,13 @@ Postgres version 9.4 or later is known to work.
|
||||||
Set up database
|
Set up database
|
||||||
===============
|
===============
|
||||||
|
|
||||||
The PostgreSQL database used *must* have the correct encoding set, otherwise
|
Assuming your PostgreSQL database user is called ``postgres``, create a user
|
||||||
|
``synapse_user`` with::
|
||||||
|
|
||||||
|
su - postgres
|
||||||
|
createuser --pwprompt synapse_user
|
||||||
|
|
||||||
|
The PostgreSQL database used *must* have the correct encoding set, otherwise it
|
||||||
would not be able to store UTF8 strings. To create a database with the correct
|
would not be able to store UTF8 strings. To create a database with the correct
|
||||||
encoding use, e.g.::
|
encoding use, e.g.::
|
||||||
|
|
||||||
|
@ -46,8 +52,8 @@ As with Debian/Ubuntu, postgres support depends on the postgres python connector
|
||||||
Synapse config
|
Synapse config
|
||||||
==============
|
==============
|
||||||
|
|
||||||
When you are ready to start using PostgreSQL, add the following line to your
|
When you are ready to start using PostgreSQL, edit the ``database`` section in
|
||||||
config file::
|
your config file to match the following lines::
|
||||||
|
|
||||||
database:
|
database:
|
||||||
name: psycopg2
|
name: psycopg2
|
||||||
|
@ -96,9 +102,12 @@ complete, restart synapse. For instance::
|
||||||
cp homeserver.db homeserver.db.snapshot
|
cp homeserver.db homeserver.db.snapshot
|
||||||
./synctl start
|
./synctl start
|
||||||
|
|
||||||
Assuming your new config file (as described in the section *Synapse config*)
|
Copy the old config file into a new config file::
|
||||||
is named ``homeserver-postgres.yaml`` and the SQLite snapshot is at
|
|
||||||
``homeserver.db.snapshot`` then simply run::
|
cp homeserver.yaml homeserver-postgres.yaml
|
||||||
|
|
||||||
|
Edit the database section as described in the section *Synapse config* above
|
||||||
|
and with the SQLite snapshot located at ``homeserver.db.snapshot`` simply run::
|
||||||
|
|
||||||
synapse_port_db --sqlite-database homeserver.db.snapshot \
|
synapse_port_db --sqlite-database homeserver.db.snapshot \
|
||||||
--postgres-config homeserver-postgres.yaml
|
--postgres-config homeserver-postgres.yaml
|
||||||
|
@ -117,6 +126,11 @@ run::
|
||||||
--postgres-config homeserver-postgres.yaml
|
--postgres-config homeserver-postgres.yaml
|
||||||
|
|
||||||
Once that has completed, change the synapse config to point at the PostgreSQL
|
Once that has completed, change the synapse config to point at the PostgreSQL
|
||||||
database configuration file ``homeserver-postgres.yaml`` (i.e. rename it to
|
database configuration file ``homeserver-postgres.yaml``:
|
||||||
``homeserver.yaml``) and restart synapse. Synapse should now be running against
|
|
||||||
PostgreSQL.
|
./synctl stop
|
||||||
|
mv homeserver.yaml homeserver-old-sqlite.yaml
|
||||||
|
mv homeserver-postgres.yaml homeserver.yaml
|
||||||
|
./synctl start
|
||||||
|
|
||||||
|
Synapse should now be running against PostgreSQL.
|
||||||
|
|
23
docs/privacy_policy_templates/en/1.0.html
Normal file
23
docs/privacy_policy_templates/en/1.0.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Matrix.org Privacy policy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if has_consented %}
|
||||||
|
<p>
|
||||||
|
Your base already belong to us.
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
All your base are belong to us.
|
||||||
|
</p>
|
||||||
|
<form method="post" action="consent">
|
||||||
|
<input type="hidden" name="v" value="{{version}}"/>
|
||||||
|
<input type="hidden" name="u" value="{{user}}"/>
|
||||||
|
<input type="hidden" name="h" value="{{userhmac}}"/>
|
||||||
|
<input type="submit" value="Sure thing!"/>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
11
docs/privacy_policy_templates/en/success.html
Normal file
11
docs/privacy_policy_templates/en/success.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Matrix.org Privacy policy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Sweet.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
74
docs/server_notices.md
Normal file
74
docs/server_notices.md
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
Server Notices
|
||||||
|
==============
|
||||||
|
|
||||||
|
'Server Notices' are a new feature introduced in Synapse 0.30. They provide a
|
||||||
|
channel whereby server administrators can send messages to users on the server.
|
||||||
|
|
||||||
|
They are used as part of communication of the server polices(see
|
||||||
|
[consent_tracking.md](consent_tracking.md)), however the intention is that
|
||||||
|
they may also find a use for features such as "Message of the day".
|
||||||
|
|
||||||
|
This is a feature specific to Synapse, but it uses standard Matrix
|
||||||
|
communication mechanisms, so should work with any Matrix client.
|
||||||
|
|
||||||
|
User experience
|
||||||
|
---------------
|
||||||
|
|
||||||
|
When the user is first sent a server notice, they will get an invitation to a
|
||||||
|
room (typically called 'Server Notices', though this is configurable in
|
||||||
|
`homeserver.yaml`). They will be **unable to reject** this invitation -
|
||||||
|
attempts to do so will receive an error.
|
||||||
|
|
||||||
|
Once they accept the invitation, they will see the notice message in the room
|
||||||
|
history; it will appear to have come from the 'server notices user' (see
|
||||||
|
below).
|
||||||
|
|
||||||
|
The user is prevented from sending any messages in this room by the power
|
||||||
|
levels.
|
||||||
|
|
||||||
|
Having joined the room, the user can leave the room if they want. Subsequent
|
||||||
|
server notices will then cause a new room to be created.
|
||||||
|
|
||||||
|
Synapse configuration
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Server notices come from a specific user id on the server. Server
|
||||||
|
administrators are free to choose the user id - something like `server` is
|
||||||
|
suggested, meaning the notices will come from
|
||||||
|
`@server:<your_server_name>`. Once the Server Notices user is configured, that
|
||||||
|
user id becomes a special, privileged user, so administrators should ensure
|
||||||
|
that **it is not already allocated**.
|
||||||
|
|
||||||
|
In order to support server notices, it is necessary to add some configuration
|
||||||
|
to the `homeserver.yaml` file. In particular, you should add a `server_notices`
|
||||||
|
section, which should look like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server_notices:
|
||||||
|
system_mxid_localpart: server
|
||||||
|
system_mxid_display_name: "Server Notices"
|
||||||
|
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
room_name: "Server Notices"
|
||||||
|
```
|
||||||
|
|
||||||
|
The only compulsory setting is `system_mxid_localpart`, which defines the user
|
||||||
|
id of the Server Notices user, as above. `room_name` defines the name of the
|
||||||
|
room which will be created.
|
||||||
|
|
||||||
|
`system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the
|
||||||
|
displayname and avatar of the Server Notices user.
|
||||||
|
|
||||||
|
Sending notices
|
||||||
|
---------------
|
||||||
|
|
||||||
|
As of the current version of synapse, there is no convenient interface for
|
||||||
|
sending notices (other than the automated ones sent as part of consent
|
||||||
|
tracking).
|
||||||
|
|
||||||
|
In the meantime, it is possible to test this feature using the manhole. Having
|
||||||
|
gone into the manhole as described in [manhole.md](manhole.md), a notice can be
|
||||||
|
sent with something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> hs.get_server_notices_manager().send_notice('@user:server.com', {'msgtype':'m.text', 'body':'foo'})
|
||||||
|
```
|
|
@ -1,5 +1,7 @@
|
||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
cd "`dirname $0`/.."
|
cd "`dirname $0`/.."
|
||||||
|
|
||||||
TOX_DIR=$WORKSPACE/.tox
|
TOX_DIR=$WORKSPACE/.tox
|
||||||
|
@ -14,7 +16,20 @@ fi
|
||||||
tox -e py27 --notest -v
|
tox -e py27 --notest -v
|
||||||
|
|
||||||
TOX_BIN=$TOX_DIR/py27/bin
|
TOX_BIN=$TOX_DIR/py27/bin
|
||||||
$TOX_BIN/pip install setuptools
|
|
||||||
|
# cryptography 2.2 requires setuptools >= 18.5.
|
||||||
|
#
|
||||||
|
# older versions of virtualenv (?) give us a virtualenv with the same version
|
||||||
|
# of setuptools as is installed on the system python (and tox runs virtualenv
|
||||||
|
# under python3, so we get the version of setuptools that is installed on that).
|
||||||
|
#
|
||||||
|
# anyway, make sure that we have a recent enough setuptools.
|
||||||
|
$TOX_BIN/pip install 'setuptools>=18.5'
|
||||||
|
|
||||||
|
# we also need a semi-recent version of pip, because old ones fail to install
|
||||||
|
# the "enum34" dependency of cryptography.
|
||||||
|
$TOX_BIN/pip install 'pip>=10'
|
||||||
|
|
||||||
{ python synapse/python_dependencies.py
|
{ python synapse/python_dependencies.py
|
||||||
echo lxml psycopg2
|
echo lxml psycopg2
|
||||||
} | xargs $TOX_BIN/pip install
|
} | xargs $TOX_BIN/pip install
|
||||||
|
|
|
@ -6,9 +6,19 @@
|
||||||
|
|
||||||
## Do not run it lightly.
|
## Do not run it lightly.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$1" == "-h" ] || [ "$1" == "" ]; then
|
||||||
|
echo "Call with ROOM_ID as first option and then pipe it into the database. So for instance you might run"
|
||||||
|
echo " nuke-room-from-db.sh <room_id> | sqlite3 homeserver.db"
|
||||||
|
echo "or"
|
||||||
|
echo " nuke-room-from-db.sh <room_id> | psql --dbname=synapse"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
ROOMID="$1"
|
ROOMID="$1"
|
||||||
|
|
||||||
sqlite3 homeserver.db <<EOF
|
cat <<EOF
|
||||||
DELETE FROM event_forward_extremities WHERE room_id = '$ROOMID';
|
DELETE FROM event_forward_extremities WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM event_backward_extremities WHERE room_id = '$ROOMID';
|
DELETE FROM event_backward_extremities WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM event_edges WHERE room_id = '$ROOMID';
|
DELETE FROM event_edges WHERE room_id = '$ROOMID';
|
||||||
|
@ -29,7 +39,7 @@ DELETE FROM state_groups WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM state_groups_state WHERE room_id = '$ROOMID';
|
DELETE FROM state_groups_state WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM receipts_graph WHERE room_id = '$ROOMID';
|
DELETE FROM receipts_graph WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM receipts_linearized WHERE room_id = '$ROOMID';
|
DELETE FROM receipts_linearized WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM event_search_content WHERE c1room_id = '$ROOMID';
|
DELETE FROM event_search WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM guest_access WHERE room_id = '$ROOMID';
|
DELETE FROM guest_access WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM history_visibility WHERE room_id = '$ROOMID';
|
DELETE FROM history_visibility WHERE room_id = '$ROOMID';
|
||||||
DELETE FROM room_tags WHERE room_id = '$ROOMID';
|
DELETE FROM room_tags WHERE room_id = '$ROOMID';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -16,4 +17,4 @@
|
||||||
""" This is a reference implementation of a Matrix home server.
|
""" This is a reference implementation of a Matrix home server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.28.1"
|
__version__ = "0.31.2"
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
import pymacaroons
|
import pymacaroons
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
@ -57,7 +59,7 @@ class Auth(object):
|
||||||
self.TOKEN_NOT_FOUND_HTTP_STATUS = 401
|
self.TOKEN_NOT_FOUND_HTTP_STATUS = 401
|
||||||
|
|
||||||
self.token_cache = LruCache(CACHE_SIZE_FACTOR * 10000)
|
self.token_cache = LruCache(CACHE_SIZE_FACTOR * 10000)
|
||||||
register_cache("token_cache", self.token_cache)
|
register_cache("cache", "token_cache", self.token_cache)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_from_context(self, event, context, do_sig_check=True):
|
def check_from_context(self, event, context, do_sig_check=True):
|
||||||
|
@ -66,7 +68,7 @@ class Auth(object):
|
||||||
)
|
)
|
||||||
auth_events = yield self.store.get_events(auth_events_ids)
|
auth_events = yield self.store.get_events(auth_events_ids)
|
||||||
auth_events = {
|
auth_events = {
|
||||||
(e.type, e.state_key): e for e in auth_events.values()
|
(e.type, e.state_key): e for e in itervalues(auth_events)
|
||||||
}
|
}
|
||||||
self.check(event, auth_events=auth_events, do_sig_check=do_sig_check)
|
self.check(event, auth_events=auth_events, do_sig_check=do_sig_check)
|
||||||
|
|
||||||
|
@ -653,7 +655,7 @@ class Auth(object):
|
||||||
auth_events[(EventTypes.PowerLevels, "")] = power_level_event
|
auth_events[(EventTypes.PowerLevels, "")] = power_level_event
|
||||||
|
|
||||||
send_level = event_auth.get_send_level(
|
send_level = event_auth.get_send_level(
|
||||||
EventTypes.Aliases, "", auth_events
|
EventTypes.Aliases, "", power_level_event,
|
||||||
)
|
)
|
||||||
user_level = event_auth.get_user_power_level(user_id, auth_events)
|
user_level = event_auth.get_user_power_level(user_id, auth_events)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import logging
|
||||||
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -51,6 +52,8 @@ class Codes(object):
|
||||||
THREEPID_DENIED = "M_THREEPID_DENIED"
|
THREEPID_DENIED = "M_THREEPID_DENIED"
|
||||||
INVALID_USERNAME = "M_INVALID_USERNAME"
|
INVALID_USERNAME = "M_INVALID_USERNAME"
|
||||||
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||||
|
CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||||
|
CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||||
|
|
||||||
|
|
||||||
class CodeMessageException(RuntimeError):
|
class CodeMessageException(RuntimeError):
|
||||||
|
@ -138,6 +141,32 @@ class SynapseError(CodeMessageException):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentNotGivenError(SynapseError):
|
||||||
|
"""The error returned to the client when the user has not consented to the
|
||||||
|
privacy policy.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg, consent_uri):
|
||||||
|
"""Constructs a ConsentNotGivenError
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): The human-readable error message
|
||||||
|
consent_url (str): The URL where the user can give their consent
|
||||||
|
"""
|
||||||
|
super(ConsentNotGivenError, self).__init__(
|
||||||
|
code=http_client.FORBIDDEN,
|
||||||
|
msg=msg,
|
||||||
|
errcode=Codes.CONSENT_NOT_GIVEN
|
||||||
|
)
|
||||||
|
self._consent_uri = consent_uri
|
||||||
|
|
||||||
|
def error_dict(self):
|
||||||
|
return cs_error(
|
||||||
|
self.msg,
|
||||||
|
self.errcode,
|
||||||
|
consent_uri=self._consent_uri
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RegistrationError(SynapseError):
|
class RegistrationError(SynapseError):
|
||||||
"""An error raised when a registration event fails."""
|
"""An error raised when a registration event fails."""
|
||||||
pass
|
pass
|
||||||
|
@ -292,7 +321,7 @@ def cs_error(msg, code=Codes.UNKNOWN, **kwargs):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (str): The error message.
|
msg (str): The error message.
|
||||||
code (int): The error code.
|
code (str): The error code.
|
||||||
kwargs : Additional keys to add to the response.
|
kwargs : Additional keys to add to the response.
|
||||||
Returns:
|
Returns:
|
||||||
A dict representing the error response JSON.
|
A dict representing the error response JSON.
|
||||||
|
|
|
@ -411,7 +411,7 @@ class Filter(object):
|
||||||
return room_ids
|
return room_ids
|
||||||
|
|
||||||
def filter(self, events):
|
def filter(self, events):
|
||||||
return filter(self.check, events)
|
return list(filter(self.check, events))
|
||||||
|
|
||||||
def limit(self):
|
def limit(self):
|
||||||
return self.filter_json.get("limit", 10)
|
return self.filter_json.get("limit", 10)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +15,12 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Contains the URL paths to prefix various aspects of the server with. """
|
"""Contains the URL paths to prefix various aspects of the server with. """
|
||||||
|
from hashlib import sha256
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
|
from synapse.config import ConfigError
|
||||||
|
|
||||||
CLIENT_PREFIX = "/_matrix/client/api/v1"
|
CLIENT_PREFIX = "/_matrix/client/api/v1"
|
||||||
CLIENT_V2_ALPHA_PREFIX = "/_matrix/client/v2_alpha"
|
CLIENT_V2_ALPHA_PREFIX = "/_matrix/client/v2_alpha"
|
||||||
|
@ -25,3 +32,46 @@ SERVER_KEY_PREFIX = "/_matrix/key/v1"
|
||||||
SERVER_KEY_V2_PREFIX = "/_matrix/key/v2"
|
SERVER_KEY_V2_PREFIX = "/_matrix/key/v2"
|
||||||
MEDIA_PREFIX = "/_matrix/media/r0"
|
MEDIA_PREFIX = "/_matrix/media/r0"
|
||||||
LEGACY_MEDIA_PREFIX = "/_matrix/media/v1"
|
LEGACY_MEDIA_PREFIX = "/_matrix/media/v1"
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentURIBuilder(object):
|
||||||
|
def __init__(self, hs_config):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
hs_config (synapse.config.homeserver.HomeServerConfig):
|
||||||
|
"""
|
||||||
|
if hs_config.form_secret is None:
|
||||||
|
raise ConfigError(
|
||||||
|
"form_secret not set in config",
|
||||||
|
)
|
||||||
|
if hs_config.public_baseurl is None:
|
||||||
|
raise ConfigError(
|
||||||
|
"public_baseurl not set in config",
|
||||||
|
)
|
||||||
|
|
||||||
|
self._hmac_secret = hs_config.form_secret.encode("utf-8")
|
||||||
|
self._public_baseurl = hs_config.public_baseurl
|
||||||
|
|
||||||
|
def build_user_consent_uri(self, user_id):
|
||||||
|
"""Build a URI which we can give to the user to do their privacy
|
||||||
|
policy consent
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (str): mxid or username of user
|
||||||
|
|
||||||
|
Returns
|
||||||
|
(str) the URI where the user can do consent
|
||||||
|
"""
|
||||||
|
mac = hmac.new(
|
||||||
|
key=self._hmac_secret,
|
||||||
|
msg=user_id,
|
||||||
|
digestmod=sha256,
|
||||||
|
).hexdigest()
|
||||||
|
consent_uri = "%s_matrix/consent?%s" % (
|
||||||
|
self._public_baseurl,
|
||||||
|
urlencode({
|
||||||
|
"u": user_id,
|
||||||
|
"h": mac
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return consent_uri
|
||||||
|
|
|
@ -124,6 +124,19 @@ def quit_with_error(error_string):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def listen_metrics(bind_addresses, port):
|
||||||
|
"""
|
||||||
|
Start Prometheus metrics server.
|
||||||
|
"""
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
|
from prometheus_client import start_http_server
|
||||||
|
|
||||||
|
for host in bind_addresses:
|
||||||
|
reactor.callInThread(start_http_server, int(port),
|
||||||
|
addr=host, registry=RegistryProxy)
|
||||||
|
logger.info("Metrics now reporting on %s:%d", host, port)
|
||||||
|
|
||||||
|
|
||||||
def listen_tcp(bind_addresses, port, factory, backlog=50):
|
def listen_tcp(bind_addresses, port, factory, backlog=50):
|
||||||
"""
|
"""
|
||||||
Create a TCP socket for a port and several addresses
|
Create a TCP socket for a port and several addresses
|
||||||
|
|
|
@ -32,10 +32,10 @@ from synapse.replication.tcp.client import ReplicationClientHandler
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor, defer
|
||||||
from twisted.web.resource import NoResource
|
from twisted.web.resource import NoResource
|
||||||
|
|
||||||
logger = logging.getLogger("synapse.app.appservice")
|
logger = logging.getLogger("synapse.app.appservice")
|
||||||
|
@ -74,6 +74,7 @@ class AppserviceServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,6 +94,13 @@ class AppserviceServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
@ -112,9 +120,14 @@ class ASReplicationHandler(ReplicationClientHandler):
|
||||||
|
|
||||||
if stream_name == "events":
|
if stream_name == "events":
|
||||||
max_stream_id = self.store.get_room_max_stream_ordering()
|
max_stream_id = self.store.get_room_max_stream_ordering()
|
||||||
preserve_fn(
|
run_in_background(self._notify_app_services, max_stream_id)
|
||||||
self.appservice_handler.notify_interested_services
|
|
||||||
)(max_stream_id)
|
@defer.inlineCallbacks
|
||||||
|
def _notify_app_services(self, room_stream_id):
|
||||||
|
try:
|
||||||
|
yield self.appservice_handler.notify_interested_services(room_stream_id)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifying application services of event")
|
||||||
|
|
||||||
|
|
||||||
def start(config_options):
|
def start(config_options):
|
||||||
|
|
|
@ -25,6 +25,7 @@ from synapse.config.logger import setup_logging
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.http.server import JsonResource
|
from synapse.http.server import JsonResource
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
||||||
|
@ -77,7 +78,7 @@ class ClientReaderServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "client":
|
elif name == "client":
|
||||||
resource = JsonResource(self, canonical_json=False)
|
resource = JsonResource(self, canonical_json=False)
|
||||||
PublicRoomListRestServlet(self).register(resource)
|
PublicRoomListRestServlet(self).register(resource)
|
||||||
|
@ -98,6 +99,7 @@ class ClientReaderServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,7 +119,13 @@ class ClientReaderServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ from synapse.config.logger import setup_logging
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.http.server import JsonResource
|
from synapse.http.server import JsonResource
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
||||||
|
@ -90,7 +91,7 @@ class EventCreatorServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "client":
|
elif name == "client":
|
||||||
resource = JsonResource(self, canonical_json=False)
|
resource = JsonResource(self, canonical_json=False)
|
||||||
RoomSendEventRestServlet(self).register(resource)
|
RoomSendEventRestServlet(self).register(resource)
|
||||||
|
@ -114,6 +115,7 @@ class EventCreatorServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -133,6 +135,13 @@ class EventCreatorServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from synapse.config.logger import setup_logging
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.federation.transport.server import TransportLayerServer
|
from synapse.federation.transport.server import TransportLayerServer
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.directory import DirectoryStore
|
from synapse.replication.slave.storage.directory import DirectoryStore
|
||||||
|
@ -71,7 +72,7 @@ class FederationReaderServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "federation":
|
elif name == "federation":
|
||||||
resources.update({
|
resources.update({
|
||||||
FEDERATION_PREFIX: TransportLayerServer(self),
|
FEDERATION_PREFIX: TransportLayerServer(self),
|
||||||
|
@ -87,6 +88,7 @@ class FederationReaderServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -106,6 +108,13 @@ class FederationReaderServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ from synapse.config.logger import setup_logging
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.federation import send_queue
|
from synapse.federation import send_queue
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage.deviceinbox import SlavedDeviceInboxStore
|
from synapse.replication.slave.storage.deviceinbox import SlavedDeviceInboxStore
|
||||||
from synapse.replication.slave.storage.devices import SlavedDeviceStore
|
from synapse.replication.slave.storage.devices import SlavedDeviceStore
|
||||||
|
@ -38,7 +39,7 @@ from synapse.server import HomeServer
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.async import Linearizer
|
from synapse.util.async import Linearizer
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
|
@ -89,7 +90,7 @@ class FederationSenderServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
|
|
||||||
root_resource = create_resource_tree(resources, NoResource())
|
root_resource = create_resource_tree(resources, NoResource())
|
||||||
|
|
||||||
|
@ -101,6 +102,7 @@ class FederationSenderServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -120,6 +122,13 @@ class FederationSenderServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
@ -229,7 +238,7 @@ class FederationSenderHandler(object):
|
||||||
# presence, typing, etc.
|
# presence, typing, etc.
|
||||||
if stream_name == "federation":
|
if stream_name == "federation":
|
||||||
send_queue.process_rows_for_federation(self.federation_sender, rows)
|
send_queue.process_rows_for_federation(self.federation_sender, rows)
|
||||||
preserve_fn(self.update_token)(token)
|
run_in_background(self.update_token, token)
|
||||||
|
|
||||||
# We also need to poke the federation sender when new events happen
|
# We also need to poke the federation sender when new events happen
|
||||||
elif stream_name == "events":
|
elif stream_name == "events":
|
||||||
|
@ -237,6 +246,7 @@ class FederationSenderHandler(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def update_token(self, token):
|
def update_token(self, token):
|
||||||
|
try:
|
||||||
self.federation_position = token
|
self.federation_position = token
|
||||||
|
|
||||||
# We linearize here to ensure we don't have races updating the token
|
# We linearize here to ensure we don't have races updating the token
|
||||||
|
@ -250,6 +260,8 @@ class FederationSenderHandler(object):
|
||||||
# its in memory queues
|
# its in memory queues
|
||||||
self.replication_client.send_federation_ack(self.federation_position)
|
self.replication_client.send_federation_ack(self.federation_position)
|
||||||
self._last_ack = self.federation_position
|
self._last_ack = self.federation_position
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error updating federation stream position")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -29,6 +29,7 @@ from synapse.http.servlet import (
|
||||||
RestServlet, parse_json_object_from_request,
|
RestServlet, parse_json_object_from_request,
|
||||||
)
|
)
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
||||||
|
@ -131,7 +132,7 @@ class FrontendProxyServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "client":
|
elif name == "client":
|
||||||
resource = JsonResource(self, canonical_json=False)
|
resource = JsonResource(self, canonical_json=False)
|
||||||
KeyUploadServlet(self).register(resource)
|
KeyUploadServlet(self).register(resource)
|
||||||
|
@ -152,6 +153,7 @@ class FrontendProxyServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -171,6 +173,13 @@ class FrontendProxyServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ from synapse.module_api import ModuleApi
|
||||||
from synapse.http.additional_resource import AdditionalResource
|
from synapse.http.additional_resource import AdditionalResource
|
||||||
from synapse.http.server import RootRedirect
|
from synapse.http.server import RootRedirect
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
from synapse.metrics import register_memory_metrics
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.python_dependencies import CONDITIONAL_REQUIREMENTS, \
|
from synapse.python_dependencies import CONDITIONAL_REQUIREMENTS, \
|
||||||
check_requirements
|
check_requirements
|
||||||
|
@ -140,6 +140,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
),
|
),
|
||||||
self.tls_server_context_factory,
|
self.tls_server_context_factory,
|
||||||
)
|
)
|
||||||
|
@ -153,6 +154,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.info("Synapse now listening on port %d", port)
|
logger.info("Synapse now listening on port %d", port)
|
||||||
|
@ -182,6 +184,15 @@ class SynapseHomeServer(HomeServer):
|
||||||
"/_matrix/client/versions": client_resource,
|
"/_matrix/client/versions": client_resource,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if name == "consent":
|
||||||
|
from synapse.rest.consent.consent_resource import ConsentResource
|
||||||
|
consent_resource = ConsentResource(self)
|
||||||
|
if compress:
|
||||||
|
consent_resource = gz_wrap(consent_resource)
|
||||||
|
resources.update({
|
||||||
|
"/_matrix/consent": consent_resource,
|
||||||
|
})
|
||||||
|
|
||||||
if name == "federation":
|
if name == "federation":
|
||||||
resources.update({
|
resources.update({
|
||||||
FEDERATION_PREFIX: TransportLayerServer(self),
|
FEDERATION_PREFIX: TransportLayerServer(self),
|
||||||
|
@ -219,7 +230,7 @@ class SynapseHomeServer(HomeServer):
|
||||||
resources[WEB_CLIENT_PREFIX] = build_resource_for_web_client(self)
|
resources[WEB_CLIENT_PREFIX] = build_resource_for_web_client(self)
|
||||||
|
|
||||||
if name == "metrics" and self.get_config().enable_metrics:
|
if name == "metrics" and self.get_config().enable_metrics:
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
|
|
||||||
if name == "replication":
|
if name == "replication":
|
||||||
resources[REPLICATION_PREFIX] = ReplicationRestResource(self)
|
resources[REPLICATION_PREFIX] = ReplicationRestResource(self)
|
||||||
|
@ -252,6 +263,13 @@ class SynapseHomeServer(HomeServer):
|
||||||
reactor.addSystemEventTrigger(
|
reactor.addSystemEventTrigger(
|
||||||
"before", "shutdown", server_listener.stopListening,
|
"before", "shutdown", server_listener.stopListening,
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
@ -351,8 +369,6 @@ def setup(config_options):
|
||||||
hs.get_datastore().start_doing_background_updates()
|
hs.get_datastore().start_doing_background_updates()
|
||||||
hs.get_federation_client().start_get_pdu_cache()
|
hs.get_federation_client().start_get_pdu_cache()
|
||||||
|
|
||||||
register_memory_metrics(hs)
|
|
||||||
|
|
||||||
reactor.callWhenRunning(start)
|
reactor.callWhenRunning(start)
|
||||||
|
|
||||||
return hs
|
return hs
|
||||||
|
@ -423,6 +439,10 @@ def run(hs):
|
||||||
total_nonbridged_users = yield hs.get_datastore().count_nonbridged_users()
|
total_nonbridged_users = yield hs.get_datastore().count_nonbridged_users()
|
||||||
stats["total_nonbridged_users"] = total_nonbridged_users
|
stats["total_nonbridged_users"] = total_nonbridged_users
|
||||||
|
|
||||||
|
daily_user_type_results = yield hs.get_datastore().count_daily_user_type()
|
||||||
|
for name, count in daily_user_type_results.iteritems():
|
||||||
|
stats["daily_user_type_" + name] = count
|
||||||
|
|
||||||
room_count = yield hs.get_datastore().get_room_count()
|
room_count = yield hs.get_datastore().get_room_count()
|
||||||
stats["total_room_count"] = room_count
|
stats["total_room_count"] = room_count
|
||||||
|
|
||||||
|
@ -473,6 +493,14 @@ def run(hs):
|
||||||
" changes across releases."
|
" changes across releases."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def generate_user_daily_visit_stats():
|
||||||
|
hs.get_datastore().generate_user_daily_visits()
|
||||||
|
|
||||||
|
# Rather than update on per session basis, batch up the requests.
|
||||||
|
# If you increase the loop period, the accuracy of user_daily_visits
|
||||||
|
# table will decrease
|
||||||
|
clock.looping_call(generate_user_daily_visit_stats, 5 * 60 * 1000)
|
||||||
|
|
||||||
if hs.config.report_stats:
|
if hs.config.report_stats:
|
||||||
logger.info("Scheduling stats reporting for 3 hour intervals")
|
logger.info("Scheduling stats reporting for 3 hour intervals")
|
||||||
clock.looping_call(phone_stats_home, 3 * 60 * 60 * 1000)
|
clock.looping_call(phone_stats_home, 3 * 60 * 60 * 1000)
|
||||||
|
|
|
@ -27,6 +27,7 @@ from synapse.config.homeserver import HomeServerConfig
|
||||||
from synapse.config.logger import setup_logging
|
from synapse.config.logger import setup_logging
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
||||||
|
@ -73,7 +74,7 @@ class MediaRepositoryServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "media":
|
elif name == "media":
|
||||||
media_repo = self.get_media_repository_resource()
|
media_repo = self.get_media_repository_resource()
|
||||||
resources.update({
|
resources.update({
|
||||||
|
@ -94,6 +95,7 @@ class MediaRepositoryServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,6 +115,13 @@ class MediaRepositoryServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ from synapse.config._base import ConfigError
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
from synapse.config.homeserver import HomeServerConfig
|
||||||
from synapse.config.logger import setup_logging
|
from synapse.config.logger import setup_logging
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
||||||
from synapse.replication.slave.storage.events import SlavedEventStore
|
from synapse.replication.slave.storage.events import SlavedEventStore
|
||||||
|
@ -33,7 +34,7 @@ from synapse.server import HomeServer
|
||||||
from synapse.storage import DataStore
|
from synapse.storage import DataStore
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
|
@ -92,7 +93,7 @@ class PusherServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
|
|
||||||
root_resource = create_resource_tree(resources, NoResource())
|
root_resource = create_resource_tree(resources, NoResource())
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@ class PusherServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -123,6 +125,13 @@ class PusherServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
@ -140,10 +149,11 @@ class PusherReplicationHandler(ReplicationClientHandler):
|
||||||
|
|
||||||
def on_rdata(self, stream_name, token, rows):
|
def on_rdata(self, stream_name, token, rows):
|
||||||
super(PusherReplicationHandler, self).on_rdata(stream_name, token, rows)
|
super(PusherReplicationHandler, self).on_rdata(stream_name, token, rows)
|
||||||
preserve_fn(self.poke_pushers)(stream_name, token, rows)
|
run_in_background(self.poke_pushers, stream_name, token, rows)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def poke_pushers(self, stream_name, token, rows):
|
def poke_pushers(self, stream_name, token, rows):
|
||||||
|
try:
|
||||||
if stream_name == "pushers":
|
if stream_name == "pushers":
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row.deleted:
|
if row.deleted:
|
||||||
|
@ -158,6 +168,8 @@ class PusherReplicationHandler(ReplicationClientHandler):
|
||||||
yield self.pusher_pool.on_new_receipts(
|
yield self.pusher_pool.on_new_receipts(
|
||||||
token, token, set(row.room_id for row in rows)
|
token, token, set(row.room_id for row in rows)
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error poking pushers")
|
||||||
|
|
||||||
def stop_pusher(self, user_id, app_id, pushkey):
|
def stop_pusher(self, user_id, app_id, pushkey):
|
||||||
key = "%s:%s" % (app_id, pushkey)
|
key = "%s:%s" % (app_id, pushkey)
|
||||||
|
|
|
@ -26,6 +26,7 @@ from synapse.config.logger import setup_logging
|
||||||
from synapse.handlers.presence import PresenceHandler, get_interested_parties
|
from synapse.handlers.presence import PresenceHandler, get_interested_parties
|
||||||
from synapse.http.server import JsonResource
|
from synapse.http.server import JsonResource
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
||||||
|
@ -51,7 +52,7 @@ from synapse.storage.engines import create_engine
|
||||||
from synapse.storage.presence import UserPresenceState
|
from synapse.storage.presence import UserPresenceState
|
||||||
from synapse.storage.roommember import RoomMemberStore
|
from synapse.storage.roommember import RoomMemberStore
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
|
@ -257,7 +258,7 @@ class SynchrotronServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "client":
|
elif name == "client":
|
||||||
resource = JsonResource(self, canonical_json=False)
|
resource = JsonResource(self, canonical_json=False)
|
||||||
sync.register_servlets(self, resource)
|
sync.register_servlets(self, resource)
|
||||||
|
@ -281,6 +282,7 @@ class SynchrotronServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -300,6 +302,13 @@ class SynchrotronServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
@ -327,8 +336,7 @@ class SyncReplicationHandler(ReplicationClientHandler):
|
||||||
|
|
||||||
def on_rdata(self, stream_name, token, rows):
|
def on_rdata(self, stream_name, token, rows):
|
||||||
super(SyncReplicationHandler, self).on_rdata(stream_name, token, rows)
|
super(SyncReplicationHandler, self).on_rdata(stream_name, token, rows)
|
||||||
|
run_in_background(self.process_and_notify, stream_name, token, rows)
|
||||||
preserve_fn(self.process_and_notify)(stream_name, token, rows)
|
|
||||||
|
|
||||||
def get_streams_to_replicate(self):
|
def get_streams_to_replicate(self):
|
||||||
args = super(SyncReplicationHandler, self).get_streams_to_replicate()
|
args = super(SyncReplicationHandler, self).get_streams_to_replicate()
|
||||||
|
@ -340,6 +348,7 @@ class SyncReplicationHandler(ReplicationClientHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def process_and_notify(self, stream_name, token, rows):
|
def process_and_notify(self, stream_name, token, rows):
|
||||||
|
try:
|
||||||
if stream_name == "events":
|
if stream_name == "events":
|
||||||
# We shouldn't get multiple rows per token for events stream, so
|
# We shouldn't get multiple rows per token for events stream, so
|
||||||
# we don't need to optimise this for multiple rows.
|
# we don't need to optimise this for multiple rows.
|
||||||
|
@ -389,6 +398,8 @@ class SyncReplicationHandler(ReplicationClientHandler):
|
||||||
self.notifier.on_new_event(
|
self.notifier.on_new_event(
|
||||||
"groups_key", token, users=[row.user_id for row in rows],
|
"groups_key", token, users=[row.user_id for row in rows],
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error processing replication")
|
||||||
|
|
||||||
|
|
||||||
def start(config_options):
|
def start(config_options):
|
||||||
|
|
|
@ -26,6 +26,7 @@ from synapse.config.logger import setup_logging
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
from synapse.http.server import JsonResource
|
from synapse.http.server import JsonResource
|
||||||
from synapse.http.site import SynapseSite
|
from synapse.http.site import SynapseSite
|
||||||
|
from synapse.metrics import RegistryProxy
|
||||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||||
from synapse.replication.slave.storage._base import BaseSlavedStore
|
from synapse.replication.slave.storage._base import BaseSlavedStore
|
||||||
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
|
||||||
|
@ -39,10 +40,10 @@ from synapse.storage.engines import create_engine
|
||||||
from synapse.storage.user_directory import UserDirectoryStore
|
from synapse.storage.user_directory import UserDirectoryStore
|
||||||
from synapse.util.caches.stream_change_cache import StreamChangeCache
|
from synapse.util.caches.stream_change_cache import StreamChangeCache
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
from synapse.util.logcontext import LoggingContext, preserve_fn
|
from synapse.util.logcontext import LoggingContext, run_in_background
|
||||||
from synapse.util.manhole import manhole
|
from synapse.util.manhole import manhole
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor, defer
|
||||||
from twisted.web.resource import NoResource
|
from twisted.web.resource import NoResource
|
||||||
|
|
||||||
logger = logging.getLogger("synapse.app.user_dir")
|
logger = logging.getLogger("synapse.app.user_dir")
|
||||||
|
@ -105,7 +106,7 @@ class UserDirectoryServer(HomeServer):
|
||||||
for res in listener_config["resources"]:
|
for res in listener_config["resources"]:
|
||||||
for name in res["names"]:
|
for name in res["names"]:
|
||||||
if name == "metrics":
|
if name == "metrics":
|
||||||
resources[METRICS_PREFIX] = MetricsResource(self)
|
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||||
elif name == "client":
|
elif name == "client":
|
||||||
resource = JsonResource(self, canonical_json=False)
|
resource = JsonResource(self, canonical_json=False)
|
||||||
user_directory.register_servlets(self, resource)
|
user_directory.register_servlets(self, resource)
|
||||||
|
@ -126,6 +127,7 @@ class UserDirectoryServer(HomeServer):
|
||||||
site_tag,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
self.version_string,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -145,6 +147,13 @@ class UserDirectoryServer(HomeServer):
|
||||||
globals={"hs": self},
|
globals={"hs": self},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif listener["type"] == "metrics":
|
||||||
|
if not self.get_config().enable_metrics:
|
||||||
|
logger.warn(("Metrics listener configured, but "
|
||||||
|
"collect_metrics is not enabled!"))
|
||||||
|
else:
|
||||||
|
_base.listen_metrics(listener["bind_addresses"],
|
||||||
|
listener["port"])
|
||||||
else:
|
else:
|
||||||
logger.warn("Unrecognized listener type: %s", listener["type"])
|
logger.warn("Unrecognized listener type: %s", listener["type"])
|
||||||
|
|
||||||
|
@ -164,7 +173,14 @@ class UserDirectoryReplicationHandler(ReplicationClientHandler):
|
||||||
stream_name, token, rows
|
stream_name, token, rows
|
||||||
)
|
)
|
||||||
if stream_name == "current_state_deltas":
|
if stream_name == "current_state_deltas":
|
||||||
preserve_fn(self.user_directory.notify_new_event)()
|
run_in_background(self._notify_directory)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _notify_directory(self):
|
||||||
|
try:
|
||||||
|
yield self.user_directory.notify_new_event()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifiying user directory of state update")
|
||||||
|
|
||||||
|
|
||||||
def start(config_options):
|
def start(config_options):
|
||||||
|
|
|
@ -51,7 +51,7 @@ components.
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.appservice import ApplicationServiceState
|
from synapse.appservice import ApplicationServiceState
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -106,7 +106,7 @@ class _ServiceQueuer(object):
|
||||||
def enqueue(self, service, event):
|
def enqueue(self, service, event):
|
||||||
# if this service isn't being sent something
|
# if this service isn't being sent something
|
||||||
self.queued_events.setdefault(service.id, []).append(event)
|
self.queued_events.setdefault(service.id, []).append(event)
|
||||||
preserve_fn(self._send_request)(service)
|
run_in_background(self._send_request, service)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _send_request(self, service):
|
def _send_request(self, service):
|
||||||
|
@ -152,10 +152,10 @@ class _TransactionController(object):
|
||||||
if sent:
|
if sent:
|
||||||
yield txn.complete(self.store)
|
yield txn.complete(self.store)
|
||||||
else:
|
else:
|
||||||
preserve_fn(self._start_recoverer)(service)
|
run_in_background(self._start_recoverer, service)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.exception(e)
|
logger.exception("Error creating appservice transaction")
|
||||||
preserve_fn(self._start_recoverer)(service)
|
run_in_background(self._start_recoverer, service)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_recovered(self, recoverer):
|
def on_recovered(self, recoverer):
|
||||||
|
@ -176,6 +176,7 @@ class _TransactionController(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _start_recoverer(self, service):
|
def _start_recoverer(self, service):
|
||||||
|
try:
|
||||||
yield self.store.set_appservice_state(
|
yield self.store.set_appservice_state(
|
||||||
service,
|
service,
|
||||||
ApplicationServiceState.DOWN
|
ApplicationServiceState.DOWN
|
||||||
|
@ -187,6 +188,8 @@ class _TransactionController(object):
|
||||||
recoverer = self.recoverer_fn(service, self.on_recovered)
|
recoverer = self.recoverer_fn(service, self.on_recovered)
|
||||||
self.add_recoverers([recoverer])
|
self.add_recoverers([recoverer])
|
||||||
recoverer.recover()
|
recoverer.recover()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error starting AS recoverer")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _is_service_up(self, service):
|
def _is_service_up(self, service):
|
||||||
|
|
|
@ -12,3 +12,9 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import ConfigError
|
||||||
|
|
||||||
|
# export ConfigError if somebody does import *
|
||||||
|
# this is largely a fudge to stop PEP8 moaning about the import
|
||||||
|
__all__ = ["ConfigError"]
|
||||||
|
|
|
@ -281,15 +281,15 @@ class Config(object):
|
||||||
)
|
)
|
||||||
if not cls.path_exists(config_dir_path):
|
if not cls.path_exists(config_dir_path):
|
||||||
os.makedirs(config_dir_path)
|
os.makedirs(config_dir_path)
|
||||||
with open(config_path, "wb") as config_file:
|
with open(config_path, "w") as config_file:
|
||||||
config_bytes, config = obj.generate_config(
|
config_str, config = obj.generate_config(
|
||||||
config_dir_path=config_dir_path,
|
config_dir_path=config_dir_path,
|
||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
report_stats=(config_args.report_stats == "yes"),
|
report_stats=(config_args.report_stats == "yes"),
|
||||||
is_generating_file=True
|
is_generating_file=True
|
||||||
)
|
)
|
||||||
obj.invoke_all("generate_files", config)
|
obj.invoke_all("generate_files", config)
|
||||||
config_file.write(config_bytes)
|
config_file.write(config_str)
|
||||||
print((
|
print((
|
||||||
"A config file has been generated in %r for server name"
|
"A config file has been generated in %r for server name"
|
||||||
" %r with corresponding SSL keys and self-signed"
|
" %r with corresponding SSL keys and self-signed"
|
||||||
|
|
|
@ -17,11 +17,11 @@ from ._base import Config, ConfigError
|
||||||
from synapse.appservice import ApplicationService
|
from synapse.appservice import ApplicationService
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
|
|
||||||
import urllib
|
|
||||||
import yaml
|
import yaml
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
from six.moves.urllib import parse as urlparse
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ def _load_appservice(hostname, as_info, config_filename):
|
||||||
)
|
)
|
||||||
|
|
||||||
localpart = as_info["sender_localpart"]
|
localpart = as_info["sender_localpart"]
|
||||||
if urllib.quote(localpart) != localpart:
|
if urlparse.quote(localpart) != localpart:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"sender_localpart needs characters which are not URL encoded."
|
"sender_localpart needs characters which are not URL encoded."
|
||||||
)
|
)
|
||||||
|
|
85
synapse/config/consent_config.py
Normal file
85
synapse/config/consent_config.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import Config
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = """\
|
||||||
|
# User Consent configuration
|
||||||
|
#
|
||||||
|
# Parts of this section are required if enabling the 'consent' resource under
|
||||||
|
# 'listeners', in particular 'template_dir' and 'version'.
|
||||||
|
#
|
||||||
|
# 'template_dir' gives the location of the templates for the HTML forms.
|
||||||
|
# This directory should contain one subdirectory per language (eg, 'en', 'fr'),
|
||||||
|
# and each language directory should contain the policy document (named as
|
||||||
|
# '<version>.html') and a success page (success.html).
|
||||||
|
#
|
||||||
|
# 'version' specifies the 'current' version of the policy document. It defines
|
||||||
|
# the version to be served by the consent resource if there is no 'v'
|
||||||
|
# parameter.
|
||||||
|
#
|
||||||
|
# 'server_notice_content', if enabled, will send a user a "Server Notice"
|
||||||
|
# asking them to consent to the privacy policy. The 'server_notices' section
|
||||||
|
# must also be configured for this to work. Notices will *not* be sent to
|
||||||
|
# guest users unless 'send_server_notice_to_guests' is set to true.
|
||||||
|
#
|
||||||
|
# 'block_events_error', if set, will block any attempts to send events
|
||||||
|
# until the user consents to the privacy policy. The value of the setting is
|
||||||
|
# used as the text of the error.
|
||||||
|
#
|
||||||
|
# user_consent:
|
||||||
|
# template_dir: res/templates/privacy
|
||||||
|
# version: 1.0
|
||||||
|
# server_notice_content:
|
||||||
|
# msgtype: m.text
|
||||||
|
# body: >-
|
||||||
|
# To continue using this homeserver you must review and agree to the
|
||||||
|
# terms and conditions at %(consent_uri)s
|
||||||
|
# send_server_notice_to_guests: True
|
||||||
|
# block_events_error: >-
|
||||||
|
# To continue using this homeserver you must review and agree to the
|
||||||
|
# terms and conditions at %(consent_uri)s
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentConfig(Config):
|
||||||
|
def __init__(self):
|
||||||
|
super(ConsentConfig, self).__init__()
|
||||||
|
|
||||||
|
self.user_consent_version = None
|
||||||
|
self.user_consent_template_dir = None
|
||||||
|
self.user_consent_server_notice_content = None
|
||||||
|
self.user_consent_server_notice_to_guests = False
|
||||||
|
self.block_events_without_consent_error = None
|
||||||
|
|
||||||
|
def read_config(self, config):
|
||||||
|
consent_config = config.get("user_consent")
|
||||||
|
if consent_config is None:
|
||||||
|
return
|
||||||
|
self.user_consent_version = str(consent_config["version"])
|
||||||
|
self.user_consent_template_dir = consent_config["template_dir"]
|
||||||
|
self.user_consent_server_notice_content = consent_config.get(
|
||||||
|
"server_notice_content",
|
||||||
|
)
|
||||||
|
self.block_events_without_consent_error = consent_config.get(
|
||||||
|
"block_events_error",
|
||||||
|
)
|
||||||
|
self.user_consent_server_notice_to_guests = bool(consent_config.get(
|
||||||
|
"send_server_notice_to_guests", False,
|
||||||
|
))
|
||||||
|
|
||||||
|
def default_config(self, **kwargs):
|
||||||
|
return DEFAULT_CONFIG
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,7 +13,6 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from .tls import TlsConfig
|
from .tls import TlsConfig
|
||||||
from .server import ServerConfig
|
from .server import ServerConfig
|
||||||
from .logger import LoggingConfig
|
from .logger import LoggingConfig
|
||||||
|
@ -37,6 +37,8 @@ from .push import PushConfig
|
||||||
from .spam_checker import SpamCheckerConfig
|
from .spam_checker import SpamCheckerConfig
|
||||||
from .groups import GroupsConfig
|
from .groups import GroupsConfig
|
||||||
from .user_directory import UserDirectoryConfig
|
from .user_directory import UserDirectoryConfig
|
||||||
|
from .consent_config import ConsentConfig
|
||||||
|
from .server_notices_config import ServerNoticesConfig
|
||||||
|
|
||||||
|
|
||||||
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
||||||
|
@ -45,12 +47,15 @@ class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
|
||||||
AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
|
AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
|
||||||
JWTConfig, PasswordConfig, EmailConfig,
|
JWTConfig, PasswordConfig, EmailConfig,
|
||||||
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
|
WorkerConfig, PasswordAuthProviderConfig, PushConfig,
|
||||||
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,):
|
SpamCheckerConfig, GroupsConfig, UserDirectoryConfig,
|
||||||
|
ConsentConfig,
|
||||||
|
ServerNoticesConfig,
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
HomeServerConfig().generate_config(sys.argv[1], sys.argv[2])[0]
|
HomeServerConfig().generate_config(sys.argv[1], sys.argv[2], True)[0]
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,14 +59,20 @@ class KeyConfig(Config):
|
||||||
|
|
||||||
self.expire_access_token = config.get("expire_access_token", False)
|
self.expire_access_token = config.get("expire_access_token", False)
|
||||||
|
|
||||||
|
# a secret which is used to calculate HMACs for form values, to stop
|
||||||
|
# falsification of values
|
||||||
|
self.form_secret = config.get("form_secret", None)
|
||||||
|
|
||||||
def default_config(self, config_dir_path, server_name, is_generating_file=False,
|
def default_config(self, config_dir_path, server_name, is_generating_file=False,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
base_key_name = os.path.join(config_dir_path, server_name)
|
base_key_name = os.path.join(config_dir_path, server_name)
|
||||||
|
|
||||||
if is_generating_file:
|
if is_generating_file:
|
||||||
macaroon_secret_key = random_string_with_symbols(50)
|
macaroon_secret_key = random_string_with_symbols(50)
|
||||||
|
form_secret = '"%s"' % random_string_with_symbols(50)
|
||||||
else:
|
else:
|
||||||
macaroon_secret_key = None
|
macaroon_secret_key = None
|
||||||
|
form_secret = 'null'
|
||||||
|
|
||||||
return """\
|
return """\
|
||||||
macaroon_secret_key: "%(macaroon_secret_key)s"
|
macaroon_secret_key: "%(macaroon_secret_key)s"
|
||||||
|
@ -74,6 +80,10 @@ class KeyConfig(Config):
|
||||||
# Used to enable access token expiration.
|
# Used to enable access token expiration.
|
||||||
expire_access_token: False
|
expire_access_token: False
|
||||||
|
|
||||||
|
# a secret which is used to calculate HMACs for form values, to stop
|
||||||
|
# falsification of values
|
||||||
|
form_secret: %(form_secret)s
|
||||||
|
|
||||||
## Signing Keys ##
|
## Signing Keys ##
|
||||||
|
|
||||||
# Path to the signing key to sign messages with
|
# Path to the signing key to sign messages with
|
||||||
|
|
|
@ -117,7 +117,7 @@ class LoggingConfig(Config):
|
||||||
log_config = config.get("log_config")
|
log_config = config.get("log_config")
|
||||||
if log_config and not os.path.exists(log_config):
|
if log_config and not os.path.exists(log_config):
|
||||||
log_file = self.abspath("homeserver.log")
|
log_file = self.abspath("homeserver.log")
|
||||||
with open(log_config, "wb") as log_config_file:
|
with open(log_config, "w") as log_config_file:
|
||||||
log_config_file.write(
|
log_config_file.write(
|
||||||
DEFAULT_LOG_CONFIG.substitute(log_file=log_file)
|
DEFAULT_LOG_CONFIG.substitute(log_file=log_file)
|
||||||
)
|
)
|
||||||
|
|
|
@ -250,6 +250,9 @@ class ContentRepositoryConfig(Config):
|
||||||
# - '192.168.0.0/16'
|
# - '192.168.0.0/16'
|
||||||
# - '100.64.0.0/10'
|
# - '100.64.0.0/10'
|
||||||
# - '169.254.0.0/16'
|
# - '169.254.0.0/16'
|
||||||
|
# - '::1/128'
|
||||||
|
# - 'fe80::/64'
|
||||||
|
# - 'fc00::/7'
|
||||||
#
|
#
|
||||||
# List of IP address CIDR ranges that the URL preview spider is allowed
|
# List of IP address CIDR ranges that the URL preview spider is allowed
|
||||||
# to access even if they are specified in url_preview_ip_range_blacklist.
|
# to access even if they are specified in url_preview_ip_range_blacklist.
|
||||||
|
|
|
@ -14,8 +14,12 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from ._base import Config, ConfigError
|
from ._base import Config, ConfigError
|
||||||
|
|
||||||
|
logger = logging.Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ServerConfig(Config):
|
class ServerConfig(Config):
|
||||||
|
|
||||||
|
@ -138,6 +142,12 @@ class ServerConfig(Config):
|
||||||
|
|
||||||
metrics_port = config.get("metrics_port")
|
metrics_port = config.get("metrics_port")
|
||||||
if metrics_port:
|
if metrics_port:
|
||||||
|
logger.warn(
|
||||||
|
("The metrics_port configuration option is deprecated in Synapse 0.31 "
|
||||||
|
"in favour of a listener. Please see "
|
||||||
|
"http://github.com/matrix-org/synapse/blob/master/docs/metrics-howto.rst"
|
||||||
|
" on how to configure the new listener."))
|
||||||
|
|
||||||
self.listeners.append({
|
self.listeners.append({
|
||||||
"port": metrics_port,
|
"port": metrics_port,
|
||||||
"bind_addresses": [config.get("metrics_bind_host", "127.0.0.1")],
|
"bind_addresses": [config.get("metrics_bind_host", "127.0.0.1")],
|
||||||
|
|
86
synapse/config/server_notices_config.py
Normal file
86
synapse/config/server_notices_config.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
from ._base import Config
|
||||||
|
from synapse.types import UserID
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = """\
|
||||||
|
# Server Notices room configuration
|
||||||
|
#
|
||||||
|
# Uncomment this section to enable a room which can be used to send notices
|
||||||
|
# from the server to users. It is a special room which cannot be left; notices
|
||||||
|
# come from a special "notices" user id.
|
||||||
|
#
|
||||||
|
# If you uncomment this section, you *must* define the system_mxid_localpart
|
||||||
|
# setting, which defines the id of the user which will be used to send the
|
||||||
|
# notices.
|
||||||
|
#
|
||||||
|
# It's also possible to override the room name, the display name of the
|
||||||
|
# "notices" user, and the avatar for the user.
|
||||||
|
#
|
||||||
|
# server_notices:
|
||||||
|
# system_mxid_localpart: notices
|
||||||
|
# system_mxid_display_name: "Server Notices"
|
||||||
|
# system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
# room_name: "Server Notices"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ServerNoticesConfig(Config):
|
||||||
|
"""Configuration for the server notices room.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
server_notices_mxid (str|None):
|
||||||
|
The MXID to use for server notices.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_mxid_display_name (str|None):
|
||||||
|
The display name to use for the server notices user.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_mxid_avatar_url (str|None):
|
||||||
|
The display name to use for the server notices user.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_room_name (str|None):
|
||||||
|
The name to use for the server notices room.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super(ServerNoticesConfig, self).__init__()
|
||||||
|
self.server_notices_mxid = None
|
||||||
|
self.server_notices_mxid_display_name = None
|
||||||
|
self.server_notices_mxid_avatar_url = None
|
||||||
|
self.server_notices_room_name = None
|
||||||
|
|
||||||
|
def read_config(self, config):
|
||||||
|
c = config.get("server_notices")
|
||||||
|
if c is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
mxid_localpart = c['system_mxid_localpart']
|
||||||
|
self.server_notices_mxid = UserID(
|
||||||
|
mxid_localpart, self.server_name,
|
||||||
|
).to_string()
|
||||||
|
self.server_notices_mxid_display_name = c.get(
|
||||||
|
'system_mxid_display_name', None,
|
||||||
|
)
|
||||||
|
self.server_notices_mxid_avatar_url = c.get(
|
||||||
|
'system_mxid_avatar_url', None,
|
||||||
|
)
|
||||||
|
# todo: i18n
|
||||||
|
self.server_notices_room_name = c.get('room_name', "Server Notices")
|
||||||
|
|
||||||
|
def default_config(self, **kwargs):
|
||||||
|
return DEFAULT_CONFIG
|
|
@ -133,7 +133,7 @@ class TlsConfig(Config):
|
||||||
tls_dh_params_path = config["tls_dh_params_path"]
|
tls_dh_params_path = config["tls_dh_params_path"]
|
||||||
|
|
||||||
if not self.path_exists(tls_private_key_path):
|
if not self.path_exists(tls_private_key_path):
|
||||||
with open(tls_private_key_path, "w") as private_key_file:
|
with open(tls_private_key_path, "wb") as private_key_file:
|
||||||
tls_private_key = crypto.PKey()
|
tls_private_key = crypto.PKey()
|
||||||
tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
|
tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
|
||||||
private_key_pem = crypto.dump_privatekey(
|
private_key_pem = crypto.dump_privatekey(
|
||||||
|
@ -148,7 +148,7 @@ class TlsConfig(Config):
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.path_exists(tls_certificate_path):
|
if not self.path_exists(tls_certificate_path):
|
||||||
with open(tls_certificate_path, "w") as certificate_file:
|
with open(tls_certificate_path, "wb") as certificate_file:
|
||||||
cert = crypto.X509()
|
cert = crypto.X509()
|
||||||
subject = cert.get_subject()
|
subject = cert.get_subject()
|
||||||
subject.CN = config["server_name"]
|
subject.CN = config["server_name"]
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import ssl
|
from twisted.internet import ssl
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL, crypto
|
||||||
from twisted.internet._sslverify import _OpenSSLECCurve, _defaultCurveName
|
from twisted.internet._sslverify import _defaultCurveName
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -32,8 +32,9 @@ class ServerContextFactory(ssl.ContextFactory):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def configure_context(context, config):
|
def configure_context(context, config):
|
||||||
try:
|
try:
|
||||||
_ecCurve = _OpenSSLECCurve(_defaultCurveName)
|
_ecCurve = crypto.get_elliptic_curve(_defaultCurveName)
|
||||||
_ecCurve.addECKeyToContext(context)
|
context.set_tmp_ecdh(_ecCurve)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to enable elliptic curve for TLS")
|
logger.exception("Failed to enable elliptic curve for TLS")
|
||||||
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
|
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
|
||||||
|
|
|
@ -19,7 +19,8 @@ from synapse.api.errors import SynapseError, Codes
|
||||||
from synapse.util import unwrapFirstError, logcontext
|
from synapse.util import unwrapFirstError, logcontext
|
||||||
from synapse.util.logcontext import (
|
from synapse.util.logcontext import (
|
||||||
PreserveLoggingContext,
|
PreserveLoggingContext,
|
||||||
preserve_fn
|
preserve_fn,
|
||||||
|
run_in_background,
|
||||||
)
|
)
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ class Keyring(object):
|
||||||
|
|
||||||
verify_requests.append(verify_request)
|
verify_requests.append(verify_request)
|
||||||
|
|
||||||
preserve_fn(self._start_key_lookups)(verify_requests)
|
run_in_background(self._start_key_lookups, verify_requests)
|
||||||
|
|
||||||
# Pass those keys to handle_key_deferred so that the json object
|
# Pass those keys to handle_key_deferred so that the json object
|
||||||
# signatures can be verified
|
# signatures can be verified
|
||||||
|
@ -146,6 +147,7 @@ class Keyring(object):
|
||||||
verify_requests (List[VerifyKeyRequest]):
|
verify_requests (List[VerifyKeyRequest]):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
# create a deferred for each server we're going to look up the keys
|
# create a deferred for each server we're going to look up the keys
|
||||||
# for; we'll resolve them once we have completed our lookups.
|
# for; we'll resolve them once we have completed our lookups.
|
||||||
# These will be passed into wait_for_previous_lookups to block
|
# These will be passed into wait_for_previous_lookups to block
|
||||||
|
@ -192,6 +194,8 @@ class Keyring(object):
|
||||||
verify_request.deferred.addBoth(
|
verify_request.deferred.addBoth(
|
||||||
remove_deferreds, verify_request,
|
remove_deferreds, verify_request,
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error starting key lookups")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def wait_for_previous_lookups(self, server_names, server_to_deferred):
|
def wait_for_previous_lookups(self, server_names, server_to_deferred):
|
||||||
|
@ -313,7 +317,7 @@ class Keyring(object):
|
||||||
if not verify_request.deferred.called:
|
if not verify_request.deferred.called:
|
||||||
verify_request.deferred.errback(err)
|
verify_request.deferred.errback(err)
|
||||||
|
|
||||||
preserve_fn(do_iterations)().addErrback(on_err)
|
run_in_background(do_iterations).addErrback(on_err)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_keys_from_store(self, server_name_and_key_ids):
|
def get_keys_from_store(self, server_name_and_key_ids):
|
||||||
|
@ -329,8 +333,9 @@ class Keyring(object):
|
||||||
"""
|
"""
|
||||||
res = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
res = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.get_server_verify_keys)(
|
run_in_background(
|
||||||
server_name, key_ids
|
self.store.get_server_verify_keys,
|
||||||
|
server_name, key_ids,
|
||||||
).addCallback(lambda ks, server: (server, ks), server_name)
|
).addCallback(lambda ks, server: (server, ks), server_name)
|
||||||
for server_name, key_ids in server_name_and_key_ids
|
for server_name, key_ids in server_name_and_key_ids
|
||||||
],
|
],
|
||||||
|
@ -358,7 +363,7 @@ class Keyring(object):
|
||||||
|
|
||||||
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(get_key)(p_name, p_keys)
|
run_in_background(get_key, p_name, p_keys)
|
||||||
for p_name, p_keys in self.perspective_servers.items()
|
for p_name, p_keys in self.perspective_servers.items()
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
|
@ -398,7 +403,7 @@ class Keyring(object):
|
||||||
|
|
||||||
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(get_key)(server_name, key_ids)
|
run_in_background(get_key, server_name, key_ids)
|
||||||
for server_name, key_ids in server_name_and_key_ids
|
for server_name, key_ids in server_name_and_key_ids
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
|
@ -481,7 +486,8 @@ class Keyring(object):
|
||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store_keys)(
|
run_in_background(
|
||||||
|
self.store_keys,
|
||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
from_server=perspective_name,
|
from_server=perspective_name,
|
||||||
verify_keys=response_keys,
|
verify_keys=response_keys,
|
||||||
|
@ -539,7 +545,8 @@ class Keyring(object):
|
||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store_keys)(
|
run_in_background(
|
||||||
|
self.store_keys,
|
||||||
server_name=key_server_name,
|
server_name=key_server_name,
|
||||||
from_server=server_name,
|
from_server=server_name,
|
||||||
verify_keys=verify_keys,
|
verify_keys=verify_keys,
|
||||||
|
@ -615,7 +622,8 @@ class Keyring(object):
|
||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.store_server_keys_json)(
|
run_in_background(
|
||||||
|
self.store.store_server_keys_json,
|
||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
key_id=key_id,
|
key_id=key_id,
|
||||||
from_server=server_name,
|
from_server=server_name,
|
||||||
|
@ -716,7 +724,8 @@ class Keyring(object):
|
||||||
# TODO(markjh): Store whether the keys have expired.
|
# TODO(markjh): Store whether the keys have expired.
|
||||||
return logcontext.make_deferred_yieldable(defer.gatherResults(
|
return logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.store_server_verify_key)(
|
run_in_background(
|
||||||
|
self.store.store_server_verify_key,
|
||||||
server_name, server_name, key.time_added, key
|
server_name, server_name, key.time_added, key
|
||||||
)
|
)
|
||||||
for key_id, key in verify_keys.items()
|
for key_id, key in verify_keys.items()
|
||||||
|
|
|
@ -34,9 +34,11 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
|
||||||
event: the event being checked.
|
event: the event being checked.
|
||||||
auth_events (dict: event-key -> event): the existing room state.
|
auth_events (dict: event-key -> event): the existing room state.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AuthError if the checks fail
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the auth checks pass.
|
if the auth checks pass.
|
||||||
"""
|
"""
|
||||||
if do_size_check:
|
if do_size_check:
|
||||||
_check_size_limits(event)
|
_check_size_limits(event)
|
||||||
|
@ -71,7 +73,7 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
|
||||||
# Oh, we don't know what the state of the room was, so we
|
# Oh, we don't know what the state of the room was, so we
|
||||||
# are trusting that this is allowed (at least for now)
|
# are trusting that this is allowed (at least for now)
|
||||||
logger.warn("Trusting event: %s", event.event_id)
|
logger.warn("Trusting event: %s", event.event_id)
|
||||||
return True
|
return
|
||||||
|
|
||||||
if event.type == EventTypes.Create:
|
if event.type == EventTypes.Create:
|
||||||
room_id_domain = get_domain_from_id(event.room_id)
|
room_id_domain = get_domain_from_id(event.room_id)
|
||||||
|
@ -81,7 +83,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
|
||||||
"Creation event's room_id domain does not match sender's"
|
"Creation event's room_id domain does not match sender's"
|
||||||
)
|
)
|
||||||
# FIXME
|
# FIXME
|
||||||
return True
|
logger.debug("Allowing! %s", event)
|
||||||
|
return
|
||||||
|
|
||||||
creation_event = auth_events.get((EventTypes.Create, ""), None)
|
creation_event = auth_events.get((EventTypes.Create, ""), None)
|
||||||
|
|
||||||
|
@ -118,7 +121,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
|
||||||
403,
|
403,
|
||||||
"Alias event's state_key does not match sender's domain"
|
"Alias event's state_key does not match sender's domain"
|
||||||
)
|
)
|
||||||
return True
|
logger.debug("Allowing! %s", event)
|
||||||
|
return
|
||||||
|
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -127,14 +131,9 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
|
||||||
)
|
)
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
allowed = _is_membership_change_allowed(
|
_is_membership_change_allowed(event, auth_events)
|
||||||
event, auth_events
|
|
||||||
)
|
|
||||||
if allowed:
|
|
||||||
logger.debug("Allowing! %s", event)
|
logger.debug("Allowing! %s", event)
|
||||||
else:
|
return
|
||||||
logger.debug("Denying! %s", event)
|
|
||||||
return allowed
|
|
||||||
|
|
||||||
_check_event_sender_in_room(event, auth_events)
|
_check_event_sender_in_room(event, auth_events)
|
||||||
|
|
||||||
|
@ -153,7 +152,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return True
|
logger.debug("Allowing! %s", event)
|
||||||
|
return
|
||||||
|
|
||||||
_can_send_event(event, auth_events)
|
_can_send_event(event, auth_events)
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ def _is_membership_change_allowed(event, auth_events):
|
||||||
create = auth_events.get(key)
|
create = auth_events.get(key)
|
||||||
if create and event.prev_events[0][0] == create.event_id:
|
if create and event.prev_events[0][0] == create.event_id:
|
||||||
if create.content["creator"] == event.state_key:
|
if create.content["creator"] == event.state_key:
|
||||||
return True
|
return
|
||||||
|
|
||||||
target_user_id = event.state_key
|
target_user_id = event.state_key
|
||||||
|
|
||||||
|
@ -265,13 +265,13 @@ def _is_membership_change_allowed(event, auth_events):
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
403, "%s is banned from the room" % (target_user_id,)
|
403, "%s is banned from the room" % (target_user_id,)
|
||||||
)
|
)
|
||||||
return True
|
return
|
||||||
|
|
||||||
if Membership.JOIN != membership:
|
if Membership.JOIN != membership:
|
||||||
if (caller_invited
|
if (caller_invited
|
||||||
and Membership.LEAVE == membership
|
and Membership.LEAVE == membership
|
||||||
and target_user_id == event.user_id):
|
and target_user_id == event.user_id):
|
||||||
return True
|
return
|
||||||
|
|
||||||
if not caller_in_room: # caller isn't joined
|
if not caller_in_room: # caller isn't joined
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
|
@ -334,8 +334,6 @@ def _is_membership_change_allowed(event, auth_events):
|
||||||
else:
|
else:
|
||||||
raise AuthError(500, "Unknown membership %s" % membership)
|
raise AuthError(500, "Unknown membership %s" % membership)
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _check_event_sender_in_room(event, auth_events):
|
def _check_event_sender_in_room(event, auth_events):
|
||||||
key = (EventTypes.Member, event.user_id, )
|
key = (EventTypes.Member, event.user_id, )
|
||||||
|
@ -355,35 +353,46 @@ def _check_joined_room(member, user_id, room_id):
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def get_send_level(etype, state_key, auth_events):
|
def get_send_level(etype, state_key, power_levels_event):
|
||||||
key = (EventTypes.PowerLevels, "", )
|
"""Get the power level required to send an event of a given type
|
||||||
send_level_event = auth_events.get(key)
|
|
||||||
send_level = None
|
The federation spec [1] refers to this as "Required Power Level".
|
||||||
if send_level_event:
|
|
||||||
send_level = send_level_event.content.get("events", {}).get(
|
https://matrix.org/docs/spec/server_server/unstable.html#definitions
|
||||||
etype
|
|
||||||
)
|
Args:
|
||||||
|
etype (str): type of event
|
||||||
|
state_key (str|None): state_key of state event, or None if it is not
|
||||||
|
a state event.
|
||||||
|
power_levels_event (synapse.events.EventBase|None): power levels event
|
||||||
|
in force at this point in the room
|
||||||
|
Returns:
|
||||||
|
int: power level required to send this event.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if power_levels_event:
|
||||||
|
power_levels_content = power_levels_event.content
|
||||||
|
else:
|
||||||
|
power_levels_content = {}
|
||||||
|
|
||||||
|
# see if we have a custom level for this event type
|
||||||
|
send_level = power_levels_content.get("events", {}).get(etype)
|
||||||
|
|
||||||
|
# otherwise, fall back to the state_default/events_default.
|
||||||
if send_level is None:
|
if send_level is None:
|
||||||
if state_key is not None:
|
if state_key is not None:
|
||||||
send_level = send_level_event.content.get(
|
send_level = power_levels_content.get("state_default", 50)
|
||||||
"state_default", 50
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
send_level = send_level_event.content.get(
|
send_level = power_levels_content.get("events_default", 0)
|
||||||
"events_default", 0
|
|
||||||
)
|
|
||||||
|
|
||||||
if send_level:
|
return int(send_level)
|
||||||
send_level = int(send_level)
|
|
||||||
else:
|
|
||||||
send_level = 0
|
|
||||||
|
|
||||||
return send_level
|
|
||||||
|
|
||||||
|
|
||||||
def _can_send_event(event, auth_events):
|
def _can_send_event(event, auth_events):
|
||||||
|
power_levels_event = _get_power_level_event(auth_events)
|
||||||
|
|
||||||
send_level = get_send_level(
|
send_level = get_send_level(
|
||||||
event.type, event.get("state_key", None), auth_events
|
event.type, event.get("state_key"), power_levels_event,
|
||||||
)
|
)
|
||||||
user_level = get_user_power_level(event.user_id, auth_events)
|
user_level = get_user_power_level(event.user_id, auth_events)
|
||||||
|
|
||||||
|
@ -471,14 +480,14 @@ def _check_power_levels(event, auth_events):
|
||||||
]
|
]
|
||||||
|
|
||||||
old_list = current_state.content.get("users", {})
|
old_list = current_state.content.get("users", {})
|
||||||
for user in set(old_list.keys() + user_list.keys()):
|
for user in set(list(old_list) + list(user_list)):
|
||||||
levels_to_check.append(
|
levels_to_check.append(
|
||||||
(user, "users")
|
(user, "users")
|
||||||
)
|
)
|
||||||
|
|
||||||
old_list = current_state.content.get("events", {})
|
old_list = current_state.content.get("events", {})
|
||||||
new_list = event.content.get("events", {})
|
new_list = event.content.get("events", {})
|
||||||
for ev_id in set(old_list.keys() + new_list.keys()):
|
for ev_id in set(list(old_list) + list(new_list)):
|
||||||
levels_to_check.append(
|
levels_to_check.append(
|
||||||
(ev_id, "events")
|
(ev_id, "events")
|
||||||
)
|
)
|
||||||
|
@ -524,13 +533,22 @@ def _check_power_levels(event, auth_events):
|
||||||
|
|
||||||
|
|
||||||
def _get_power_level_event(auth_events):
|
def _get_power_level_event(auth_events):
|
||||||
key = (EventTypes.PowerLevels, "", )
|
return auth_events.get((EventTypes.PowerLevels, ""))
|
||||||
return auth_events.get(key)
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_power_level(user_id, auth_events):
|
def get_user_power_level(user_id, auth_events):
|
||||||
power_level_event = _get_power_level_event(auth_events)
|
"""Get a user's power level
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (str): user's id to look up in power_levels
|
||||||
|
auth_events (dict[(str, str), synapse.events.EventBase]):
|
||||||
|
state in force at this point in the room (or rather, a subset of
|
||||||
|
it including at least the create event and power levels event.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: the user's power level in this room.
|
||||||
|
"""
|
||||||
|
power_level_event = _get_power_level_event(auth_events)
|
||||||
if power_level_event:
|
if power_level_event:
|
||||||
level = power_level_event.content.get("users", {}).get(user_id)
|
level = power_level_event.content.get("users", {}).get(user_id)
|
||||||
if not level:
|
if not level:
|
||||||
|
@ -541,6 +559,11 @@ def get_user_power_level(user_id, auth_events):
|
||||||
else:
|
else:
|
||||||
return int(level)
|
return int(level)
|
||||||
else:
|
else:
|
||||||
|
# if there is no power levels event, the creator gets 100 and everyone
|
||||||
|
# else gets 0.
|
||||||
|
|
||||||
|
# some things which call this don't pass the create event: hack around
|
||||||
|
# that.
|
||||||
key = (EventTypes.Create, "", )
|
key = (EventTypes.Create, "", )
|
||||||
create_event = auth_events.get(key)
|
create_event = auth_events.get(key)
|
||||||
if (create_event is not None and
|
if (create_event is not None and
|
||||||
|
|
|
@ -47,14 +47,26 @@ class _EventInternalMetadata(object):
|
||||||
|
|
||||||
|
|
||||||
def _event_dict_property(key):
|
def _event_dict_property(key):
|
||||||
|
# We want to be able to use hasattr with the event dict properties.
|
||||||
|
# However, (on python3) hasattr expects AttributeError to be raised. Hence,
|
||||||
|
# we need to transform the KeyError into an AttributeError
|
||||||
def getter(self):
|
def getter(self):
|
||||||
|
try:
|
||||||
return self._event_dict[key]
|
return self._event_dict[key]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
def setter(self, v):
|
def setter(self, v):
|
||||||
|
try:
|
||||||
self._event_dict[key] = v
|
self._event_dict[key] = v
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
try:
|
||||||
del self._event_dict[key]
|
del self._event_dict[key]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
return property(
|
return property(
|
||||||
getter,
|
getter,
|
||||||
|
@ -134,7 +146,7 @@ class EventBase(object):
|
||||||
return field in self._event_dict
|
return field in self._event_dict
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return self._event_dict.items()
|
return list(self._event_dict.items())
|
||||||
|
|
||||||
|
|
||||||
class FrozenEvent(EventBase):
|
class FrozenEvent(EventBase):
|
||||||
|
|
|
@ -20,6 +20,8 @@ from frozendict import frozendict
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
# Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
|
# Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
|
||||||
# (?<!stuff) matches if the current position in the string is not preceded
|
# (?<!stuff) matches if the current position in the string is not preceded
|
||||||
# by a match for 'stuff'.
|
# by a match for 'stuff'.
|
||||||
|
@ -277,7 +279,7 @@ def serialize_event(e, time_now_ms, as_client_event=True,
|
||||||
|
|
||||||
if only_event_fields:
|
if only_event_fields:
|
||||||
if (not isinstance(only_event_fields, list) or
|
if (not isinstance(only_event_fields, list) or
|
||||||
not all(isinstance(f, basestring) for f in only_event_fields)):
|
not all(isinstance(f, string_types) for f in only_event_fields)):
|
||||||
raise TypeError("only_event_fields must be a list of strings")
|
raise TypeError("only_event_fields must be a list of strings")
|
||||||
d = only_fields(d, only_event_fields)
|
d = only_fields(d, only_event_fields)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ from synapse.types import EventID, RoomID, UserID
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
|
||||||
class EventValidator(object):
|
class EventValidator(object):
|
||||||
|
|
||||||
|
@ -49,7 +51,7 @@ class EventValidator(object):
|
||||||
strings.append("state_key")
|
strings.append("state_key")
|
||||||
|
|
||||||
for s in strings:
|
for s in strings:
|
||||||
if not isinstance(getattr(event, s), basestring):
|
if not isinstance(getattr(event, s), string_types):
|
||||||
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
||||||
|
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
|
@ -88,5 +90,5 @@ class EventValidator(object):
|
||||||
for s in keys:
|
for s in keys:
|
||||||
if s not in d:
|
if s not in d:
|
||||||
raise SynapseError(400, "'%s' not in content" % (s,))
|
raise SynapseError(400, "'%s' not in content" % (s,))
|
||||||
if not isinstance(d[s], basestring):
|
if not isinstance(d[s], string_types):
|
||||||
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
raise SynapseError(400, "Not '%s' a string type" % (s,))
|
||||||
|
|
|
@ -19,6 +19,8 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from six.moves import range
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.constants import Membership
|
from synapse.api.constants import Membership
|
||||||
|
@ -30,20 +32,17 @@ from synapse.federation.federation_base import (
|
||||||
FederationBase,
|
FederationBase,
|
||||||
event_from_pdu_json,
|
event_from_pdu_json,
|
||||||
)
|
)
|
||||||
import synapse.metrics
|
|
||||||
from synapse.util import logcontext, unwrapFirstError
|
from synapse.util import logcontext, unwrapFirstError
|
||||||
from synapse.util.caches.expiringcache import ExpiringCache
|
from synapse.util.caches.expiringcache import ExpiringCache
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.retryutils import NotRetryingDestination
|
from synapse.util.retryutils import NotRetryingDestination
|
||||||
|
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
sent_queries_counter = Counter("synapse_federation_client_sent_queries", "", ["type"])
|
||||||
# synapse.federation.federation_client is a silly name
|
|
||||||
metrics = synapse.metrics.get_metrics_for("synapse.federation.client")
|
|
||||||
|
|
||||||
sent_queries_counter = metrics.register_counter("sent_queries", labels=["type"])
|
|
||||||
|
|
||||||
|
|
||||||
PDU_RETRY_TIME_MS = 1 * 60 * 1000
|
PDU_RETRY_TIME_MS = 1 * 60 * 1000
|
||||||
|
@ -106,7 +105,7 @@ class FederationClient(FederationBase):
|
||||||
a Deferred which will eventually yield a JSON object from the
|
a Deferred which will eventually yield a JSON object from the
|
||||||
response
|
response
|
||||||
"""
|
"""
|
||||||
sent_queries_counter.inc(query_type)
|
sent_queries_counter.labels(query_type).inc()
|
||||||
|
|
||||||
return self.transport_layer.make_query(
|
return self.transport_layer.make_query(
|
||||||
destination, query_type, args, retry_on_dns_fail=retry_on_dns_fail,
|
destination, query_type, args, retry_on_dns_fail=retry_on_dns_fail,
|
||||||
|
@ -125,7 +124,7 @@ class FederationClient(FederationBase):
|
||||||
a Deferred which will eventually yield a JSON object from the
|
a Deferred which will eventually yield a JSON object from the
|
||||||
response
|
response
|
||||||
"""
|
"""
|
||||||
sent_queries_counter.inc("client_device_keys")
|
sent_queries_counter.labels("client_device_keys").inc()
|
||||||
return self.transport_layer.query_client_keys(
|
return self.transport_layer.query_client_keys(
|
||||||
destination, content, timeout
|
destination, content, timeout
|
||||||
)
|
)
|
||||||
|
@ -135,7 +134,7 @@ class FederationClient(FederationBase):
|
||||||
"""Query the device keys for a list of user ids hosted on a remote
|
"""Query the device keys for a list of user ids hosted on a remote
|
||||||
server.
|
server.
|
||||||
"""
|
"""
|
||||||
sent_queries_counter.inc("user_devices")
|
sent_queries_counter.labels("user_devices").inc()
|
||||||
return self.transport_layer.query_user_devices(
|
return self.transport_layer.query_user_devices(
|
||||||
destination, user_id, timeout
|
destination, user_id, timeout
|
||||||
)
|
)
|
||||||
|
@ -152,7 +151,7 @@ class FederationClient(FederationBase):
|
||||||
a Deferred which will eventually yield a JSON object from the
|
a Deferred which will eventually yield a JSON object from the
|
||||||
response
|
response
|
||||||
"""
|
"""
|
||||||
sent_queries_counter.inc("client_one_time_keys")
|
sent_queries_counter.labels("client_one_time_keys").inc()
|
||||||
return self.transport_layer.claim_client_keys(
|
return self.transport_layer.claim_client_keys(
|
||||||
destination, content, timeout
|
destination, content, timeout
|
||||||
)
|
)
|
||||||
|
@ -392,7 +391,7 @@ class FederationClient(FederationBase):
|
||||||
"""
|
"""
|
||||||
if return_local:
|
if return_local:
|
||||||
seen_events = yield self.store.get_events(event_ids, allow_rejected=True)
|
seen_events = yield self.store.get_events(event_ids, allow_rejected=True)
|
||||||
signed_events = seen_events.values()
|
signed_events = list(seen_events.values())
|
||||||
else:
|
else:
|
||||||
seen_events = yield self.store.have_seen_events(event_ids)
|
seen_events = yield self.store.have_seen_events(event_ids)
|
||||||
signed_events = []
|
signed_events = []
|
||||||
|
@ -413,11 +412,12 @@ class FederationClient(FederationBase):
|
||||||
|
|
||||||
batch_size = 20
|
batch_size = 20
|
||||||
missing_events = list(missing_events)
|
missing_events = list(missing_events)
|
||||||
for i in xrange(0, len(missing_events), batch_size):
|
for i in range(0, len(missing_events), batch_size):
|
||||||
batch = set(missing_events[i:i + batch_size])
|
batch = set(missing_events[i:i + batch_size])
|
||||||
|
|
||||||
deferreds = [
|
deferreds = [
|
||||||
preserve_fn(self.get_pdu)(
|
run_in_background(
|
||||||
|
self.get_pdu,
|
||||||
destinations=random_server_list(),
|
destinations=random_server_list(),
|
||||||
event_id=e_id,
|
event_id=e_id,
|
||||||
)
|
)
|
||||||
|
@ -589,7 +589,7 @@ class FederationClient(FederationBase):
|
||||||
}
|
}
|
||||||
|
|
||||||
valid_pdus = yield self._check_sigs_and_hash_and_fetch(
|
valid_pdus = yield self._check_sigs_and_hash_and_fetch(
|
||||||
destination, pdus.values(),
|
destination, list(pdus.values()),
|
||||||
outlier=True,
|
outlier=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,13 @@ from synapse.federation.federation_base import (
|
||||||
|
|
||||||
from synapse.federation.persistence import TransactionActions
|
from synapse.federation.persistence import TransactionActions
|
||||||
from synapse.federation.units import Edu, Transaction
|
from synapse.federation.units import Edu, Transaction
|
||||||
import synapse.metrics
|
|
||||||
from synapse.types import get_domain_from_id
|
from synapse.types import get_domain_from_id
|
||||||
from synapse.util import async
|
from synapse.util import async
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
|
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
# when processing incoming transactions, we try to handle multiple rooms in
|
# when processing incoming transactions, we try to handle multiple rooms in
|
||||||
|
@ -41,17 +42,17 @@ TRANSACTION_CONCURRENCY_LIMIT = 10
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# synapse.federation.federation_server is a silly name
|
received_pdus_counter = Counter("synapse_federation_server_received_pdus", "")
|
||||||
metrics = synapse.metrics.get_metrics_for("synapse.federation.server")
|
|
||||||
|
|
||||||
received_pdus_counter = metrics.register_counter("received_pdus")
|
received_edus_counter = Counter("synapse_federation_server_received_edus", "")
|
||||||
|
|
||||||
received_edus_counter = metrics.register_counter("received_edus")
|
received_queries_counter = Counter(
|
||||||
|
"synapse_federation_server_received_queries", "", ["type"]
|
||||||
received_queries_counter = metrics.register_counter("received_queries", labels=["type"])
|
)
|
||||||
|
|
||||||
|
|
||||||
class FederationServer(FederationBase):
|
class FederationServer(FederationBase):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(FederationServer, self).__init__(hs)
|
super(FederationServer, self).__init__(hs)
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ class FederationServer(FederationBase):
|
||||||
|
|
||||||
logger.debug("[%s] Transaction is new", transaction.transaction_id)
|
logger.debug("[%s] Transaction is new", transaction.transaction_id)
|
||||||
|
|
||||||
received_pdus_counter.inc_by(len(transaction.pdus))
|
received_pdus_counter.inc(len(transaction.pdus))
|
||||||
|
|
||||||
pdus_by_room = {}
|
pdus_by_room = {}
|
||||||
|
|
||||||
|
@ -292,7 +293,7 @@ class FederationServer(FederationBase):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_query_request(self, query_type, args):
|
def on_query_request(self, query_type, args):
|
||||||
received_queries_counter.inc(query_type)
|
received_queries_counter.labels(query_type).inc()
|
||||||
resp = yield self.registry.on_query(query_type, args)
|
resp = yield self.registry.on_query(query_type, args)
|
||||||
defer.returnValue((200, resp))
|
defer.returnValue((200, resp))
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ from .units import Edu
|
||||||
|
|
||||||
from synapse.storage.presence import UserPresenceState
|
from synapse.storage.presence import UserPresenceState
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
import synapse.metrics
|
from synapse.metrics import LaterGauge
|
||||||
|
|
||||||
from blist import sorteddict
|
from blist import sorteddict
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
@ -45,9 +45,6 @@ from six import itervalues, iteritems
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class FederationRemoteSendQueue(object):
|
class FederationRemoteSendQueue(object):
|
||||||
"""A drop in replacement for TransactionQueue"""
|
"""A drop in replacement for TransactionQueue"""
|
||||||
|
|
||||||
|
@ -77,10 +74,8 @@ class FederationRemoteSendQueue(object):
|
||||||
# lambda binds to the queue rather than to the name of the queue which
|
# lambda binds to the queue rather than to the name of the queue which
|
||||||
# changes. ARGH.
|
# changes. ARGH.
|
||||||
def register(name, queue):
|
def register(name, queue):
|
||||||
metrics.register_callback(
|
LaterGauge("synapse_federation_send_queue_%s_size" % (queue_name,),
|
||||||
queue_name + "_size",
|
"", [], lambda: len(queue))
|
||||||
lambda: len(queue),
|
|
||||||
)
|
|
||||||
|
|
||||||
for queue_name in [
|
for queue_name in [
|
||||||
"presence_map", "presence_changed", "keyed_edu", "keyed_edu_changed",
|
"presence_map", "presence_changed", "keyed_edu", "keyed_edu_changed",
|
||||||
|
@ -202,7 +197,7 @@ class FederationRemoteSendQueue(object):
|
||||||
|
|
||||||
# We only want to send presence for our own users, so lets always just
|
# We only want to send presence for our own users, so lets always just
|
||||||
# filter here just in case.
|
# filter here just in case.
|
||||||
local_states = filter(lambda s: self.is_mine_id(s.user_id), states)
|
local_states = list(filter(lambda s: self.is_mine_id(s.user_id), states))
|
||||||
|
|
||||||
self.presence_map.update({state.user_id: state for state in local_states})
|
self.presence_map.update({state.user_id: state for state in local_states})
|
||||||
self.presence_changed[pos] = [state.user_id for state in local_states]
|
self.presence_changed[pos] = [state.user_id for state in local_states]
|
||||||
|
|
|
@ -26,23 +26,25 @@ from synapse.util.retryutils import NotRetryingDestination, get_retry_limiter
|
||||||
from synapse.util.metrics import measure_func
|
from synapse.util.metrics import measure_func
|
||||||
from synapse.handlers.presence import format_user_presence_state, get_interested_remotes
|
from synapse.handlers.presence import format_user_presence_state, get_interested_remotes
|
||||||
import synapse.metrics
|
import synapse.metrics
|
||||||
|
from synapse.metrics import LaterGauge
|
||||||
|
from synapse.metrics import (
|
||||||
|
sent_edus_counter,
|
||||||
|
sent_transactions_counter,
|
||||||
|
events_processed_counter,
|
||||||
|
)
|
||||||
|
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
sent_pdus_destination_dist = Counter(
|
||||||
|
"synapse_federation_transaction_queue_sent_pdu_destinations", ""
|
||||||
client_metrics = synapse.metrics.get_metrics_for("synapse.federation.client")
|
|
||||||
sent_pdus_destination_dist = client_metrics.register_distribution(
|
|
||||||
"sent_pdu_destinations"
|
|
||||||
)
|
)
|
||||||
sent_edus_counter = client_metrics.register_counter("sent_edus")
|
|
||||||
|
|
||||||
sent_transactions_counter = client_metrics.register_counter("sent_transactions")
|
|
||||||
|
|
||||||
events_processed_counter = client_metrics.register_counter("events_processed")
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionQueue(object):
|
class TransactionQueue(object):
|
||||||
|
@ -69,8 +71,10 @@ class TransactionQueue(object):
|
||||||
# done
|
# done
|
||||||
self.pending_transactions = {}
|
self.pending_transactions = {}
|
||||||
|
|
||||||
metrics.register_callback(
|
LaterGauge(
|
||||||
"pending_destinations",
|
"synapse_federation_transaction_queue_pending_destinations",
|
||||||
|
"",
|
||||||
|
[],
|
||||||
lambda: len(self.pending_transactions),
|
lambda: len(self.pending_transactions),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,12 +98,16 @@ class TransactionQueue(object):
|
||||||
# Map of destination -> (edu_type, key) -> Edu
|
# Map of destination -> (edu_type, key) -> Edu
|
||||||
self.pending_edus_keyed_by_dest = edus_keyed = {}
|
self.pending_edus_keyed_by_dest = edus_keyed = {}
|
||||||
|
|
||||||
metrics.register_callback(
|
LaterGauge(
|
||||||
"pending_pdus",
|
"synapse_federation_transaction_queue_pending_pdus",
|
||||||
|
"",
|
||||||
|
[],
|
||||||
lambda: sum(map(len, pdus.values())),
|
lambda: sum(map(len, pdus.values())),
|
||||||
)
|
)
|
||||||
metrics.register_callback(
|
LaterGauge(
|
||||||
"pending_edus",
|
"synapse_federation_transaction_queue_pending_edus",
|
||||||
|
"",
|
||||||
|
[],
|
||||||
lambda: (
|
lambda: (
|
||||||
sum(map(len, edus.values()))
|
sum(map(len, edus.values()))
|
||||||
+ sum(map(len, presence.values()))
|
+ sum(map(len, presence.values()))
|
||||||
|
@ -228,7 +236,7 @@ class TransactionQueue(object):
|
||||||
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
logcontext.run_in_background(handle_room_events, evs)
|
logcontext.run_in_background(handle_room_events, evs)
|
||||||
for evs in events_by_room.itervalues()
|
for evs in itervalues(events_by_room)
|
||||||
],
|
],
|
||||||
consumeErrors=True
|
consumeErrors=True
|
||||||
))
|
))
|
||||||
|
@ -241,18 +249,15 @@ class TransactionQueue(object):
|
||||||
now = self.clock.time_msec()
|
now = self.clock.time_msec()
|
||||||
ts = yield self.store.get_received_ts(events[-1].event_id)
|
ts = yield self.store.get_received_ts(events[-1].event_id)
|
||||||
|
|
||||||
synapse.metrics.event_processing_lag.set(
|
synapse.metrics.event_processing_lag.labels(
|
||||||
now - ts, "federation_sender",
|
"federation_sender").set(now - ts)
|
||||||
)
|
synapse.metrics.event_processing_last_ts.labels(
|
||||||
synapse.metrics.event_processing_last_ts.set(
|
"federation_sender").set(ts)
|
||||||
ts, "federation_sender",
|
|
||||||
)
|
|
||||||
|
|
||||||
events_processed_counter.inc_by(len(events))
|
events_processed_counter.inc(len(events))
|
||||||
|
|
||||||
synapse.metrics.event_processing_positions.set(
|
synapse.metrics.event_processing_positions.labels(
|
||||||
next_token, "federation_sender",
|
"federation_sender").set(next_token)
|
||||||
)
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._is_processing = False
|
self._is_processing = False
|
||||||
|
@ -275,7 +280,7 @@ class TransactionQueue(object):
|
||||||
if not destinations:
|
if not destinations:
|
||||||
return
|
return
|
||||||
|
|
||||||
sent_pdus_destination_dist.inc_by(len(destinations))
|
sent_pdus_destination_dist.inc(len(destinations))
|
||||||
|
|
||||||
for destination in destinations:
|
for destination in destinations:
|
||||||
self.pending_pdus_by_dest.setdefault(destination, []).append(
|
self.pending_pdus_by_dest.setdefault(destination, []).append(
|
||||||
|
@ -322,7 +327,9 @@ class TransactionQueue(object):
|
||||||
if not states_map:
|
if not states_map:
|
||||||
break
|
break
|
||||||
|
|
||||||
yield self._process_presence_inner(states_map.values())
|
yield self._process_presence_inner(list(states_map.values()))
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error sending presence states to servers")
|
||||||
finally:
|
finally:
|
||||||
self._processing_pending_presence = False
|
self._processing_pending_presence = False
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ from synapse.http.servlet import (
|
||||||
)
|
)
|
||||||
from synapse.util.ratelimitutils import FederationRateLimiter
|
from synapse.util.ratelimitutils import FederationRateLimiter
|
||||||
from synapse.util.versionstring import get_version_string
|
from synapse.util.versionstring import get_version_string
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.types import ThirdPartyInstanceID, get_domain_from_id
|
from synapse.types import ThirdPartyInstanceID, get_domain_from_id
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
@ -152,11 +152,18 @@ class Authenticator(object):
|
||||||
# alive
|
# alive
|
||||||
retry_timings = yield self.store.get_destination_retry_timings(origin)
|
retry_timings = yield self.store.get_destination_retry_timings(origin)
|
||||||
if retry_timings and retry_timings["retry_last_ts"]:
|
if retry_timings and retry_timings["retry_last_ts"]:
|
||||||
logger.info("Marking origin %r as up", origin)
|
run_in_background(self._reset_retry_timings, origin)
|
||||||
preserve_fn(self.store.set_destination_retry_timings)(origin, 0, 0)
|
|
||||||
|
|
||||||
defer.returnValue(origin)
|
defer.returnValue(origin)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _reset_retry_timings(self, origin):
|
||||||
|
try:
|
||||||
|
logger.info("Marking origin %r as up", origin)
|
||||||
|
yield self.store.set_destination_retry_timings(origin, 0, 0)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error resetting retry timings on %s", origin)
|
||||||
|
|
||||||
|
|
||||||
class BaseFederationServlet(object):
|
class BaseFederationServlet(object):
|
||||||
REQUIRE_AUTH = True
|
REQUIRE_AUTH = True
|
||||||
|
|
|
@ -74,8 +74,6 @@ class Transaction(JsonEncodedObject):
|
||||||
"previous_ids",
|
"previous_ids",
|
||||||
"pdus",
|
"pdus",
|
||||||
"edus",
|
"edus",
|
||||||
"transaction_id",
|
|
||||||
"destination",
|
|
||||||
"pdu_failures",
|
"pdu_failures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.types import get_domain_from_id
|
from synapse.types import get_domain_from_id
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
|
|
||||||
from signedjson.sign import sign_json
|
from signedjson.sign import sign_json
|
||||||
|
|
||||||
|
@ -165,6 +165,7 @@ class GroupAttestionRenewer(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _renew_attestation(group_id, user_id):
|
def _renew_attestation(group_id, user_id):
|
||||||
|
try:
|
||||||
if not self.is_mine_id(group_id):
|
if not self.is_mine_id(group_id):
|
||||||
destination = get_domain_from_id(group_id)
|
destination = get_domain_from_id(group_id)
|
||||||
elif not self.is_mine_id(user_id):
|
elif not self.is_mine_id(user_id):
|
||||||
|
@ -187,9 +188,12 @@ class GroupAttestionRenewer(object):
|
||||||
yield self.store.update_attestation_renewal(
|
yield self.store.update_attestation_renewal(
|
||||||
group_id, user_id, attestation
|
group_id, user_id, attestation
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error renewing attestation of %r in %r",
|
||||||
|
user_id, group_id)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
group_id = row["group_id"]
|
group_id = row["group_id"]
|
||||||
user_id = row["user_id"]
|
user_id = row["user_id"]
|
||||||
|
|
||||||
preserve_fn(_renew_attestation)(group_id, user_id)
|
run_in_background(_renew_attestation, group_id, user_id)
|
||||||
|
|
|
@ -20,6 +20,8 @@ from synapse.api.errors import SynapseError
|
||||||
from synapse.types import GroupID, RoomID, UserID, get_domain_from_id
|
from synapse.types import GroupID, RoomID, UserID, get_domain_from_id
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -431,7 +433,7 @@ class GroupsServerHandler(object):
|
||||||
"long_description"):
|
"long_description"):
|
||||||
if keyname in content:
|
if keyname in content:
|
||||||
value = content[keyname]
|
value = content[keyname]
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, string_types):
|
||||||
raise SynapseError(400, "%r value is not a string" % (keyname,))
|
raise SynapseError(400, "%r value is not a string" % (keyname,))
|
||||||
profile[keyname] = value
|
profile[keyname] = value
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from .register import RegistrationHandler
|
from .register import RegistrationHandler
|
||||||
from .room import (
|
from .room import RoomContextHandler
|
||||||
RoomCreationHandler, RoomContextHandler,
|
|
||||||
)
|
|
||||||
from .message import MessageHandler
|
from .message import MessageHandler
|
||||||
from .federation import FederationHandler
|
from .federation import FederationHandler
|
||||||
from .directory import DirectoryHandler
|
from .directory import DirectoryHandler
|
||||||
|
@ -47,7 +45,6 @@ class Handlers(object):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.registration_handler = RegistrationHandler(hs)
|
self.registration_handler = RegistrationHandler(hs)
|
||||||
self.message_handler = MessageHandler(hs)
|
self.message_handler = MessageHandler(hs)
|
||||||
self.room_creation_handler = RoomCreationHandler(hs)
|
|
||||||
self.federation_handler = FederationHandler(hs)
|
self.federation_handler = FederationHandler(hs)
|
||||||
self.directory_handler = DirectoryHandler(hs)
|
self.directory_handler = DirectoryHandler(hs)
|
||||||
self.admin_handler = AdminHandler(hs)
|
self.admin_handler = AdminHandler(hs)
|
||||||
|
|
|
@ -114,14 +114,14 @@ class BaseHandler(object):
|
||||||
if guest_access != "can_join":
|
if guest_access != "can_join":
|
||||||
if context:
|
if context:
|
||||||
current_state = yield self.store.get_events(
|
current_state = yield self.store.get_events(
|
||||||
context.current_state_ids.values()
|
list(context.current_state_ids.values())
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
current_state = yield self.state_handler.get_current_state(
|
current_state = yield self.state_handler.get_current_state(
|
||||||
event.room_id
|
event.room_id
|
||||||
)
|
)
|
||||||
|
|
||||||
current_state = current_state.values()
|
current_state = list(current_state.values())
|
||||||
|
|
||||||
logger.info("maybe_kick_guest_users %r", current_state)
|
logger.info("maybe_kick_guest_users %r", current_state)
|
||||||
yield self.kick_guest_users(current_state)
|
yield self.kick_guest_users(current_state)
|
||||||
|
|
|
@ -15,20 +15,21 @@
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from six import itervalues
|
||||||
|
|
||||||
import synapse
|
import synapse
|
||||||
from synapse.api.constants import EventTypes
|
from synapse.api.constants import EventTypes
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.util.logcontext import (
|
from synapse.util.logcontext import (
|
||||||
make_deferred_yieldable, preserve_fn, run_in_background,
|
make_deferred_yieldable, run_in_background,
|
||||||
)
|
)
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
events_processed_counter = Counter("synapse_handlers_appservice_events_processed", "")
|
||||||
|
|
||||||
events_processed_counter = metrics.register_counter("events_processed")
|
|
||||||
|
|
||||||
|
|
||||||
def log_failure(failure):
|
def log_failure(failure):
|
||||||
|
@ -111,9 +112,7 @@ class ApplicationServicesHandler(object):
|
||||||
|
|
||||||
# Fork off pushes to these services
|
# Fork off pushes to these services
|
||||||
for service in services:
|
for service in services:
|
||||||
preserve_fn(self.scheduler.submit_event_for_as)(
|
self.scheduler.submit_event_for_as(service, event)
|
||||||
service, event
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def handle_room_events(events):
|
def handle_room_events(events):
|
||||||
|
@ -122,7 +121,7 @@ class ApplicationServicesHandler(object):
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults([
|
yield make_deferred_yieldable(defer.gatherResults([
|
||||||
run_in_background(handle_room_events, evs)
|
run_in_background(handle_room_events, evs)
|
||||||
for evs in events_by_room.itervalues()
|
for evs in itervalues(events_by_room)
|
||||||
], consumeErrors=True))
|
], consumeErrors=True))
|
||||||
|
|
||||||
yield self.store.set_appservice_last_pos(upper_bound)
|
yield self.store.set_appservice_last_pos(upper_bound)
|
||||||
|
@ -130,18 +129,15 @@ class ApplicationServicesHandler(object):
|
||||||
now = self.clock.time_msec()
|
now = self.clock.time_msec()
|
||||||
ts = yield self.store.get_received_ts(events[-1].event_id)
|
ts = yield self.store.get_received_ts(events[-1].event_id)
|
||||||
|
|
||||||
synapse.metrics.event_processing_positions.set(
|
synapse.metrics.event_processing_positions.labels(
|
||||||
upper_bound, "appservice_sender",
|
"appservice_sender").set(upper_bound)
|
||||||
)
|
|
||||||
|
|
||||||
events_processed_counter.inc_by(len(events))
|
events_processed_counter.inc(len(events))
|
||||||
|
|
||||||
synapse.metrics.event_processing_lag.set(
|
synapse.metrics.event_processing_lag.labels(
|
||||||
now - ts, "appservice_sender",
|
"appservice_sender").set(now - ts)
|
||||||
)
|
synapse.metrics.event_processing_last_ts.labels(
|
||||||
synapse.metrics.event_processing_last_ts.set(
|
"appservice_sender").set(ts)
|
||||||
ts, "appservice_sender",
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
self.is_processing = False
|
self.is_processing = False
|
||||||
|
|
||||||
|
@ -198,7 +194,10 @@ class ApplicationServicesHandler(object):
|
||||||
services = yield self._get_services_for_3pn(protocol)
|
services = yield self._get_services_for_3pn(protocol)
|
||||||
|
|
||||||
results = yield make_deferred_yieldable(defer.DeferredList([
|
results = yield make_deferred_yieldable(defer.DeferredList([
|
||||||
preserve_fn(self.appservice_api.query_3pe)(service, kind, protocol, fields)
|
run_in_background(
|
||||||
|
self.appservice_api.query_3pe,
|
||||||
|
service, kind, protocol, fields,
|
||||||
|
)
|
||||||
for service in services
|
for service in services
|
||||||
], consumeErrors=True))
|
], consumeErrors=True))
|
||||||
|
|
||||||
|
@ -259,11 +258,15 @@ class ApplicationServicesHandler(object):
|
||||||
event based on the service regex.
|
event based on the service regex.
|
||||||
"""
|
"""
|
||||||
services = self.store.get_app_services()
|
services = self.store.get_app_services()
|
||||||
interested_list = [
|
|
||||||
s for s in services if (
|
# we can't use a list comprehension here. Since python 3, list
|
||||||
yield s.is_interested(event, self.store)
|
# comprehensions use a generator internally. This means you can't yield
|
||||||
)
|
# inside of a list comprehension anymore.
|
||||||
]
|
interested_list = []
|
||||||
|
for s in services:
|
||||||
|
if (yield s.is_interested(event, self.store)):
|
||||||
|
interested_list.append(s)
|
||||||
|
|
||||||
defer.returnValue(interested_list)
|
defer.returnValue(interested_list)
|
||||||
|
|
||||||
def _get_services_for_user(self, user_id):
|
def _get_services_for_user(self, user_id):
|
||||||
|
|
|
@ -249,7 +249,7 @@ class AuthHandler(BaseHandler):
|
||||||
errordict = e.error_dict()
|
errordict = e.error_dict()
|
||||||
|
|
||||||
for f in flows:
|
for f in flows:
|
||||||
if len(set(f) - set(creds.keys())) == 0:
|
if len(set(f) - set(creds)) == 0:
|
||||||
# it's very useful to know what args are stored, but this can
|
# it's very useful to know what args are stored, but this can
|
||||||
# include the password in the case of registering, so only log
|
# include the password in the case of registering, so only log
|
||||||
# the keys (confusingly, clientdict may contain a password
|
# the keys (confusingly, clientdict may contain a password
|
||||||
|
@ -257,12 +257,12 @@ class AuthHandler(BaseHandler):
|
||||||
# and is not sensitive).
|
# and is not sensitive).
|
||||||
logger.info(
|
logger.info(
|
||||||
"Auth completed with creds: %r. Client dict has keys: %r",
|
"Auth completed with creds: %r. Client dict has keys: %r",
|
||||||
creds, clientdict.keys()
|
creds, list(clientdict)
|
||||||
)
|
)
|
||||||
defer.returnValue((creds, clientdict, session['id']))
|
defer.returnValue((creds, clientdict, session['id']))
|
||||||
|
|
||||||
ret = self._auth_dict_for_flows(flows, session)
|
ret = self._auth_dict_for_flows(flows, session)
|
||||||
ret['completed'] = creds.keys()
|
ret['completed'] = list(creds)
|
||||||
ret.update(errordict)
|
ret.update(errordict)
|
||||||
raise InteractiveAuthIncompleteError(
|
raise InteractiveAuthIncompleteError(
|
||||||
ret,
|
ret,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2017 New Vector Ltd
|
# Copyright 2017, 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,9 +12,11 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer, reactor
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
from synapse.types import UserID, create_requester
|
||||||
|
from synapse.util.logcontext import run_in_background
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -27,6 +29,15 @@ class DeactivateAccountHandler(BaseHandler):
|
||||||
super(DeactivateAccountHandler, self).__init__(hs)
|
super(DeactivateAccountHandler, self).__init__(hs)
|
||||||
self._auth_handler = hs.get_auth_handler()
|
self._auth_handler = hs.get_auth_handler()
|
||||||
self._device_handler = hs.get_device_handler()
|
self._device_handler = hs.get_device_handler()
|
||||||
|
self._room_member_handler = hs.get_room_member_handler()
|
||||||
|
self.user_directory_handler = hs.get_user_directory_handler()
|
||||||
|
|
||||||
|
# Flag that indicates whether the process to part users from rooms is running
|
||||||
|
self._user_parter_running = False
|
||||||
|
|
||||||
|
# Start the user parter loop so it can resume parting users from rooms where
|
||||||
|
# it left off (if it has work left to do).
|
||||||
|
reactor.callWhenRunning(self._start_user_parting)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def deactivate_account(self, user_id):
|
def deactivate_account(self, user_id):
|
||||||
|
@ -50,3 +61,73 @@ class DeactivateAccountHandler(BaseHandler):
|
||||||
|
|
||||||
yield self.store.user_delete_threepids(user_id)
|
yield self.store.user_delete_threepids(user_id)
|
||||||
yield self.store.user_set_password_hash(user_id, None)
|
yield self.store.user_set_password_hash(user_id, None)
|
||||||
|
|
||||||
|
# Add the user to a table of users pending deactivation (ie.
|
||||||
|
# removal from all the rooms they're a member of)
|
||||||
|
yield self.store.add_user_pending_deactivation(user_id)
|
||||||
|
|
||||||
|
# delete from user directory
|
||||||
|
yield self.user_directory_handler.handle_user_deactivated(user_id)
|
||||||
|
|
||||||
|
# Now start the process that goes through that list and
|
||||||
|
# parts users from rooms (if it isn't already running)
|
||||||
|
self._start_user_parting()
|
||||||
|
|
||||||
|
def _start_user_parting(self):
|
||||||
|
"""
|
||||||
|
Start the process that goes through the table of users
|
||||||
|
pending deactivation, if it isn't already running.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
if not self._user_parter_running:
|
||||||
|
run_in_background(self._user_parter_loop)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _user_parter_loop(self):
|
||||||
|
"""Loop that parts deactivated users from rooms
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
self._user_parter_running = True
|
||||||
|
logger.info("Starting user parter")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
user_id = yield self.store.get_user_pending_deactivation()
|
||||||
|
if user_id is None:
|
||||||
|
break
|
||||||
|
logger.info("User parter parting %r", user_id)
|
||||||
|
yield self._part_user(user_id)
|
||||||
|
yield self.store.del_user_pending_deactivation(user_id)
|
||||||
|
logger.info("User parter finished parting %r", user_id)
|
||||||
|
logger.info("User parter finished: stopping")
|
||||||
|
finally:
|
||||||
|
self._user_parter_running = False
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _part_user(self, user_id):
|
||||||
|
"""Causes the given user_id to leave all the rooms they're joined to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
|
rooms_for_user = yield self.store.get_rooms_for_user(user_id)
|
||||||
|
for room_id in rooms_for_user:
|
||||||
|
logger.info("User parter parting %r from %r", user_id, room_id)
|
||||||
|
try:
|
||||||
|
yield self._room_member_handler.update_membership(
|
||||||
|
create_requester(user),
|
||||||
|
user,
|
||||||
|
room_id,
|
||||||
|
"leave",
|
||||||
|
ratelimit=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Failed to part user %r from room %r: ignoring and continuing",
|
||||||
|
user_id, room_id,
|
||||||
|
)
|
||||||
|
|
|
@ -26,6 +26,8 @@ from ._base import BaseHandler
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ class DeviceHandler(BaseHandler):
|
||||||
user_id, device_id=None
|
user_id, device_id=None
|
||||||
)
|
)
|
||||||
|
|
||||||
devices = device_map.values()
|
devices = list(device_map.values())
|
||||||
for device in devices:
|
for device in devices:
|
||||||
_update_device_from_client_ips(device, ips)
|
_update_device_from_client_ips(device, ips)
|
||||||
|
|
||||||
|
@ -185,7 +187,7 @@ class DeviceHandler(BaseHandler):
|
||||||
defer.Deferred:
|
defer.Deferred:
|
||||||
"""
|
"""
|
||||||
device_map = yield self.store.get_devices_by_user(user_id)
|
device_map = yield self.store.get_devices_by_user(user_id)
|
||||||
device_ids = device_map.keys()
|
device_ids = list(device_map)
|
||||||
if except_device_id is not None:
|
if except_device_id is not None:
|
||||||
device_ids = [d for d in device_ids if d != except_device_id]
|
device_ids = [d for d in device_ids if d != except_device_id]
|
||||||
yield self.delete_devices(user_id, device_ids)
|
yield self.delete_devices(user_id, device_ids)
|
||||||
|
@ -318,7 +320,7 @@ class DeviceHandler(BaseHandler):
|
||||||
# The user may have left the room
|
# The user may have left the room
|
||||||
# TODO: Check if they actually did or if we were just invited.
|
# TODO: Check if they actually did or if we were just invited.
|
||||||
if room_id not in room_ids:
|
if room_id not in room_ids:
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
|
@ -338,7 +340,7 @@ class DeviceHandler(BaseHandler):
|
||||||
# special-case for an empty prev state: include all members
|
# special-case for an empty prev state: include all members
|
||||||
# in the changed list
|
# in the changed list
|
||||||
if not event_ids:
|
if not event_ids:
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
|
@ -354,10 +356,10 @@ class DeviceHandler(BaseHandler):
|
||||||
|
|
||||||
# Check if we've joined the room? If so we just blindly add all the users to
|
# Check if we've joined the room? If so we just blindly add all the users to
|
||||||
# the "possibly changed" users.
|
# the "possibly changed" users.
|
||||||
for state_dict in prev_state_ids.itervalues():
|
for state_dict in itervalues(prev_state_ids):
|
||||||
member_event = state_dict.get((EventTypes.Member, user_id), None)
|
member_event = state_dict.get((EventTypes.Member, user_id), None)
|
||||||
if not member_event or member_event != current_member_id:
|
if not member_event or member_event != current_member_id:
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
|
@ -367,14 +369,14 @@ class DeviceHandler(BaseHandler):
|
||||||
# If there has been any change in membership, include them in the
|
# If there has been any change in membership, include them in the
|
||||||
# possibly changed list. We'll check if they are joined below,
|
# possibly changed list. We'll check if they are joined below,
|
||||||
# and we're not toooo worried about spuriously adding users.
|
# and we're not toooo worried about spuriously adding users.
|
||||||
for key, event_id in current_state_ids.iteritems():
|
for key, event_id in iteritems(current_state_ids):
|
||||||
etype, state_key = key
|
etype, state_key = key
|
||||||
if etype != EventTypes.Member:
|
if etype != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# check if this member has changed since any of the extremities
|
# check if this member has changed since any of the extremities
|
||||||
# at the stream_ordering, and add them to the list if so.
|
# at the stream_ordering, and add them to the list if so.
|
||||||
for state_dict in prev_state_ids.itervalues():
|
for state_dict in itervalues(prev_state_ids):
|
||||||
prev_event_id = state_dict.get(key, None)
|
prev_event_id = state_dict.get(key, None)
|
||||||
if not prev_event_id or prev_event_id != event_id:
|
if not prev_event_id or prev_event_id != event_id:
|
||||||
if state_key != user_id:
|
if state_key != user_id:
|
||||||
|
|
|
@ -19,12 +19,13 @@ import logging
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
SynapseError, CodeMessageException, FederationDeniedError,
|
SynapseError, CodeMessageException, FederationDeniedError,
|
||||||
)
|
)
|
||||||
from synapse.types import get_domain_from_id, UserID
|
from synapse.types import get_domain_from_id, UserID
|
||||||
from synapse.util.logcontext import preserve_fn, make_deferred_yieldable
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
from synapse.util.retryutils import NotRetryingDestination
|
from synapse.util.retryutils import NotRetryingDestination
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -92,7 +93,7 @@ class E2eKeysHandler(object):
|
||||||
remote_queries_not_in_cache = {}
|
remote_queries_not_in_cache = {}
|
||||||
if remote_queries:
|
if remote_queries:
|
||||||
query_list = []
|
query_list = []
|
||||||
for user_id, device_ids in remote_queries.iteritems():
|
for user_id, device_ids in iteritems(remote_queries):
|
||||||
if device_ids:
|
if device_ids:
|
||||||
query_list.extend((user_id, device_id) for device_id in device_ids)
|
query_list.extend((user_id, device_id) for device_id in device_ids)
|
||||||
else:
|
else:
|
||||||
|
@ -103,9 +104,9 @@ class E2eKeysHandler(object):
|
||||||
query_list
|
query_list
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for user_id, devices in remote_results.iteritems():
|
for user_id, devices in iteritems(remote_results):
|
||||||
user_devices = results.setdefault(user_id, {})
|
user_devices = results.setdefault(user_id, {})
|
||||||
for device_id, device in devices.iteritems():
|
for device_id, device in iteritems(devices):
|
||||||
keys = device.get("keys", None)
|
keys = device.get("keys", None)
|
||||||
device_display_name = device.get("device_display_name", None)
|
device_display_name = device.get("device_display_name", None)
|
||||||
if keys:
|
if keys:
|
||||||
|
@ -139,9 +140,9 @@ class E2eKeysHandler(object):
|
||||||
failures[destination] = _exception_to_failure(e)
|
failures[destination] = _exception_to_failure(e)
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults([
|
yield make_deferred_yieldable(defer.gatherResults([
|
||||||
preserve_fn(do_remote_query)(destination)
|
run_in_background(do_remote_query, destination)
|
||||||
for destination in remote_queries_not_in_cache
|
for destination in remote_queries_not_in_cache
|
||||||
]))
|
], consumeErrors=True))
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
"device_keys": results, "failures": failures,
|
"device_keys": results, "failures": failures,
|
||||||
|
@ -242,17 +243,17 @@ class E2eKeysHandler(object):
|
||||||
failures[destination] = _exception_to_failure(e)
|
failures[destination] = _exception_to_failure(e)
|
||||||
|
|
||||||
yield make_deferred_yieldable(defer.gatherResults([
|
yield make_deferred_yieldable(defer.gatherResults([
|
||||||
preserve_fn(claim_client_keys)(destination)
|
run_in_background(claim_client_keys, destination)
|
||||||
for destination in remote_queries
|
for destination in remote_queries
|
||||||
]))
|
], consumeErrors=True))
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Claimed one-time-keys: %s",
|
"Claimed one-time-keys: %s",
|
||||||
",".join((
|
",".join((
|
||||||
"%s for %s:%s" % (key_id, user_id, device_id)
|
"%s for %s:%s" % (key_id, user_id, device_id)
|
||||||
for user_id, user_keys in json_result.iteritems()
|
for user_id, user_keys in iteritems(json_result)
|
||||||
for device_id, device_keys in user_keys.iteritems()
|
for device_id, device_keys in iteritems(user_keys)
|
||||||
for key_id, _ in device_keys.iteritems()
|
for key_id, _ in iteritems(device_keys)
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ class EventStreamHandler(BaseHandler):
|
||||||
|
|
||||||
self.notifier = hs.get_notifier()
|
self.notifier = hs.get_notifier()
|
||||||
self.state = hs.get_state_handler()
|
self.state = hs.get_state_handler()
|
||||||
|
self._server_notices_sender = hs.get_server_notices_sender()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
|
@ -58,6 +59,10 @@ class EventStreamHandler(BaseHandler):
|
||||||
|
|
||||||
If `only_keys` is not None, events from keys will be sent down.
|
If `only_keys` is not None, events from keys will be sent down.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# send any outstanding server notices to the user.
|
||||||
|
yield self._server_notices_sender.on_user_syncing(auth_user_id)
|
||||||
|
|
||||||
auth_user = UserID.from_string(auth_user_id)
|
auth_user = UserID.from_string(auth_user_id)
|
||||||
presence_handler = self.hs.get_presence_handler()
|
presence_handler = self.hs.get_presence_handler()
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,15 @@
|
||||||
|
|
||||||
"""Contains handlers for federation events."""
|
"""Contains handlers for federation events."""
|
||||||
|
|
||||||
import httplib
|
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
from signedjson.key import decode_verify_key_bytes
|
from signedjson.key import decode_verify_key_bytes
|
||||||
from signedjson.sign import verify_signed_json
|
from signedjson.sign import verify_signed_json
|
||||||
|
import six
|
||||||
|
from six.moves import http_client
|
||||||
|
from six import iteritems
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
|
|
||||||
|
@ -49,7 +52,6 @@ from synapse.util.retryutils import NotRetryingDestination
|
||||||
|
|
||||||
from synapse.util.distributor import user_joined_room
|
from synapse.util.distributor import user_joined_room
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,6 +81,7 @@ class FederationHandler(BaseHandler):
|
||||||
self.pusher_pool = hs.get_pusherpool()
|
self.pusher_pool = hs.get_pusherpool()
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
||||||
|
|
||||||
# When joining a room we need to queue any events for that room up
|
# When joining a room we need to queue any events for that room up
|
||||||
self.room_queues = {}
|
self.room_queues = {}
|
||||||
|
@ -476,7 +479,7 @@ class FederationHandler(BaseHandler):
|
||||||
# to get all state ids that we're interested in.
|
# to get all state ids that we're interested in.
|
||||||
event_map = yield self.store.get_events([
|
event_map = yield self.store.get_events([
|
||||||
e_id
|
e_id
|
||||||
for key_to_eid in event_to_state_ids.values()
|
for key_to_eid in list(event_to_state_ids.values())
|
||||||
for key, e_id in key_to_eid.items()
|
for key, e_id in key_to_eid.items()
|
||||||
if key[0] != EventTypes.Member or check_match(key[1])
|
if key[0] != EventTypes.Member or check_match(key[1])
|
||||||
])
|
])
|
||||||
|
@ -484,10 +487,10 @@ class FederationHandler(BaseHandler):
|
||||||
event_to_state = {
|
event_to_state = {
|
||||||
e_id: {
|
e_id: {
|
||||||
key: event_map[inner_e_id]
|
key: event_map[inner_e_id]
|
||||||
for key, inner_e_id in key_to_eid.items()
|
for key, inner_e_id in key_to_eid.iteritems()
|
||||||
if inner_e_id in event_map
|
if inner_e_id in event_map
|
||||||
}
|
}
|
||||||
for e_id, key_to_eid in event_to_state_ids.items()
|
for e_id, key_to_eid in event_to_state_ids.iteritems()
|
||||||
}
|
}
|
||||||
|
|
||||||
def redact_disallowed(event, state):
|
def redact_disallowed(event, state):
|
||||||
|
@ -502,7 +505,7 @@ class FederationHandler(BaseHandler):
|
||||||
# membership states for the requesting server to determine
|
# membership states for the requesting server to determine
|
||||||
# if the server is either in the room or has been invited
|
# if the server is either in the room or has been invited
|
||||||
# into the room.
|
# into the room.
|
||||||
for ev in state.values():
|
for ev in state.itervalues():
|
||||||
if ev.type != EventTypes.Member:
|
if ev.type != EventTypes.Member:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
|
@ -637,7 +640,8 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
logcontext.preserve_fn(self.replication_layer.get_pdu)(
|
logcontext.run_in_background(
|
||||||
|
self.replication_layer.get_pdu,
|
||||||
[dest],
|
[dest],
|
||||||
event_id,
|
event_id,
|
||||||
outlier=True,
|
outlier=True,
|
||||||
|
@ -747,9 +751,19 @@ class FederationHandler(BaseHandler):
|
||||||
curr_state = yield self.state_handler.get_current_state(room_id)
|
curr_state = yield self.state_handler.get_current_state(room_id)
|
||||||
|
|
||||||
def get_domains_from_state(state):
|
def get_domains_from_state(state):
|
||||||
|
"""Get joined domains from state
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state (dict[tuple, FrozenEvent]): State map from type/state
|
||||||
|
key to event.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[tuple[str, int]]: Returns a list of servers with the
|
||||||
|
lowest depth of their joins. Sorted by lowest depth first.
|
||||||
|
"""
|
||||||
joined_users = [
|
joined_users = [
|
||||||
(state_key, int(event.depth))
|
(state_key, int(event.depth))
|
||||||
for (e_type, state_key), event in state.items()
|
for (e_type, state_key), event in state.iteritems()
|
||||||
if e_type == EventTypes.Member
|
if e_type == EventTypes.Member
|
||||||
and event.membership == Membership.JOIN
|
and event.membership == Membership.JOIN
|
||||||
]
|
]
|
||||||
|
@ -766,7 +780,7 @@ class FederationHandler(BaseHandler):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return sorted(joined_domains.items(), key=lambda d: d[1])
|
return sorted(joined_domains.iteritems(), key=lambda d: d[1])
|
||||||
|
|
||||||
curr_domains = get_domains_from_state(curr_state)
|
curr_domains = get_domains_from_state(curr_state)
|
||||||
|
|
||||||
|
@ -783,7 +797,7 @@ class FederationHandler(BaseHandler):
|
||||||
yield self.backfill(
|
yield self.backfill(
|
||||||
dom, room_id,
|
dom, room_id,
|
||||||
limit=100,
|
limit=100,
|
||||||
extremities=[e for e in extremities.keys()]
|
extremities=extremities,
|
||||||
)
|
)
|
||||||
# If this succeeded then we probably already have the
|
# If this succeeded then we probably already have the
|
||||||
# appropriate stuff.
|
# appropriate stuff.
|
||||||
|
@ -829,7 +843,7 @@ class FederationHandler(BaseHandler):
|
||||||
tried_domains = set(likely_domains)
|
tried_domains = set(likely_domains)
|
||||||
tried_domains.add(self.server_name)
|
tried_domains.add(self.server_name)
|
||||||
|
|
||||||
event_ids = list(extremities.keys())
|
event_ids = list(extremities.iterkeys())
|
||||||
|
|
||||||
logger.debug("calling resolve_state_groups in _maybe_backfill")
|
logger.debug("calling resolve_state_groups in _maybe_backfill")
|
||||||
resolve = logcontext.preserve_fn(
|
resolve = logcontext.preserve_fn(
|
||||||
|
@ -839,31 +853,34 @@ class FederationHandler(BaseHandler):
|
||||||
[resolve(room_id, [e]) for e in event_ids],
|
[resolve(room_id, [e]) for e in event_ids],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# dict[str, dict[tuple, str]], a map from event_id to state map of
|
||||||
|
# event_ids.
|
||||||
states = dict(zip(event_ids, [s.state for s in states]))
|
states = dict(zip(event_ids, [s.state for s in states]))
|
||||||
|
|
||||||
state_map = yield self.store.get_events(
|
state_map = yield self.store.get_events(
|
||||||
[e_id for ids in states.values() for e_id in ids],
|
[e_id for ids in states.itervalues() for e_id in ids.itervalues()],
|
||||||
get_prev_content=False
|
get_prev_content=False
|
||||||
)
|
)
|
||||||
states = {
|
states = {
|
||||||
key: {
|
key: {
|
||||||
k: state_map[e_id]
|
k: state_map[e_id]
|
||||||
for k, e_id in state_dict.items()
|
for k, e_id in state_dict.iteritems()
|
||||||
if e_id in state_map
|
if e_id in state_map
|
||||||
} for key, state_dict in states.items()
|
} for key, state_dict in states.iteritems()
|
||||||
}
|
}
|
||||||
|
|
||||||
for e_id, _ in sorted_extremeties_tuple:
|
for e_id, _ in sorted_extremeties_tuple:
|
||||||
likely_domains = get_domains_from_state(states[e_id])
|
likely_domains = get_domains_from_state(states[e_id])
|
||||||
|
|
||||||
success = yield try_backfill([
|
success = yield try_backfill([
|
||||||
dom for dom in likely_domains
|
dom for dom, _ in likely_domains
|
||||||
if dom not in tried_domains
|
if dom not in tried_domains
|
||||||
])
|
])
|
||||||
if success:
|
if success:
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
tried_domains.update(likely_domains)
|
tried_domains.update(dom for dom, _ in likely_domains)
|
||||||
|
|
||||||
defer.returnValue(False)
|
defer.returnValue(False)
|
||||||
|
|
||||||
|
@ -887,7 +904,7 @@ class FederationHandler(BaseHandler):
|
||||||
logger.warn("Rejecting event %s which has %i prev_events",
|
logger.warn("Rejecting event %s which has %i prev_events",
|
||||||
ev.event_id, len(ev.prev_events))
|
ev.event_id, len(ev.prev_events))
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
httplib.BAD_REQUEST,
|
http_client.BAD_REQUEST,
|
||||||
"Too many prev_events",
|
"Too many prev_events",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -895,7 +912,7 @@ class FederationHandler(BaseHandler):
|
||||||
logger.warn("Rejecting event %s which has %i auth_events",
|
logger.warn("Rejecting event %s which has %i auth_events",
|
||||||
ev.event_id, len(ev.auth_events))
|
ev.event_id, len(ev.auth_events))
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
httplib.BAD_REQUEST,
|
http_client.BAD_REQUEST,
|
||||||
"Too many auth_events",
|
"Too many auth_events",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1023,7 +1040,7 @@ class FederationHandler(BaseHandler):
|
||||||
# lots of requests for missing prev_events which we do actually
|
# lots of requests for missing prev_events which we do actually
|
||||||
# have. Hence we fire off the deferred, but don't wait for it.
|
# have. Hence we fire off the deferred, but don't wait for it.
|
||||||
|
|
||||||
logcontext.preserve_fn(self._handle_queued_pdus)(room_queue)
|
logcontext.run_in_background(self._handle_queued_pdus, room_queue)
|
||||||
|
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
|
@ -1131,13 +1148,13 @@ class FederationHandler(BaseHandler):
|
||||||
user = UserID.from_string(event.state_key)
|
user = UserID.from_string(event.state_key)
|
||||||
yield user_joined_room(self.distributor, user, event.room_id)
|
yield user_joined_room(self.distributor, user, event.room_id)
|
||||||
|
|
||||||
state_ids = context.prev_state_ids.values()
|
state_ids = list(context.prev_state_ids.values())
|
||||||
auth_chain = yield self.store.get_auth_chain(state_ids)
|
auth_chain = yield self.store.get_auth_chain(state_ids)
|
||||||
|
|
||||||
state = yield self.store.get_events(context.prev_state_ids.values())
|
state = yield self.store.get_events(list(context.prev_state_ids.values()))
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
"state": state.values(),
|
"state": list(state.values()),
|
||||||
"auth_chain": auth_chain,
|
"auth_chain": auth_chain,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1177,6 +1194,13 @@ class FederationHandler(BaseHandler):
|
||||||
if not self.is_mine_id(event.state_key):
|
if not self.is_mine_id(event.state_key):
|
||||||
raise SynapseError(400, "The invite event must be for this server")
|
raise SynapseError(400, "The invite event must be for this server")
|
||||||
|
|
||||||
|
# block any attempts to invite the server notices mxid
|
||||||
|
if event.state_key == self._server_notices_mxid:
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.FORBIDDEN,
|
||||||
|
"Cannot invite this user",
|
||||||
|
)
|
||||||
|
|
||||||
event.internal_metadata.outlier = True
|
event.internal_metadata.outlier = True
|
||||||
event.internal_metadata.invite_from_remote = True
|
event.internal_metadata.invite_from_remote = True
|
||||||
|
|
||||||
|
@ -1364,7 +1388,7 @@ class FederationHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
if state_groups:
|
if state_groups:
|
||||||
_, state = state_groups.items().pop()
|
_, state = list(iteritems(state_groups)).pop()
|
||||||
results = {
|
results = {
|
||||||
(e.type, e.state_key): e for e in state
|
(e.type, e.state_key): e for e in state
|
||||||
}
|
}
|
||||||
|
@ -1380,7 +1404,7 @@ class FederationHandler(BaseHandler):
|
||||||
else:
|
else:
|
||||||
del results[(event.type, event.state_key)]
|
del results[(event.type, event.state_key)]
|
||||||
|
|
||||||
res = results.values()
|
res = list(results.values())
|
||||||
for event in res:
|
for event in res:
|
||||||
# We sign these again because there was a bug where we
|
# We sign these again because there was a bug where we
|
||||||
# incorrectly signed things the first time round
|
# incorrectly signed things the first time round
|
||||||
|
@ -1421,7 +1445,7 @@ class FederationHandler(BaseHandler):
|
||||||
else:
|
else:
|
||||||
results.pop((event.type, event.state_key), None)
|
results.pop((event.type, event.state_key), None)
|
||||||
|
|
||||||
defer.returnValue(results.values())
|
defer.returnValue(list(results.values()))
|
||||||
else:
|
else:
|
||||||
defer.returnValue([])
|
defer.returnValue([])
|
||||||
|
|
||||||
|
@ -1513,18 +1537,21 @@ class FederationHandler(BaseHandler):
|
||||||
backfilled=backfilled,
|
backfilled=backfilled,
|
||||||
)
|
)
|
||||||
except: # noqa: E722, as we reraise the exception this is fine.
|
except: # noqa: E722, as we reraise the exception this is fine.
|
||||||
# Ensure that we actually remove the entries in the push actions
|
tp, value, tb = sys.exc_info()
|
||||||
# staging area
|
|
||||||
logcontext.preserve_fn(
|
logcontext.run_in_background(
|
||||||
self.store.remove_push_actions_from_staging
|
self.store.remove_push_actions_from_staging,
|
||||||
)(event.event_id)
|
event.event_id,
|
||||||
raise
|
)
|
||||||
|
|
||||||
|
six.reraise(tp, value, tb)
|
||||||
|
|
||||||
if not backfilled:
|
if not backfilled:
|
||||||
# this intentionally does not yield: we don't care about the result
|
# this intentionally does not yield: we don't care about the result
|
||||||
# and don't need to wait for it.
|
# and don't need to wait for it.
|
||||||
logcontext.preserve_fn(self.pusher_pool.on_new_notifications)(
|
logcontext.run_in_background(
|
||||||
event_stream_id, max_stream_id
|
self.pusher_pool.on_new_notifications,
|
||||||
|
event_stream_id, max_stream_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((context, event_stream_id, max_stream_id))
|
defer.returnValue((context, event_stream_id, max_stream_id))
|
||||||
|
@ -1538,7 +1565,8 @@ class FederationHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
contexts = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
contexts = yield logcontext.make_deferred_yieldable(defer.gatherResults(
|
||||||
[
|
[
|
||||||
logcontext.preserve_fn(self._prep_event)(
|
logcontext.run_in_background(
|
||||||
|
self._prep_event,
|
||||||
origin,
|
origin,
|
||||||
ev_info["event"],
|
ev_info["event"],
|
||||||
state=ev_info.get("state"),
|
state=ev_info.get("state"),
|
||||||
|
@ -1766,6 +1794,10 @@ class FederationHandler(BaseHandler):
|
||||||
min_depth=min_depth,
|
min_depth=min_depth,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
missing_events = yield self._filter_events_for_server(
|
||||||
|
origin, room_id, missing_events,
|
||||||
|
)
|
||||||
|
|
||||||
defer.returnValue(missing_events)
|
defer.returnValue(missing_events)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -1867,7 +1899,8 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
different_events = yield logcontext.make_deferred_yieldable(
|
different_events = yield logcontext.make_deferred_yieldable(
|
||||||
defer.gatherResults([
|
defer.gatherResults([
|
||||||
logcontext.preserve_fn(self.store.get_event)(
|
logcontext.run_in_background(
|
||||||
|
self.store.get_event,
|
||||||
d,
|
d,
|
||||||
allow_none=True,
|
allow_none=True,
|
||||||
allow_rejected=False,
|
allow_rejected=False,
|
||||||
|
@ -1885,7 +1918,7 @@ class FederationHandler(BaseHandler):
|
||||||
})
|
})
|
||||||
|
|
||||||
new_state = self.state_handler.resolve_events(
|
new_state = self.state_handler.resolve_events(
|
||||||
[local_view.values(), remote_view.values()],
|
[list(local_view.values()), list(remote_view.values())],
|
||||||
event
|
event
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2005,7 +2038,7 @@ class FederationHandler(BaseHandler):
|
||||||
this will not be included in the current_state in the context.
|
this will not be included in the current_state in the context.
|
||||||
"""
|
"""
|
||||||
state_updates = {
|
state_updates = {
|
||||||
k: a.event_id for k, a in auth_events.iteritems()
|
k: a.event_id for k, a in iteritems(auth_events)
|
||||||
if k != event_key
|
if k != event_key
|
||||||
}
|
}
|
||||||
context.current_state_ids = dict(context.current_state_ids)
|
context.current_state_ids = dict(context.current_state_ids)
|
||||||
|
@ -2015,7 +2048,7 @@ class FederationHandler(BaseHandler):
|
||||||
context.delta_ids.update(state_updates)
|
context.delta_ids.update(state_updates)
|
||||||
context.prev_state_ids = dict(context.prev_state_ids)
|
context.prev_state_ids = dict(context.prev_state_ids)
|
||||||
context.prev_state_ids.update({
|
context.prev_state_ids.update({
|
||||||
k: a.event_id for k, a in auth_events.iteritems()
|
k: a.event_id for k, a in iteritems(auth_events)
|
||||||
})
|
})
|
||||||
context.state_group = yield self.store.store_state_group(
|
context.state_group = yield self.store.store_state_group(
|
||||||
event.event_id,
|
event.event_id,
|
||||||
|
@ -2067,7 +2100,7 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
def get_next(it, opt=None):
|
def get_next(it, opt=None):
|
||||||
try:
|
try:
|
||||||
return it.next()
|
return next(it)
|
||||||
except Exception:
|
except Exception:
|
||||||
return opt
|
return opt
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.types import get_domain_from_id
|
from synapse.types import get_domain_from_id
|
||||||
|
@ -449,7 +450,7 @@ class GroupsLocalHandler(object):
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
failed_results = []
|
failed_results = []
|
||||||
for destination, dest_user_ids in destinations.iteritems():
|
for destination, dest_user_ids in iteritems(destinations):
|
||||||
try:
|
try:
|
||||||
r = yield self.transport_client.bulk_get_publicised_groups(
|
r = yield self.transport_client.bulk_get_publicised_groups(
|
||||||
destination, list(dest_user_ids),
|
destination, list(dest_user_ids),
|
||||||
|
|
|
@ -27,7 +27,7 @@ from synapse.types import (
|
||||||
from synapse.util import unwrapFirstError
|
from synapse.util import unwrapFirstError
|
||||||
from synapse.util.async import concurrently_execute
|
from synapse.util.async import concurrently_execute
|
||||||
from synapse.util.caches.snapshot_cache import SnapshotCache
|
from synapse.util.caches.snapshot_cache import SnapshotCache
|
||||||
from synapse.util.logcontext import make_deferred_yieldable, preserve_fn
|
from synapse.util.logcontext import make_deferred_yieldable, run_in_background
|
||||||
from synapse.visibility import filter_events_for_client
|
from synapse.visibility import filter_events_for_client
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
@ -166,7 +166,8 @@ class InitialSyncHandler(BaseHandler):
|
||||||
(messages, token), current_state = yield make_deferred_yieldable(
|
(messages, token), current_state = yield make_deferred_yieldable(
|
||||||
defer.gatherResults(
|
defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(self.store.get_recent_events_for_room)(
|
run_in_background(
|
||||||
|
self.store.get_recent_events_for_room,
|
||||||
event.room_id,
|
event.room_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
end_token=room_end_token,
|
end_token=room_end_token,
|
||||||
|
@ -180,8 +181,8 @@ class InitialSyncHandler(BaseHandler):
|
||||||
self.store, user_id, messages
|
self.store, user_id, messages
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace("room_key", token[0])
|
start_token = now_token.copy_and_replace("room_key", token)
|
||||||
end_token = now_token.copy_and_replace("room_key", token[1])
|
end_token = now_token.copy_and_replace("room_key", room_end_token)
|
||||||
time_now = self.clock.time_msec()
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
d["messages"] = {
|
d["messages"] = {
|
||||||
|
@ -324,8 +325,8 @@ class InitialSyncHandler(BaseHandler):
|
||||||
self.store, user_id, messages, is_peeking=is_peeking
|
self.store, user_id, messages, is_peeking=is_peeking
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = StreamToken.START.copy_and_replace("room_key", token[0])
|
start_token = StreamToken.START.copy_and_replace("room_key", token)
|
||||||
end_token = StreamToken.START.copy_and_replace("room_key", token[1])
|
end_token = StreamToken.START.copy_and_replace("room_key", stream_token)
|
||||||
|
|
||||||
time_now = self.clock.time_msec()
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
|
@ -391,9 +392,10 @@ class InitialSyncHandler(BaseHandler):
|
||||||
|
|
||||||
presence, receipts, (messages, token) = yield defer.gatherResults(
|
presence, receipts, (messages, token) = yield defer.gatherResults(
|
||||||
[
|
[
|
||||||
preserve_fn(get_presence)(),
|
run_in_background(get_presence),
|
||||||
preserve_fn(get_receipts)(),
|
run_in_background(get_receipts),
|
||||||
preserve_fn(self.store.get_recent_events_for_room)(
|
run_in_background(
|
||||||
|
self.store.get_recent_events_for_room,
|
||||||
room_id,
|
room_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
end_token=now_token.room_key,
|
end_token=now_token.room_key,
|
||||||
|
@ -406,8 +408,8 @@ class InitialSyncHandler(BaseHandler):
|
||||||
self.store, user_id, messages, is_peeking=is_peeking,
|
self.store, user_id, messages, is_peeking=is_peeking,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace("room_key", token[0])
|
start_token = now_token.copy_and_replace("room_key", token)
|
||||||
end_token = now_token.copy_and_replace("room_key", token[1])
|
end_token = now_token
|
||||||
|
|
||||||
time_now = self.clock.time_msec()
|
time_now = self.clock.time_msec()
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,23 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import logging
|
||||||
|
import simplejson
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from canonicaljson import encode_canonical_json
|
||||||
|
import six
|
||||||
|
from six import string_types, itervalues, iteritems
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
|
from twisted.internet.defer import succeed
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, Membership, MAX_DEPTH
|
from synapse.api.constants import EventTypes, Membership, MAX_DEPTH
|
||||||
from synapse.api.errors import AuthError, Codes, SynapseError
|
from synapse.api.errors import (
|
||||||
|
AuthError, Codes, SynapseError,
|
||||||
|
ConsentNotGivenError,
|
||||||
|
)
|
||||||
|
from synapse.api.urls import ConsentURIBuilder
|
||||||
from synapse.crypto.event_signing import add_hashes_and_signatures
|
from synapse.crypto.event_signing import add_hashes_and_signatures
|
||||||
from synapse.events.utils import serialize_event
|
from synapse.events.utils import serialize_event
|
||||||
from synapse.events.validator import EventValidator
|
from synapse.events.validator import EventValidator
|
||||||
|
@ -25,7 +37,7 @@ from synapse.types import (
|
||||||
UserID, RoomAlias, RoomStreamToken,
|
UserID, RoomAlias, RoomStreamToken,
|
||||||
)
|
)
|
||||||
from synapse.util.async import run_on_reactor, ReadWriteLock, Limiter
|
from synapse.util.async import run_on_reactor, ReadWriteLock, Limiter
|
||||||
from synapse.util.logcontext import preserve_fn, run_in_background
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.metrics import measure_func
|
from synapse.util.metrics import measure_func
|
||||||
from synapse.util.frozenutils import frozendict_json_encoder
|
from synapse.util.frozenutils import frozendict_json_encoder
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
|
@ -34,11 +46,6 @@ from synapse.replication.http.send_event import send_event_to_master
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import simplejson
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,14 +92,14 @@ class MessageHandler(BaseHandler):
|
||||||
# map from purge id to PurgeStatus
|
# map from purge id to PurgeStatus
|
||||||
self._purges_by_id = {}
|
self._purges_by_id = {}
|
||||||
|
|
||||||
def start_purge_history(self, room_id, topological_ordering,
|
def start_purge_history(self, room_id, token,
|
||||||
delete_local_events=False):
|
delete_local_events=False):
|
||||||
"""Start off a history purge on a room.
|
"""Start off a history purge on a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
room_id (str): The room to purge from
|
room_id (str): The room to purge from
|
||||||
|
|
||||||
topological_ordering (int): minimum topo ordering to preserve
|
token (str): topological token to delete events before
|
||||||
delete_local_events (bool): True to delete local events as well as
|
delete_local_events (bool): True to delete local events as well as
|
||||||
remote ones
|
remote ones
|
||||||
|
|
||||||
|
@ -114,19 +121,19 @@ class MessageHandler(BaseHandler):
|
||||||
self._purges_by_id[purge_id] = PurgeStatus()
|
self._purges_by_id[purge_id] = PurgeStatus()
|
||||||
run_in_background(
|
run_in_background(
|
||||||
self._purge_history,
|
self._purge_history,
|
||||||
purge_id, room_id, topological_ordering, delete_local_events,
|
purge_id, room_id, token, delete_local_events,
|
||||||
)
|
)
|
||||||
return purge_id
|
return purge_id
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _purge_history(self, purge_id, room_id, topological_ordering,
|
def _purge_history(self, purge_id, room_id, token,
|
||||||
delete_local_events):
|
delete_local_events):
|
||||||
"""Carry out a history purge on a room.
|
"""Carry out a history purge on a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
purge_id (str): The id for this purge
|
purge_id (str): The id for this purge
|
||||||
room_id (str): The room to purge from
|
room_id (str): The room to purge from
|
||||||
topological_ordering (int): minimum topo ordering to preserve
|
token (str): topological token to delete events before
|
||||||
delete_local_events (bool): True to delete local events as well as
|
delete_local_events (bool): True to delete local events as well as
|
||||||
remote ones
|
remote ones
|
||||||
|
|
||||||
|
@ -137,7 +144,7 @@ class MessageHandler(BaseHandler):
|
||||||
try:
|
try:
|
||||||
with (yield self.pagination_lock.write(room_id)):
|
with (yield self.pagination_lock.write(room_id)):
|
||||||
yield self.store.purge_history(
|
yield self.store.purge_history(
|
||||||
room_id, topological_ordering, delete_local_events,
|
room_id, token, delete_local_events,
|
||||||
)
|
)
|
||||||
logger.info("[purge] complete")
|
logger.info("[purge] complete")
|
||||||
self._purges_by_id[purge_id].status = PurgeStatus.STATUS_COMPLETE
|
self._purges_by_id[purge_id].status = PurgeStatus.STATUS_COMPLETE
|
||||||
|
@ -396,7 +403,7 @@ class MessageHandler(BaseHandler):
|
||||||
"avatar_url": profile.avatar_url,
|
"avatar_url": profile.avatar_url,
|
||||||
"display_name": profile.display_name,
|
"display_name": profile.display_name,
|
||||||
}
|
}
|
||||||
for user_id, profile in users_with_profile.iteritems()
|
for user_id, profile in iteritems(users_with_profile)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -430,6 +437,9 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
|
|
||||||
|
if self.config.block_events_without_consent_error is not None:
|
||||||
|
self._consent_uri_builder = ConsentURIBuilder(self.config)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_event(self, requester, event_dict, token_id=None, txn_id=None,
|
def create_event(self, requester, event_dict, token_id=None, txn_id=None,
|
||||||
prev_events_and_hashes=None):
|
prev_events_and_hashes=None):
|
||||||
|
@ -481,6 +491,10 @@ class EventCreationHandler(object):
|
||||||
target, e
|
target, e
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_exempt = yield self._is_exempt_from_privacy_policy(builder)
|
||||||
|
if not is_exempt:
|
||||||
|
yield self.assert_accepted_privacy_policy(requester)
|
||||||
|
|
||||||
if token_id is not None:
|
if token_id is not None:
|
||||||
builder.internal_metadata.token_id = token_id
|
builder.internal_metadata.token_id = token_id
|
||||||
|
|
||||||
|
@ -495,6 +509,86 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
defer.returnValue((event, context))
|
defer.returnValue((event, context))
|
||||||
|
|
||||||
|
def _is_exempt_from_privacy_policy(self, builder):
|
||||||
|
""""Determine if an event to be sent is exempt from having to consent
|
||||||
|
to the privacy policy
|
||||||
|
|
||||||
|
Args:
|
||||||
|
builder (synapse.events.builder.EventBuilder): event being created
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[bool]: true if the event can be sent without the user
|
||||||
|
consenting
|
||||||
|
"""
|
||||||
|
# the only thing the user can do is join the server notices room.
|
||||||
|
if builder.type == EventTypes.Member:
|
||||||
|
membership = builder.content.get("membership", None)
|
||||||
|
if membership == Membership.JOIN:
|
||||||
|
return self._is_server_notices_room(builder.room_id)
|
||||||
|
return succeed(False)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _is_server_notices_room(self, room_id):
|
||||||
|
if self.config.server_notices_mxid is None:
|
||||||
|
defer.returnValue(False)
|
||||||
|
user_ids = yield self.store.get_users_in_room(room_id)
|
||||||
|
defer.returnValue(self.config.server_notices_mxid in user_ids)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def assert_accepted_privacy_policy(self, requester):
|
||||||
|
"""Check if a user has accepted the privacy policy
|
||||||
|
|
||||||
|
Called when the given user is about to do something that requires
|
||||||
|
privacy consent. We see if the user is exempt and otherwise check that
|
||||||
|
they have given consent. If they have not, a ConsentNotGiven error is
|
||||||
|
raised.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester (synapse.types.Requester):
|
||||||
|
The user making the request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[None]: returns normally if the user has consented or is
|
||||||
|
exempt
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConsentNotGivenError: if the user has not given consent yet
|
||||||
|
"""
|
||||||
|
if self.config.block_events_without_consent_error is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# exempt AS users from needing consent
|
||||||
|
if requester.app_service is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = requester.user.to_string()
|
||||||
|
|
||||||
|
# exempt the system notices user
|
||||||
|
if (
|
||||||
|
self.config.server_notices_mxid is not None and
|
||||||
|
user_id == self.config.server_notices_mxid
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
u = yield self.store.get_user_by_id(user_id)
|
||||||
|
assert u is not None
|
||||||
|
if u["appservice_id"] is not None:
|
||||||
|
# users registered by an appservice are exempt
|
||||||
|
return
|
||||||
|
if u["consent_version"] == self.config.user_consent_version:
|
||||||
|
return
|
||||||
|
|
||||||
|
consent_uri = self._consent_uri_builder.build_user_consent_uri(
|
||||||
|
requester.user.localpart,
|
||||||
|
)
|
||||||
|
msg = self.config.block_events_without_consent_error % {
|
||||||
|
'consent_uri': consent_uri,
|
||||||
|
}
|
||||||
|
raise ConsentNotGivenError(
|
||||||
|
msg=msg,
|
||||||
|
consent_uri=consent_uri,
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def send_nonmember_event(self, requester, event, context, ratelimit=True):
|
def send_nonmember_event(self, requester, event, context, ratelimit=True):
|
||||||
"""
|
"""
|
||||||
|
@ -577,7 +671,7 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
spam_error = self.spam_checker.check_event_for_spam(event)
|
spam_error = self.spam_checker.check_event_for_spam(event)
|
||||||
if spam_error:
|
if spam_error:
|
||||||
if not isinstance(spam_error, basestring):
|
if not isinstance(spam_error, string_types):
|
||||||
spam_error = "Spam is not permitted here"
|
spam_error = "Spam is not permitted here"
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
403, spam_error, Codes.FORBIDDEN
|
403, spam_error, Codes.FORBIDDEN
|
||||||
|
@ -733,8 +827,14 @@ class EventCreationHandler(object):
|
||||||
except: # noqa: E722, as we reraise the exception this is fine.
|
except: # noqa: E722, as we reraise the exception this is fine.
|
||||||
# Ensure that we actually remove the entries in the push actions
|
# Ensure that we actually remove the entries in the push actions
|
||||||
# staging area, if we calculated them.
|
# staging area, if we calculated them.
|
||||||
preserve_fn(self.store.remove_push_actions_from_staging)(event.event_id)
|
tp, value, tb = sys.exc_info()
|
||||||
raise
|
|
||||||
|
run_in_background(
|
||||||
|
self.store.remove_push_actions_from_staging,
|
||||||
|
event.event_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
six.reraise(tp, value, tb)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def persist_and_notify_client_event(
|
def persist_and_notify_client_event(
|
||||||
|
@ -785,7 +885,7 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
state_to_include_ids = [
|
state_to_include_ids = [
|
||||||
e_id
|
e_id
|
||||||
for k, e_id in context.current_state_ids.iteritems()
|
for k, e_id in iteritems(context.current_state_ids)
|
||||||
if k[0] in self.hs.config.room_invite_state_types
|
if k[0] in self.hs.config.room_invite_state_types
|
||||||
or k == (EventTypes.Member, event.sender)
|
or k == (EventTypes.Member, event.sender)
|
||||||
]
|
]
|
||||||
|
@ -799,7 +899,7 @@ class EventCreationHandler(object):
|
||||||
"content": e.content,
|
"content": e.content,
|
||||||
"sender": e.sender,
|
"sender": e.sender,
|
||||||
}
|
}
|
||||||
for e in state_to_include.itervalues()
|
for e in itervalues(state_to_include)
|
||||||
]
|
]
|
||||||
|
|
||||||
invitee = UserID.from_string(event.state_key)
|
invitee = UserID.from_string(event.state_key)
|
||||||
|
@ -854,22 +954,33 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
# this intentionally does not yield: we don't care about the result
|
# this intentionally does not yield: we don't care about the result
|
||||||
# and don't need to wait for it.
|
# and don't need to wait for it.
|
||||||
preserve_fn(self.pusher_pool.on_new_notifications)(
|
run_in_background(
|
||||||
|
self.pusher_pool.on_new_notifications,
|
||||||
event_stream_id, max_stream_id
|
event_stream_id, max_stream_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _notify():
|
def _notify():
|
||||||
yield run_on_reactor()
|
yield run_on_reactor()
|
||||||
|
try:
|
||||||
self.notifier.on_new_room_event(
|
self.notifier.on_new_room_event(
|
||||||
event, event_stream_id, max_stream_id,
|
event, event_stream_id, max_stream_id,
|
||||||
extra_users=extra_users
|
extra_users=extra_users
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifying about new room event")
|
||||||
|
|
||||||
preserve_fn(_notify)()
|
run_in_background(_notify)
|
||||||
|
|
||||||
if event.type == EventTypes.Message:
|
if event.type == EventTypes.Message:
|
||||||
presence = self.hs.get_presence_handler()
|
|
||||||
# We don't want to block sending messages on any presence code. This
|
# We don't want to block sending messages on any presence code. This
|
||||||
# matters as sometimes presence code can take a while.
|
# matters as sometimes presence code can take a while.
|
||||||
preserve_fn(presence.bump_presence_active_time)(requester.user)
|
run_in_background(self._bump_active_time, requester.user)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _bump_active_time(self, user):
|
||||||
|
try:
|
||||||
|
presence = self.hs.get_presence_handler()
|
||||||
|
yield presence.bump_presence_active_time(user)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error bumping presence active time")
|
||||||
|
|
|
@ -25,38 +25,42 @@ The methods that define policy are:
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.api.constants import PresenceState
|
from synapse.api.constants import PresenceState
|
||||||
from synapse.storage.presence import UserPresenceState
|
from synapse.storage.presence import UserPresenceState
|
||||||
|
|
||||||
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
||||||
from synapse.util.async import Linearizer
|
from synapse.util.async import Linearizer
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.util.wheel_timer import WheelTimer
|
from synapse.util.wheel_timer import WheelTimer
|
||||||
from synapse.types import UserID, get_domain_from_id
|
from synapse.types import UserID, get_domain_from_id
|
||||||
import synapse.metrics
|
from synapse.metrics import LaterGauge
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
|
||||||
|
|
||||||
notified_presence_counter = metrics.register_counter("notified_presence")
|
notified_presence_counter = Counter("synapse_handler_presence_notified_presence", "")
|
||||||
federation_presence_out_counter = metrics.register_counter("federation_presence_out")
|
federation_presence_out_counter = Counter(
|
||||||
presence_updates_counter = metrics.register_counter("presence_updates")
|
"synapse_handler_presence_federation_presence_out", "")
|
||||||
timers_fired_counter = metrics.register_counter("timers_fired")
|
presence_updates_counter = Counter("synapse_handler_presence_presence_updates", "")
|
||||||
federation_presence_counter = metrics.register_counter("federation_presence")
|
timers_fired_counter = Counter("synapse_handler_presence_timers_fired", "")
|
||||||
bump_active_time_counter = metrics.register_counter("bump_active_time")
|
federation_presence_counter = Counter("synapse_handler_presence_federation_presence", "")
|
||||||
|
bump_active_time_counter = Counter("synapse_handler_presence_bump_active_time", "")
|
||||||
|
|
||||||
get_updates_counter = metrics.register_counter("get_updates", labels=["type"])
|
get_updates_counter = Counter("synapse_handler_presence_get_updates", "", ["type"])
|
||||||
|
|
||||||
notify_reason_counter = metrics.register_counter("notify_reason", labels=["reason"])
|
notify_reason_counter = Counter(
|
||||||
state_transition_counter = metrics.register_counter(
|
"synapse_handler_presence_notify_reason", "", ["reason"])
|
||||||
"state_transition", labels=["from", "to"]
|
state_transition_counter = Counter(
|
||||||
|
"synapse_handler_presence_state_transition", "", ["from", "to"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,6 +91,11 @@ assert LAST_ACTIVE_GRANULARITY < IDLE_TIMER
|
||||||
class PresenceHandler(object):
|
class PresenceHandler(object):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
self.is_mine = hs.is_mine
|
self.is_mine = hs.is_mine
|
||||||
self.is_mine_id = hs.is_mine_id
|
self.is_mine_id = hs.is_mine_id
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
@ -94,7 +103,6 @@ class PresenceHandler(object):
|
||||||
self.wheel_timer = WheelTimer()
|
self.wheel_timer = WheelTimer()
|
||||||
self.notifier = hs.get_notifier()
|
self.notifier = hs.get_notifier()
|
||||||
self.federation = hs.get_federation_sender()
|
self.federation = hs.get_federation_sender()
|
||||||
|
|
||||||
self.state = hs.get_state_handler()
|
self.state = hs.get_state_handler()
|
||||||
|
|
||||||
federation_registry = hs.get_federation_registry()
|
federation_registry = hs.get_federation_registry()
|
||||||
|
@ -137,8 +145,9 @@ class PresenceHandler(object):
|
||||||
for state in active_presence
|
for state in active_presence
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.register_callback(
|
LaterGauge(
|
||||||
"user_to_current_state_size", lambda: len(self.user_to_current_state)
|
"synapse_handlers_presence_user_to_current_state_size", "", [],
|
||||||
|
lambda: len(self.user_to_current_state)
|
||||||
)
|
)
|
||||||
|
|
||||||
now = self.clock.time_msec()
|
now = self.clock.time_msec()
|
||||||
|
@ -208,7 +217,8 @@ class PresenceHandler(object):
|
||||||
60 * 1000,
|
60 * 1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
metrics.register_callback("wheel_timer_size", lambda: len(self.wheel_timer))
|
LaterGauge("synapse_handlers_presence_wheel_timer_size", "", [],
|
||||||
|
lambda: len(self.wheel_timer))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _on_shutdown(self):
|
def _on_shutdown(self):
|
||||||
|
@ -254,6 +264,14 @@ class PresenceHandler(object):
|
||||||
|
|
||||||
logger.info("Finished _persist_unpersisted_changes")
|
logger.info("Finished _persist_unpersisted_changes")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _update_states_and_catch_exception(self, new_states):
|
||||||
|
try:
|
||||||
|
res = yield self._update_states(new_states)
|
||||||
|
defer.returnValue(res)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error updating presence")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _update_states(self, new_states):
|
def _update_states(self, new_states):
|
||||||
"""Updates presence of users. Sets the appropriate timeouts. Pokes
|
"""Updates presence of users. Sets the appropriate timeouts. Pokes
|
||||||
|
@ -303,11 +321,11 @@ class PresenceHandler(object):
|
||||||
|
|
||||||
# TODO: We should probably ensure there are no races hereafter
|
# TODO: We should probably ensure there are no races hereafter
|
||||||
|
|
||||||
presence_updates_counter.inc_by(len(new_states))
|
presence_updates_counter.inc(len(new_states))
|
||||||
|
|
||||||
if to_notify:
|
if to_notify:
|
||||||
notified_presence_counter.inc_by(len(to_notify))
|
notified_presence_counter.inc(len(to_notify))
|
||||||
yield self._persist_and_notify(to_notify.values())
|
yield self._persist_and_notify(list(to_notify.values()))
|
||||||
|
|
||||||
self.unpersisted_users_changes |= set(s.user_id for s in new_states)
|
self.unpersisted_users_changes |= set(s.user_id for s in new_states)
|
||||||
self.unpersisted_users_changes -= set(to_notify.keys())
|
self.unpersisted_users_changes -= set(to_notify.keys())
|
||||||
|
@ -317,7 +335,7 @@ class PresenceHandler(object):
|
||||||
if user_id not in to_notify
|
if user_id not in to_notify
|
||||||
}
|
}
|
||||||
if to_federation_ping:
|
if to_federation_ping:
|
||||||
federation_presence_out_counter.inc_by(len(to_federation_ping))
|
federation_presence_out_counter.inc(len(to_federation_ping))
|
||||||
|
|
||||||
self._push_to_remotes(to_federation_ping.values())
|
self._push_to_remotes(to_federation_ping.values())
|
||||||
|
|
||||||
|
@ -355,7 +373,7 @@ class PresenceHandler(object):
|
||||||
for user_id in users_to_check
|
for user_id in users_to_check
|
||||||
]
|
]
|
||||||
|
|
||||||
timers_fired_counter.inc_by(len(states))
|
timers_fired_counter.inc(len(states))
|
||||||
|
|
||||||
changes = handle_timeouts(
|
changes = handle_timeouts(
|
||||||
states,
|
states,
|
||||||
|
@ -364,7 +382,7 @@ class PresenceHandler(object):
|
||||||
now=now,
|
now=now,
|
||||||
)
|
)
|
||||||
|
|
||||||
preserve_fn(self._update_states)(changes)
|
run_in_background(self._update_states_and_catch_exception, changes)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Exception in _handle_timeouts loop")
|
logger.exception("Exception in _handle_timeouts loop")
|
||||||
|
|
||||||
|
@ -422,20 +440,23 @@ class PresenceHandler(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _end():
|
def _end():
|
||||||
if affect_presence:
|
try:
|
||||||
self.user_to_num_current_syncs[user_id] -= 1
|
self.user_to_num_current_syncs[user_id] -= 1
|
||||||
|
|
||||||
prev_state = yield self.current_state_for_user(user_id)
|
prev_state = yield self.current_state_for_user(user_id)
|
||||||
yield self._update_states([prev_state.copy_and_replace(
|
yield self._update_states([prev_state.copy_and_replace(
|
||||||
last_user_sync_ts=self.clock.time_msec(),
|
last_user_sync_ts=self.clock.time_msec(),
|
||||||
)])
|
)])
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error updating presence after sync")
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _user_syncing():
|
def _user_syncing():
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
preserve_fn(_end)()
|
if affect_presence:
|
||||||
|
run_in_background(_end)
|
||||||
|
|
||||||
defer.returnValue(_user_syncing())
|
defer.returnValue(_user_syncing())
|
||||||
|
|
||||||
|
@ -452,61 +473,6 @@ class PresenceHandler(object):
|
||||||
syncing_user_ids.update(user_ids)
|
syncing_user_ids.update(user_ids)
|
||||||
return syncing_user_ids
|
return syncing_user_ids
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def update_external_syncs(self, process_id, syncing_user_ids):
|
|
||||||
"""Update the syncing users for an external process
|
|
||||||
|
|
||||||
Args:
|
|
||||||
process_id(str): An identifier for the process the users are
|
|
||||||
syncing against. This allows synapse to process updates
|
|
||||||
as user start and stop syncing against a given process.
|
|
||||||
syncing_user_ids(set(str)): The set of user_ids that are
|
|
||||||
currently syncing on that server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Grab the previous list of user_ids that were syncing on that process
|
|
||||||
prev_syncing_user_ids = (
|
|
||||||
self.external_process_to_current_syncs.get(process_id, set())
|
|
||||||
)
|
|
||||||
# Grab the current presence state for both the users that are syncing
|
|
||||||
# now and the users that were syncing before this update.
|
|
||||||
prev_states = yield self.current_state_for_users(
|
|
||||||
syncing_user_ids | prev_syncing_user_ids
|
|
||||||
)
|
|
||||||
updates = []
|
|
||||||
time_now_ms = self.clock.time_msec()
|
|
||||||
|
|
||||||
# For each new user that is syncing check if we need to mark them as
|
|
||||||
# being online.
|
|
||||||
for new_user_id in syncing_user_ids - prev_syncing_user_ids:
|
|
||||||
prev_state = prev_states[new_user_id]
|
|
||||||
if prev_state.state == PresenceState.OFFLINE:
|
|
||||||
updates.append(prev_state.copy_and_replace(
|
|
||||||
state=PresenceState.ONLINE,
|
|
||||||
last_active_ts=time_now_ms,
|
|
||||||
last_user_sync_ts=time_now_ms,
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
updates.append(prev_state.copy_and_replace(
|
|
||||||
last_user_sync_ts=time_now_ms,
|
|
||||||
))
|
|
||||||
|
|
||||||
# For each user that is still syncing or stopped syncing update the
|
|
||||||
# last sync time so that we will correctly apply the grace period when
|
|
||||||
# they stop syncing.
|
|
||||||
for old_user_id in prev_syncing_user_ids:
|
|
||||||
prev_state = prev_states[old_user_id]
|
|
||||||
updates.append(prev_state.copy_and_replace(
|
|
||||||
last_user_sync_ts=time_now_ms,
|
|
||||||
))
|
|
||||||
|
|
||||||
yield self._update_states(updates)
|
|
||||||
|
|
||||||
# Update the last updated time for the process. We expire the entries
|
|
||||||
# if we don't receive an update in the given timeframe.
|
|
||||||
self.external_process_last_updated_ms[process_id] = self.clock.time_msec()
|
|
||||||
self.external_process_to_current_syncs[process_id] = syncing_user_ids
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def update_external_syncs_row(self, process_id, user_id, is_syncing, sync_time_msec):
|
def update_external_syncs_row(self, process_id, user_id, is_syncing, sync_time_msec):
|
||||||
"""Update the syncing users for an external process as a delta.
|
"""Update the syncing users for an external process as a delta.
|
||||||
|
@ -570,7 +536,7 @@ class PresenceHandler(object):
|
||||||
prev_state.copy_and_replace(
|
prev_state.copy_and_replace(
|
||||||
last_user_sync_ts=time_now_ms,
|
last_user_sync_ts=time_now_ms,
|
||||||
)
|
)
|
||||||
for prev_state in prev_states.itervalues()
|
for prev_state in itervalues(prev_states)
|
||||||
])
|
])
|
||||||
self.external_process_last_updated_ms.pop(process_id, None)
|
self.external_process_last_updated_ms.pop(process_id, None)
|
||||||
|
|
||||||
|
@ -593,14 +559,14 @@ class PresenceHandler(object):
|
||||||
for user_id in user_ids
|
for user_id in user_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
missing = [user_id for user_id, state in states.iteritems() if not state]
|
missing = [user_id for user_id, state in iteritems(states) if not state]
|
||||||
if missing:
|
if missing:
|
||||||
# There are things not in our in memory cache. Lets pull them out of
|
# There are things not in our in memory cache. Lets pull them out of
|
||||||
# the database.
|
# the database.
|
||||||
res = yield self.store.get_presence_for_users(missing)
|
res = yield self.store.get_presence_for_users(missing)
|
||||||
states.update(res)
|
states.update(res)
|
||||||
|
|
||||||
missing = [user_id for user_id, state in states.iteritems() if not state]
|
missing = [user_id for user_id, state in iteritems(states) if not state]
|
||||||
if missing:
|
if missing:
|
||||||
new = {
|
new = {
|
||||||
user_id: UserPresenceState.default(user_id)
|
user_id: UserPresenceState.default(user_id)
|
||||||
|
@ -696,7 +662,7 @@ class PresenceHandler(object):
|
||||||
updates.append(prev_state.copy_and_replace(**new_fields))
|
updates.append(prev_state.copy_and_replace(**new_fields))
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
federation_presence_counter.inc_by(len(updates))
|
federation_presence_counter.inc(len(updates))
|
||||||
yield self._update_states(updates)
|
yield self._update_states(updates)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -721,7 +687,7 @@ class PresenceHandler(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
updates = yield self.current_state_for_users(target_user_ids)
|
updates = yield self.current_state_for_users(target_user_ids)
|
||||||
updates = updates.values()
|
updates = list(updates.values())
|
||||||
|
|
||||||
for user_id in set(target_user_ids) - set(u.user_id for u in updates):
|
for user_id in set(target_user_ids) - set(u.user_id for u in updates):
|
||||||
updates.append(UserPresenceState.default(user_id))
|
updates.append(UserPresenceState.default(user_id))
|
||||||
|
@ -787,11 +753,11 @@ class PresenceHandler(object):
|
||||||
self._push_to_remotes([state])
|
self._push_to_remotes([state])
|
||||||
else:
|
else:
|
||||||
user_ids = yield self.store.get_users_in_room(room_id)
|
user_ids = yield self.store.get_users_in_room(room_id)
|
||||||
user_ids = filter(self.is_mine_id, user_ids)
|
user_ids = list(filter(self.is_mine_id, user_ids))
|
||||||
|
|
||||||
states = yield self.current_state_for_users(user_ids)
|
states = yield self.current_state_for_users(user_ids)
|
||||||
|
|
||||||
self._push_to_remotes(states.values())
|
self._push_to_remotes(list(states.values()))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_presence_list(self, observer_user, accepted=None):
|
def get_presence_list(self, observer_user, accepted=None):
|
||||||
|
@ -971,28 +937,28 @@ def should_notify(old_state, new_state):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if old_state.status_msg != new_state.status_msg:
|
if old_state.status_msg != new_state.status_msg:
|
||||||
notify_reason_counter.inc("status_msg_change")
|
notify_reason_counter.labels("status_msg_change").inc()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if old_state.state != new_state.state:
|
if old_state.state != new_state.state:
|
||||||
notify_reason_counter.inc("state_change")
|
notify_reason_counter.labels("state_change").inc()
|
||||||
state_transition_counter.inc(old_state.state, new_state.state)
|
state_transition_counter.labels(old_state.state, new_state.state).inc()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if old_state.state == PresenceState.ONLINE:
|
if old_state.state == PresenceState.ONLINE:
|
||||||
if new_state.currently_active != old_state.currently_active:
|
if new_state.currently_active != old_state.currently_active:
|
||||||
notify_reason_counter.inc("current_active_change")
|
notify_reason_counter.labels("current_active_change").inc()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if new_state.last_active_ts - old_state.last_active_ts > LAST_ACTIVE_GRANULARITY:
|
if new_state.last_active_ts - old_state.last_active_ts > LAST_ACTIVE_GRANULARITY:
|
||||||
# Only notify about last active bumps if we're not currently acive
|
# Only notify about last active bumps if we're not currently acive
|
||||||
if not new_state.currently_active:
|
if not new_state.currently_active:
|
||||||
notify_reason_counter.inc("last_active_change_online")
|
notify_reason_counter.labels("last_active_change_online").inc()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif new_state.last_active_ts - old_state.last_active_ts > LAST_ACTIVE_GRANULARITY:
|
elif new_state.last_active_ts - old_state.last_active_ts > LAST_ACTIVE_GRANULARITY:
|
||||||
# Always notify for a transition where last active gets bumped.
|
# Always notify for a transition where last active gets bumped.
|
||||||
notify_reason_counter.inc("last_active_change_not_online")
|
notify_reason_counter.labels("last_active_change_not_online").inc()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -1066,14 +1032,14 @@ class PresenceEventSource(object):
|
||||||
if changed is not None and len(changed) < 500:
|
if changed is not None and len(changed) < 500:
|
||||||
# For small deltas, its quicker to get all changes and then
|
# For small deltas, its quicker to get all changes and then
|
||||||
# work out if we share a room or they're in our presence list
|
# work out if we share a room or they're in our presence list
|
||||||
get_updates_counter.inc("stream")
|
get_updates_counter.labels("stream").inc()
|
||||||
for other_user_id in changed:
|
for other_user_id in changed:
|
||||||
if other_user_id in users_interested_in:
|
if other_user_id in users_interested_in:
|
||||||
user_ids_changed.add(other_user_id)
|
user_ids_changed.add(other_user_id)
|
||||||
else:
|
else:
|
||||||
# Too many possible updates. Find all users we can see and check
|
# Too many possible updates. Find all users we can see and check
|
||||||
# if any of them have changed.
|
# if any of them have changed.
|
||||||
get_updates_counter.inc("full")
|
get_updates_counter.labels("full").inc()
|
||||||
|
|
||||||
if from_key:
|
if from_key:
|
||||||
user_ids_changed = stream_change_cache.get_entities_changed(
|
user_ids_changed = stream_change_cache.get_entities_changed(
|
||||||
|
@ -1085,10 +1051,10 @@ class PresenceEventSource(object):
|
||||||
updates = yield presence.current_state_for_users(user_ids_changed)
|
updates = yield presence.current_state_for_users(user_ids_changed)
|
||||||
|
|
||||||
if include_offline:
|
if include_offline:
|
||||||
defer.returnValue((updates.values(), max_token))
|
defer.returnValue((list(updates.values()), max_token))
|
||||||
else:
|
else:
|
||||||
defer.returnValue(([
|
defer.returnValue(([
|
||||||
s for s in updates.itervalues()
|
s for s in itervalues(updates)
|
||||||
if s.state != PresenceState.OFFLINE
|
if s.state != PresenceState.OFFLINE
|
||||||
], max_token))
|
], max_token))
|
||||||
|
|
||||||
|
@ -1146,7 +1112,7 @@ def handle_timeouts(user_states, is_mine_fn, syncing_user_ids, now):
|
||||||
if new_state:
|
if new_state:
|
||||||
changes[state.user_id] = new_state
|
changes[state.user_id] = new_state
|
||||||
|
|
||||||
return changes.values()
|
return list(changes.values())
|
||||||
|
|
||||||
|
|
||||||
def handle_timeout(state, is_mine, syncing_user_ids, now):
|
def handle_timeout(state, is_mine, syncing_user_ids, now):
|
||||||
|
@ -1345,11 +1311,11 @@ def get_interested_remotes(store, states, state_handler):
|
||||||
# hosts in those rooms.
|
# hosts in those rooms.
|
||||||
room_ids_to_states, users_to_states = yield get_interested_parties(store, states)
|
room_ids_to_states, users_to_states = yield get_interested_parties(store, states)
|
||||||
|
|
||||||
for room_id, states in room_ids_to_states.iteritems():
|
for room_id, states in iteritems(room_ids_to_states):
|
||||||
hosts = yield state_handler.get_current_hosts_in_room(room_id)
|
hosts = yield state_handler.get_current_hosts_in_room(room_id)
|
||||||
hosts_and_states.append((hosts, states))
|
hosts_and_states.append((hosts, states))
|
||||||
|
|
||||||
for user_id, states in users_to_states.iteritems():
|
for user_id, states in iteritems(users_to_states):
|
||||||
host = get_domain_from_id(user_id)
|
host = get_domain_from_id(user_id)
|
||||||
hosts_and_states.append(([host], states))
|
hosts_and_states.append(([host], states))
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,7 @@ class ReceiptsHandler(BaseHandler):
|
||||||
"""Given a list of receipts, works out which remote servers should be
|
"""Given a list of receipts, works out which remote servers should be
|
||||||
poked and pokes them.
|
poked and pokes them.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
# TODO: Some of this stuff should be coallesced.
|
# TODO: Some of this stuff should be coallesced.
|
||||||
for receipt in receipts:
|
for receipt in receipts:
|
||||||
room_id = receipt["room_id"]
|
room_id = receipt["room_id"]
|
||||||
|
@ -166,6 +167,8 @@ class ReceiptsHandler(BaseHandler):
|
||||||
},
|
},
|
||||||
key=(room_id, receipt_type, user_id),
|
key=(room_id, receipt_type, user_id),
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error pushing receipts to remote servers")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_receipts_for_room(self, room_id, to_key):
|
def get_receipts_for_room(self, room_id, to_key):
|
||||||
|
|
|
@ -34,6 +34,11 @@ logger = logging.getLogger(__name__)
|
||||||
class RegistrationHandler(BaseHandler):
|
class RegistrationHandler(BaseHandler):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
super(RegistrationHandler, self).__init__(hs)
|
super(RegistrationHandler, self).__init__(hs)
|
||||||
|
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
@ -49,6 +54,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
self._generate_user_id_linearizer = Linearizer(
|
self._generate_user_id_linearizer = Linearizer(
|
||||||
name="_generate_user_id_linearizer",
|
name="_generate_user_id_linearizer",
|
||||||
)
|
)
|
||||||
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_username(self, localpart, guest_access_token=None,
|
def check_username(self, localpart, guest_access_token=None,
|
||||||
|
@ -360,6 +366,14 @@ class RegistrationHandler(BaseHandler):
|
||||||
yield identity_handler.bind_threepid(c, user_id)
|
yield identity_handler.bind_threepid(c, user_id)
|
||||||
|
|
||||||
def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
|
def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
|
||||||
|
# don't allow people to register the server notices mxid
|
||||||
|
if self._server_notices_mxid is not None:
|
||||||
|
if user_id == self._server_notices_mxid:
|
||||||
|
raise SynapseError(
|
||||||
|
400, "This user ID is reserved.",
|
||||||
|
errcode=Codes.EXCLUSIVE
|
||||||
|
)
|
||||||
|
|
||||||
# valid user IDs must not clash with any user ID namespaces claimed by
|
# valid user IDs must not clash with any user ID namespaces claimed by
|
||||||
# application services.
|
# application services.
|
||||||
services = self.store.get_app_services()
|
services = self.store.get_app_services()
|
||||||
|
|
|
@ -70,14 +70,27 @@ class RoomCreationHandler(BaseHandler):
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_room(self, requester, config, ratelimit=True):
|
def create_room(self, requester, config, ratelimit=True,
|
||||||
|
creator_join_profile=None):
|
||||||
""" Creates a new room.
|
""" Creates a new room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
requester (Requester): The user who requested the room creation.
|
requester (synapse.types.Requester):
|
||||||
|
The user who requested the room creation.
|
||||||
config (dict) : A dict of configuration options.
|
config (dict) : A dict of configuration options.
|
||||||
|
ratelimit (bool): set to False to disable the rate limiter
|
||||||
|
|
||||||
|
creator_join_profile (dict|None):
|
||||||
|
Set to override the displayname and avatar for the creating
|
||||||
|
user in this room. If unset, displayname and avatar will be
|
||||||
|
derived from the user's profile. If set, should contain the
|
||||||
|
values to go in the body of the 'join' event (typically
|
||||||
|
`avatar_url` and/or `displayname`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The new room ID.
|
Deferred[dict]:
|
||||||
|
a dict containing the keys `room_id` and, if an alias was
|
||||||
|
requested, `room_alias`.
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError if the room ID couldn't be stored, or something went
|
SynapseError if the room ID couldn't be stored, or something went
|
||||||
horribly wrong.
|
horribly wrong.
|
||||||
|
@ -115,6 +128,10 @@ class RoomCreationHandler(BaseHandler):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
||||||
|
|
||||||
|
yield self.event_creation_handler.assert_accepted_privacy_policy(
|
||||||
|
requester,
|
||||||
|
)
|
||||||
|
|
||||||
invite_3pid_list = config.get("invite_3pid", [])
|
invite_3pid_list = config.get("invite_3pid", [])
|
||||||
|
|
||||||
visibility = config.get("visibility", None)
|
visibility = config.get("visibility", None)
|
||||||
|
@ -178,7 +195,8 @@ class RoomCreationHandler(BaseHandler):
|
||||||
initial_state=initial_state,
|
initial_state=initial_state,
|
||||||
creation_content=creation_content,
|
creation_content=creation_content,
|
||||||
room_alias=room_alias,
|
room_alias=room_alias,
|
||||||
power_level_content_override=config.get("power_level_content_override", {})
|
power_level_content_override=config.get("power_level_content_override", {}),
|
||||||
|
creator_join_profile=creator_join_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
if "name" in config:
|
if "name" in config:
|
||||||
|
@ -258,6 +276,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
creation_content,
|
creation_content,
|
||||||
room_alias,
|
room_alias,
|
||||||
power_level_content_override,
|
power_level_content_override,
|
||||||
|
creator_join_profile,
|
||||||
):
|
):
|
||||||
def create(etype, content, **kwargs):
|
def create(etype, content, **kwargs):
|
||||||
e = {
|
e = {
|
||||||
|
@ -301,6 +320,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
room_id,
|
room_id,
|
||||||
"join",
|
"join",
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
|
content=creator_join_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
# We treat the power levels override specially as this needs to be one
|
# We treat the power levels override specially as this needs to be one
|
||||||
|
@ -446,7 +466,7 @@ class RoomContextHandler(BaseHandler):
|
||||||
state = yield self.store.get_state_for_events(
|
state = yield self.store.get_state_for_events(
|
||||||
[last_event_id], None
|
[last_event_id], None
|
||||||
)
|
)
|
||||||
results["state"] = state[last_event_id].values()
|
results["state"] = list(state[last_event_id].values())
|
||||||
|
|
||||||
results["start"] = now_token.copy_and_replace(
|
results["start"] = now_token.copy_and_replace(
|
||||||
"room_key", results["start"]
|
"room_key", results["start"]
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
from six.moves import range
|
||||||
|
|
||||||
from ._base import BaseHandler
|
from ._base import BaseHandler
|
||||||
|
|
||||||
from synapse.api.constants import (
|
from synapse.api.constants import (
|
||||||
|
@ -200,7 +203,7 @@ class RoomListHandler(BaseHandler):
|
||||||
step = len(rooms_to_scan) if len(rooms_to_scan) != 0 else 1
|
step = len(rooms_to_scan) if len(rooms_to_scan) != 0 else 1
|
||||||
|
|
||||||
chunk = []
|
chunk = []
|
||||||
for i in xrange(0, len(rooms_to_scan), step):
|
for i in range(0, len(rooms_to_scan), step):
|
||||||
batch = rooms_to_scan[i:i + step]
|
batch = rooms_to_scan[i:i + step]
|
||||||
logger.info("Processing %i rooms for result", len(batch))
|
logger.info("Processing %i rooms for result", len(batch))
|
||||||
yield concurrently_execute(
|
yield concurrently_execute(
|
||||||
|
@ -305,7 +308,7 @@ class RoomListHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
event_map = yield self.store.get_events([
|
event_map = yield self.store.get_events([
|
||||||
event_id for key, event_id in current_state_ids.iteritems()
|
event_id for key, event_id in iteritems(current_state_ids)
|
||||||
if key[0] in (
|
if key[0] in (
|
||||||
EventTypes.JoinRules,
|
EventTypes.JoinRules,
|
||||||
EventTypes.Name,
|
EventTypes.Name,
|
||||||
|
|
|
@ -17,11 +17,14 @@
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
from signedjson.key import decode_verify_key_bytes
|
from signedjson.key import decode_verify_key_bytes
|
||||||
from signedjson.sign import verify_signed_json
|
from signedjson.sign import verify_signed_json
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
|
|
||||||
|
import synapse.server
|
||||||
import synapse.types
|
import synapse.types
|
||||||
from synapse.api.constants import (
|
from synapse.api.constants import (
|
||||||
EventTypes, Membership,
|
EventTypes, Membership,
|
||||||
|
@ -46,6 +49,11 @@ class RoomMemberHandler(object):
|
||||||
__metaclass__ = abc.ABCMeta
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hs (synapse.server.HomeServer):
|
||||||
|
"""
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
@ -63,6 +71,7 @@ class RoomMemberHandler(object):
|
||||||
|
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.spam_checker = hs.get_spam_checker()
|
self.spam_checker = hs.get_spam_checker()
|
||||||
|
self._server_notices_mxid = self.config.server_notices_mxid
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
|
def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
|
||||||
|
@ -290,11 +299,26 @@ class RoomMemberHandler(object):
|
||||||
if is_blocked:
|
if is_blocked:
|
||||||
raise SynapseError(403, "This room has been blocked on this server")
|
raise SynapseError(403, "This room has been blocked on this server")
|
||||||
|
|
||||||
if effective_membership_state == "invite":
|
if effective_membership_state == Membership.INVITE:
|
||||||
|
# block any attempts to invite the server notices mxid
|
||||||
|
if target.to_string() == self._server_notices_mxid:
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.FORBIDDEN,
|
||||||
|
"Cannot invite this user",
|
||||||
|
)
|
||||||
|
|
||||||
block_invite = False
|
block_invite = False
|
||||||
|
|
||||||
|
if (self._server_notices_mxid is not None and
|
||||||
|
requester.user.to_string() == self._server_notices_mxid):
|
||||||
|
# allow the server notices mxid to send invites
|
||||||
|
is_requester_admin = True
|
||||||
|
|
||||||
|
else:
|
||||||
is_requester_admin = yield self.auth.is_server_admin(
|
is_requester_admin = yield self.auth.is_server_admin(
|
||||||
requester.user,
|
requester.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not is_requester_admin:
|
if not is_requester_admin:
|
||||||
if self.config.block_non_admin_invites:
|
if self.config.block_non_admin_invites:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -349,6 +373,20 @@ class RoomMemberHandler(object):
|
||||||
if same_sender and same_membership and same_content:
|
if same_sender and same_membership and same_content:
|
||||||
defer.returnValue(old_state)
|
defer.returnValue(old_state)
|
||||||
|
|
||||||
|
# we don't allow people to reject invites to the server notice
|
||||||
|
# room, but they can leave it once they are joined.
|
||||||
|
if (
|
||||||
|
old_membership == Membership.INVITE and
|
||||||
|
effective_membership_state == Membership.LEAVE
|
||||||
|
):
|
||||||
|
is_blocked = yield self._is_server_notice_room(room_id)
|
||||||
|
if is_blocked:
|
||||||
|
raise SynapseError(
|
||||||
|
http_client.FORBIDDEN,
|
||||||
|
"You cannot reject this invite",
|
||||||
|
errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
|
||||||
|
)
|
||||||
|
|
||||||
is_host_in_room = yield self._is_host_in_room(current_state_ids)
|
is_host_in_room = yield self._is_host_in_room(current_state_ids)
|
||||||
|
|
||||||
if effective_membership_state == Membership.JOIN:
|
if effective_membership_state == Membership.JOIN:
|
||||||
|
@ -844,6 +882,13 @@ class RoomMemberHandler(object):
|
||||||
|
|
||||||
defer.returnValue(False)
|
defer.returnValue(False)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _is_server_notice_room(self, room_id):
|
||||||
|
if self._server_notices_mxid is None:
|
||||||
|
defer.returnValue(False)
|
||||||
|
user_ids = yield self.store.get_users_in_room(room_id)
|
||||||
|
defer.returnValue(self._server_notices_mxid in user_ids)
|
||||||
|
|
||||||
|
|
||||||
class RoomMemberMasterHandler(RoomMemberHandler):
|
class RoomMemberMasterHandler(RoomMemberHandler):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
|
|
@ -348,7 +348,7 @@ class SearchHandler(BaseHandler):
|
||||||
rooms = set(e.room_id for e in allowed_events)
|
rooms = set(e.room_id for e in allowed_events)
|
||||||
for room_id in rooms:
|
for room_id in rooms:
|
||||||
state = yield self.state_handler.get_current_state(room_id)
|
state = yield self.state_handler.get_current_state(room_id)
|
||||||
state_results[room_id] = state.values()
|
state_results[room_id] = list(state.values())
|
||||||
|
|
||||||
state_results.values()
|
state_results.values()
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ import collections
|
||||||
import logging
|
import logging
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
from six import itervalues, iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -275,7 +277,7 @@ class SyncHandler(object):
|
||||||
# result returned by the event source is poor form (it might cache
|
# result returned by the event source is poor form (it might cache
|
||||||
# the object)
|
# the object)
|
||||||
room_id = event["room_id"]
|
room_id = event["room_id"]
|
||||||
event_copy = {k: v for (k, v) in event.iteritems()
|
event_copy = {k: v for (k, v) in iteritems(event)
|
||||||
if k != "room_id"}
|
if k != "room_id"}
|
||||||
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
||||||
|
|
||||||
|
@ -294,7 +296,7 @@ class SyncHandler(object):
|
||||||
for event in receipts:
|
for event in receipts:
|
||||||
room_id = event["room_id"]
|
room_id = event["room_id"]
|
||||||
# exclude room id, as above
|
# exclude room id, as above
|
||||||
event_copy = {k: v for (k, v) in event.iteritems()
|
event_copy = {k: v for (k, v) in iteritems(event)
|
||||||
if k != "room_id"}
|
if k != "room_id"}
|
||||||
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
ephemeral_by_room.setdefault(room_id, []).append(event_copy)
|
||||||
|
|
||||||
|
@ -325,7 +327,7 @@ class SyncHandler(object):
|
||||||
current_state_ids = frozenset()
|
current_state_ids = frozenset()
|
||||||
if any(e.is_state() for e in recents):
|
if any(e.is_state() for e in recents):
|
||||||
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
||||||
current_state_ids = frozenset(current_state_ids.itervalues())
|
current_state_ids = frozenset(itervalues(current_state_ids))
|
||||||
|
|
||||||
recents = yield filter_events_for_client(
|
recents = yield filter_events_for_client(
|
||||||
self.store,
|
self.store,
|
||||||
|
@ -354,12 +356,24 @@ class SyncHandler(object):
|
||||||
since_key = since_token.room_key
|
since_key = since_token.room_key
|
||||||
|
|
||||||
while limited and len(recents) < timeline_limit and max_repeat:
|
while limited and len(recents) < timeline_limit and max_repeat:
|
||||||
|
# If we have a since_key then we are trying to get any events
|
||||||
|
# that have happened since `since_key` up to `end_key`, so we
|
||||||
|
# can just use `get_room_events_stream_for_room`.
|
||||||
|
# Otherwise, we want to return the last N events in the room
|
||||||
|
# in toplogical ordering.
|
||||||
|
if since_key:
|
||||||
events, end_key = yield self.store.get_room_events_stream_for_room(
|
events, end_key = yield self.store.get_room_events_stream_for_room(
|
||||||
room_id,
|
room_id,
|
||||||
limit=load_limit + 1,
|
limit=load_limit + 1,
|
||||||
from_key=since_key,
|
from_key=since_key,
|
||||||
to_key=end_key,
|
to_key=end_key,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
events, end_key = yield self.store.get_recent_events_for_room(
|
||||||
|
room_id,
|
||||||
|
limit=load_limit + 1,
|
||||||
|
end_token=end_key,
|
||||||
|
)
|
||||||
loaded_recents = sync_config.filter_collection.filter_room_timeline(
|
loaded_recents = sync_config.filter_collection.filter_room_timeline(
|
||||||
events
|
events
|
||||||
)
|
)
|
||||||
|
@ -370,7 +384,7 @@ class SyncHandler(object):
|
||||||
current_state_ids = frozenset()
|
current_state_ids = frozenset()
|
||||||
if any(e.is_state() for e in loaded_recents):
|
if any(e.is_state() for e in loaded_recents):
|
||||||
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
current_state_ids = yield self.state.get_current_state_ids(room_id)
|
||||||
current_state_ids = frozenset(current_state_ids.itervalues())
|
current_state_ids = frozenset(itervalues(current_state_ids))
|
||||||
|
|
||||||
loaded_recents = yield filter_events_for_client(
|
loaded_recents = yield filter_events_for_client(
|
||||||
self.store,
|
self.store,
|
||||||
|
@ -429,7 +443,11 @@ class SyncHandler(object):
|
||||||
Returns:
|
Returns:
|
||||||
A Deferred map from ((type, state_key)->Event)
|
A Deferred map from ((type, state_key)->Event)
|
||||||
"""
|
"""
|
||||||
last_events, token = yield self.store.get_recent_events_for_room(
|
# FIXME this claims to get the state at a stream position, but
|
||||||
|
# get_recent_events_for_room operates by topo ordering. This therefore
|
||||||
|
# does not reliably give you the state at the given stream position.
|
||||||
|
# (https://github.com/matrix-org/synapse/issues/3305)
|
||||||
|
last_events, _ = yield self.store.get_recent_events_for_room(
|
||||||
room_id, end_token=stream_position.room_key, limit=1,
|
room_id, end_token=stream_position.room_key, limit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -523,11 +541,11 @@ class SyncHandler(object):
|
||||||
|
|
||||||
state = {}
|
state = {}
|
||||||
if state_ids:
|
if state_ids:
|
||||||
state = yield self.store.get_events(state_ids.values())
|
state = yield self.store.get_events(list(state_ids.values()))
|
||||||
|
|
||||||
defer.returnValue({
|
defer.returnValue({
|
||||||
(e.type, e.state_key): e
|
(e.type, e.state_key): e
|
||||||
for e in sync_config.filter_collection.filter_room_state(state.values())
|
for e in sync_config.filter_collection.filter_room_state(list(state.values()))
|
||||||
})
|
})
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -876,7 +894,7 @@ class SyncHandler(object):
|
||||||
presence.extend(states)
|
presence.extend(states)
|
||||||
|
|
||||||
# Deduplicate the presence entries so that there's at most one per user
|
# Deduplicate the presence entries so that there's at most one per user
|
||||||
presence = {p.user_id: p for p in presence}.values()
|
presence = list({p.user_id: p for p in presence}.values())
|
||||||
|
|
||||||
presence = sync_config.filter_collection.filter_presence(
|
presence = sync_config.filter_collection.filter_presence(
|
||||||
presence
|
presence
|
||||||
|
@ -972,7 +990,7 @@ class SyncHandler(object):
|
||||||
if since_token:
|
if since_token:
|
||||||
for joined_sync in sync_result_builder.joined:
|
for joined_sync in sync_result_builder.joined:
|
||||||
it = itertools.chain(
|
it = itertools.chain(
|
||||||
joined_sync.timeline.events, joined_sync.state.itervalues()
|
joined_sync.timeline.events, itervalues(joined_sync.state)
|
||||||
)
|
)
|
||||||
for event in it:
|
for event in it:
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
|
@ -1028,7 +1046,13 @@ class SyncHandler(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred(tuple): Returns a tuple of the form:
|
Deferred(tuple): Returns a tuple of the form:
|
||||||
`([RoomSyncResultBuilder], [InvitedSyncResult], newly_joined_rooms)`
|
`(room_entries, invited_rooms, newly_joined_rooms, newly_left_rooms)`
|
||||||
|
|
||||||
|
where:
|
||||||
|
room_entries is a list [RoomSyncResultBuilder]
|
||||||
|
invited_rooms is a list [InvitedSyncResult]
|
||||||
|
newly_joined rooms is a list[str] of room ids
|
||||||
|
newly_left_rooms is a list[str] of room ids
|
||||||
"""
|
"""
|
||||||
user_id = sync_result_builder.sync_config.user.to_string()
|
user_id = sync_result_builder.sync_config.user.to_string()
|
||||||
since_token = sync_result_builder.since_token
|
since_token = sync_result_builder.since_token
|
||||||
|
@ -1050,7 +1074,7 @@ class SyncHandler(object):
|
||||||
newly_left_rooms = []
|
newly_left_rooms = []
|
||||||
room_entries = []
|
room_entries = []
|
||||||
invited = []
|
invited = []
|
||||||
for room_id, events in mem_change_events_by_room_id.iteritems():
|
for room_id, events in iteritems(mem_change_events_by_room_id):
|
||||||
non_joins = [e for e in events if e.membership != Membership.JOIN]
|
non_joins = [e for e in events if e.membership != Membership.JOIN]
|
||||||
has_join = len(non_joins) != len(events)
|
has_join = len(non_joins) != len(events)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError, AuthError
|
from synapse.api.errors import SynapseError, AuthError
|
||||||
from synapse.util.logcontext import preserve_fn
|
from synapse.util.logcontext import run_in_background
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.util.wheel_timer import WheelTimer
|
from synapse.util.wheel_timer import WheelTimer
|
||||||
from synapse.types import UserID, get_domain_from_id
|
from synapse.types import UserID, get_domain_from_id
|
||||||
|
@ -97,7 +97,8 @@ class TypingHandler(object):
|
||||||
if self.hs.is_mine_id(member.user_id):
|
if self.hs.is_mine_id(member.user_id):
|
||||||
last_fed_poke = self._member_last_federation_poke.get(member, None)
|
last_fed_poke = self._member_last_federation_poke.get(member, None)
|
||||||
if not last_fed_poke or last_fed_poke + FEDERATION_PING_INTERVAL <= now:
|
if not last_fed_poke or last_fed_poke + FEDERATION_PING_INTERVAL <= now:
|
||||||
preserve_fn(self._push_remote)(
|
run_in_background(
|
||||||
|
self._push_remote,
|
||||||
member=member,
|
member=member,
|
||||||
typing=True
|
typing=True
|
||||||
)
|
)
|
||||||
|
@ -196,7 +197,7 @@ class TypingHandler(object):
|
||||||
def _push_update(self, member, typing):
|
def _push_update(self, member, typing):
|
||||||
if self.hs.is_mine_id(member.user_id):
|
if self.hs.is_mine_id(member.user_id):
|
||||||
# Only send updates for changes to our own users.
|
# Only send updates for changes to our own users.
|
||||||
preserve_fn(self._push_remote)(member, typing)
|
run_in_background(self._push_remote, member, typing)
|
||||||
|
|
||||||
self._push_update_local(
|
self._push_update_local(
|
||||||
member=member,
|
member=member,
|
||||||
|
@ -205,6 +206,7 @@ class TypingHandler(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _push_remote(self, member, typing):
|
def _push_remote(self, member, typing):
|
||||||
|
try:
|
||||||
users = yield self.state.get_current_user_in_room(member.room_id)
|
users = yield self.state.get_current_user_in_room(member.room_id)
|
||||||
self._member_last_federation_poke[member] = self.clock.time_msec()
|
self._member_last_federation_poke[member] = self.clock.time_msec()
|
||||||
|
|
||||||
|
@ -227,6 +229,8 @@ class TypingHandler(object):
|
||||||
},
|
},
|
||||||
key=member,
|
key=member,
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error pushing typing notif to remotes")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _recv_edu(self, origin, content):
|
def _recv_edu(self, origin, content):
|
||||||
|
|
|
@ -22,6 +22,7 @@ from synapse.util.metrics import Measure
|
||||||
from synapse.util.async import sleep
|
from synapse.util.async import sleep
|
||||||
from synapse.types import get_localpart_from_id
|
from synapse.types import get_localpart_from_id
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -122,6 +123,13 @@ class UserDirectoryHandler(object):
|
||||||
user_id, profile.display_name, profile.avatar_url, None,
|
user_id, profile.display_name, profile.avatar_url, None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def handle_user_deactivated(self, user_id):
|
||||||
|
"""Called when a user ID is deactivated
|
||||||
|
"""
|
||||||
|
yield self.store.remove_from_user_dir(user_id)
|
||||||
|
yield self.store.remove_from_user_in_public_room(user_id)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _unsafe_process(self):
|
def _unsafe_process(self):
|
||||||
# If self.pos is None then means we haven't fetched it from DB
|
# If self.pos is None then means we haven't fetched it from DB
|
||||||
|
@ -403,7 +411,7 @@ class UserDirectoryHandler(object):
|
||||||
|
|
||||||
if change:
|
if change:
|
||||||
users_with_profile = yield self.state.get_current_user_in_room(room_id)
|
users_with_profile = yield self.state.get_current_user_in_room(room_id)
|
||||||
for user_id, profile in users_with_profile.iteritems():
|
for user_id, profile in iteritems(users_with_profile):
|
||||||
yield self._handle_new_user(room_id, user_id, profile)
|
yield self._handle_new_user(room_id, user_id, profile)
|
||||||
else:
|
else:
|
||||||
users = yield self.store.get_users_in_public_due_to_room(room_id)
|
users = yield self.store.get_users_in_public_due_to_room(room_id)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,3 +13,24 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
from twisted.internet.defer import CancelledError
|
||||||
|
from twisted.python import failure
|
||||||
|
|
||||||
|
from synapse.api.errors import SynapseError
|
||||||
|
|
||||||
|
|
||||||
|
class RequestTimedOutError(SynapseError):
|
||||||
|
"""Exception representing timeout of an outbound request"""
|
||||||
|
def __init__(self):
|
||||||
|
super(RequestTimedOutError, self).__init__(504, "Timed out")
|
||||||
|
|
||||||
|
|
||||||
|
def cancelled_to_request_timed_out_error(value, timeout):
|
||||||
|
"""Turns CancelledErrors into RequestTimedOutErrors.
|
||||||
|
|
||||||
|
For use with async.add_timeout_to_deferred
|
||||||
|
"""
|
||||||
|
if isinstance(value, failure.Failure):
|
||||||
|
value.trap(CancelledError)
|
||||||
|
raise RequestTimedOutError()
|
||||||
|
return value
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from synapse.http.server import wrap_request_handler
|
from synapse.http.server import wrap_json_request_handler
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
from twisted.web.server import NOT_DONE_YET
|
from twisted.web.server import NOT_DONE_YET
|
||||||
|
|
||||||
|
@ -42,14 +42,13 @@ class AdditionalResource(Resource):
|
||||||
Resource.__init__(self)
|
Resource.__init__(self)
|
||||||
self._handler = handler
|
self._handler = handler
|
||||||
|
|
||||||
# these are required by the request_handler wrapper
|
# required by the request_handler wrapper
|
||||||
self.version_string = hs.version_string
|
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
self._async_render(request)
|
self._async_render(request)
|
||||||
return NOT_DONE_YET
|
return NOT_DONE_YET
|
||||||
|
|
||||||
@wrap_request_handler
|
@wrap_json_request_handler
|
||||||
def _async_render(self, request):
|
def _async_render(self, request):
|
||||||
return self._handler(request)
|
return self._handler(request)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -18,10 +19,10 @@ from OpenSSL.SSL import VERIFY_NONE
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
CodeMessageException, MatrixCodeMessageException, SynapseError, Codes,
|
CodeMessageException, MatrixCodeMessageException, SynapseError, Codes,
|
||||||
)
|
)
|
||||||
|
from synapse.http import cancelled_to_request_timed_out_error
|
||||||
|
from synapse.util.async import add_timeout_to_deferred
|
||||||
from synapse.util.caches import CACHE_SIZE_FACTOR
|
from synapse.util.caches import CACHE_SIZE_FACTOR
|
||||||
from synapse.util.logcontext import make_deferred_yieldable
|
from synapse.util.logcontext import make_deferred_yieldable
|
||||||
from synapse.util import logcontext
|
|
||||||
import synapse.metrics
|
|
||||||
from synapse.http.endpoint import SpiderEndpoint
|
from synapse.http.endpoint import SpiderEndpoint
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
|
@ -38,8 +39,9 @@ from twisted.web.http import PotentialDataLoss
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web._newclient import ResponseDone
|
from twisted.web._newclient import ResponseDone
|
||||||
|
|
||||||
from StringIO import StringIO
|
from six import StringIO
|
||||||
|
|
||||||
|
from prometheus_client import Counter
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
import logging
|
import logging
|
||||||
import urllib
|
import urllib
|
||||||
|
@ -47,16 +49,9 @@ import urllib
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
outgoing_requests_counter = Counter("synapse_http_client_requests", "", ["method"])
|
||||||
|
incoming_responses_counter = Counter("synapse_http_client_responses", "",
|
||||||
outgoing_requests_counter = metrics.register_counter(
|
["method", "code"])
|
||||||
"requests",
|
|
||||||
labels=["method"],
|
|
||||||
)
|
|
||||||
incoming_responses_counter = metrics.register_counter(
|
|
||||||
"responses",
|
|
||||||
labels=["method", "code"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleHttpClient(object):
|
class SimpleHttpClient(object):
|
||||||
|
@ -93,32 +88,28 @@ class SimpleHttpClient(object):
|
||||||
def request(self, method, uri, *args, **kwargs):
|
def request(self, method, uri, *args, **kwargs):
|
||||||
# A small wrapper around self.agent.request() so we can easily attach
|
# A small wrapper around self.agent.request() so we can easily attach
|
||||||
# counters to it
|
# counters to it
|
||||||
outgoing_requests_counter.inc(method)
|
outgoing_requests_counter.labels(method).inc()
|
||||||
|
|
||||||
def send_request():
|
|
||||||
request_deferred = self.agent.request(
|
|
||||||
method, uri, *args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.clock.time_bound_deferred(
|
|
||||||
request_deferred,
|
|
||||||
time_out=60,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("Sending request %s %s", method, uri)
|
logger.info("Sending request %s %s", method, uri)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with logcontext.PreserveLoggingContext():
|
request_deferred = self.agent.request(
|
||||||
response = yield send_request()
|
method, uri, *args, **kwargs
|
||||||
|
)
|
||||||
|
add_timeout_to_deferred(
|
||||||
|
request_deferred,
|
||||||
|
60, cancelled_to_request_timed_out_error,
|
||||||
|
)
|
||||||
|
response = yield make_deferred_yieldable(request_deferred)
|
||||||
|
|
||||||
incoming_responses_counter.inc(method, response.code)
|
incoming_responses_counter.labels(method, response.code).inc()
|
||||||
logger.info(
|
logger.info(
|
||||||
"Received response to %s %s: %s",
|
"Received response to %s %s: %s",
|
||||||
method, uri, response.code
|
method, uri, response.code
|
||||||
)
|
)
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
incoming_responses_counter.inc(method, "ERR")
|
incoming_responses_counter.labels(method, "ERR").inc()
|
||||||
logger.info(
|
logger.info(
|
||||||
"Error sending request to %s %s: %s %s",
|
"Error sending request to %s %s: %s %s",
|
||||||
method, uri, type(e).__name__, e.message
|
method, uri, type(e).__name__, e.message
|
||||||
|
@ -509,7 +500,7 @@ class SpiderHttpClient(SimpleHttpClient):
|
||||||
reactor,
|
reactor,
|
||||||
SpiderEndpointFactory(hs)
|
SpiderEndpointFactory(hs)
|
||||||
)
|
)
|
||||||
), [('gzip', GzipDecoder)]
|
), [(b'gzip', GzipDecoder)]
|
||||||
)
|
)
|
||||||
# We could look like Chrome:
|
# We could look like Chrome:
|
||||||
# self.user_agent = ("Mozilla/5.0 (%s) (KHTML, like Gecko)
|
# self.user_agent = ("Mozilla/5.0 (%s) (KHTML, like Gecko)
|
||||||
|
|
|
@ -115,9 +115,14 @@ class _WrappedConnection(object):
|
||||||
if time.time() - self.last_request >= 2.5 * 60:
|
if time.time() - self.last_request >= 2.5 * 60:
|
||||||
self.abort()
|
self.abort()
|
||||||
# Abort the underlying TLS connection. The abort() method calls
|
# Abort the underlying TLS connection. The abort() method calls
|
||||||
# loseConnection() on the underlying TLS connection which tries to
|
# loseConnection() on the TLS connection which tries to
|
||||||
# shutdown the connection cleanly. We call abortConnection()
|
# shutdown the connection cleanly. We call abortConnection()
|
||||||
# since that will promptly close the underlying TCP connection.
|
# since that will promptly close the TLS connection.
|
||||||
|
#
|
||||||
|
# In Twisted >18.4; the TLS connection will be None if it has closed
|
||||||
|
# which will make abortConnection() throw. Check that the TLS connection
|
||||||
|
# is not None before trying to close it.
|
||||||
|
if self.transport.getHandle() is not None:
|
||||||
self.transport.abortConnection()
|
self.transport.abortConnection()
|
||||||
|
|
||||||
def request(self, request):
|
def request(self, request):
|
||||||
|
@ -286,7 +291,7 @@ def resolve_service(service_name, dns_client=client, cache=SERVER_CACHE, clock=t
|
||||||
if (len(answers) == 1
|
if (len(answers) == 1
|
||||||
and answers[0].type == dns.SRV
|
and answers[0].type == dns.SRV
|
||||||
and answers[0].payload
|
and answers[0].payload
|
||||||
and answers[0].payload.target == dns.Name('.')):
|
and answers[0].payload.target == dns.Name(b'.')):
|
||||||
raise ConnectError("Service %s unavailable" % service_name)
|
raise ConnectError("Service %s unavailable" % service_name)
|
||||||
|
|
||||||
for answer in answers:
|
for answer in answers:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,17 +13,19 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import synapse.util.retryutils
|
|
||||||
from twisted.internet import defer, reactor, protocol
|
from twisted.internet import defer, reactor, protocol
|
||||||
from twisted.internet.error import DNSLookupError
|
from twisted.internet.error import DNSLookupError
|
||||||
from twisted.web.client import readBody, HTTPConnectionPool, Agent
|
from twisted.web.client import readBody, HTTPConnectionPool, Agent
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web._newclient import ResponseDone
|
from twisted.web._newclient import ResponseDone
|
||||||
|
|
||||||
|
from synapse.http import cancelled_to_request_timed_out_error
|
||||||
from synapse.http.endpoint import matrix_federation_endpoint
|
from synapse.http.endpoint import matrix_federation_endpoint
|
||||||
from synapse.util.async import sleep
|
|
||||||
from synapse.util import logcontext
|
|
||||||
import synapse.metrics
|
import synapse.metrics
|
||||||
|
from synapse.util.async import sleep, add_timeout_to_deferred
|
||||||
|
from synapse.util import logcontext
|
||||||
|
from synapse.util.logcontext import make_deferred_yieldable
|
||||||
|
import synapse.util.retryutils
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json
|
from canonicaljson import encode_canonical_json
|
||||||
|
|
||||||
|
@ -38,22 +41,19 @@ import logging
|
||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
import urllib
|
import urllib
|
||||||
import urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
outbound_logger = logging.getLogger("synapse.http.outbound")
|
outbound_logger = logging.getLogger("synapse.http.outbound")
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
outgoing_requests_counter = Counter("synapse_http_matrixfederationclient_requests",
|
||||||
|
"", ["method"])
|
||||||
outgoing_requests_counter = metrics.register_counter(
|
incoming_responses_counter = Counter("synapse_http_matrixfederationclient_responses",
|
||||||
"requests",
|
"", ["method", "code"])
|
||||||
labels=["method"],
|
|
||||||
)
|
|
||||||
incoming_responses_counter = metrics.register_counter(
|
|
||||||
"responses",
|
|
||||||
labels=["method", "code"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAX_LONG_RETRIES = 10
|
MAX_LONG_RETRIES = 10
|
||||||
|
@ -184,21 +184,20 @@ class MatrixFederationHttpClient(object):
|
||||||
producer = body_callback(method, http_url_bytes, headers_dict)
|
producer = body_callback(method, http_url_bytes, headers_dict)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
def send_request():
|
|
||||||
request_deferred = self.agent.request(
|
request_deferred = self.agent.request(
|
||||||
method,
|
method,
|
||||||
url_bytes,
|
url_bytes,
|
||||||
Headers(headers_dict),
|
Headers(headers_dict),
|
||||||
producer
|
producer
|
||||||
)
|
)
|
||||||
|
add_timeout_to_deferred(
|
||||||
return self.clock.time_bound_deferred(
|
request_deferred,
|
||||||
|
timeout / 1000. if timeout else 60,
|
||||||
|
cancelled_to_request_timed_out_error,
|
||||||
|
)
|
||||||
|
response = yield make_deferred_yieldable(
|
||||||
request_deferred,
|
request_deferred,
|
||||||
time_out=timeout / 1000. if timeout else 60,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with logcontext.PreserveLoggingContext():
|
|
||||||
response = yield send_request()
|
|
||||||
|
|
||||||
log_result = "%d %s" % (response.code, response.phrase,)
|
log_result = "%d %s" % (response.code, response.phrase,)
|
||||||
break
|
break
|
||||||
|
@ -552,7 +551,7 @@ class MatrixFederationHttpClient(object):
|
||||||
|
|
||||||
encoded_args = {}
|
encoded_args = {}
|
||||||
for k, vs in args.items():
|
for k, vs in args.items():
|
||||||
if isinstance(vs, basestring):
|
if isinstance(vs, string_types):
|
||||||
vs = [vs]
|
vs = [vs]
|
||||||
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
||||||
|
|
||||||
|
@ -667,7 +666,7 @@ def check_content_type_is_json(headers):
|
||||||
RuntimeError if the
|
RuntimeError if the
|
||||||
|
|
||||||
"""
|
"""
|
||||||
c_type = headers.getRawHeaders("Content-Type")
|
c_type = headers.getRawHeaders(b"Content-Type")
|
||||||
if c_type is None:
|
if c_type is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"No Content-Type header"
|
"No Content-Type header"
|
||||||
|
@ -684,7 +683,7 @@ def check_content_type_is_json(headers):
|
||||||
def encode_query_args(args):
|
def encode_query_args(args):
|
||||||
encoded_args = {}
|
encoded_args = {}
|
||||||
for k, vs in args.items():
|
for k, vs in args.items():
|
||||||
if isinstance(vs, basestring):
|
if isinstance(vs, string_types):
|
||||||
vs = [vs]
|
vs = [vs]
|
||||||
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
||||||
|
|
||||||
|
|
273
synapse/http/request_metrics.py
Normal file
273
synapse/http/request_metrics.py
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from prometheus_client.core import Counter, Histogram
|
||||||
|
from synapse.metrics import LaterGauge
|
||||||
|
|
||||||
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# total number of responses served, split by method/servlet/tag
|
||||||
|
response_count = Counter(
|
||||||
|
"synapse_http_server_response_count", "", ["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
requests_counter = Counter(
|
||||||
|
"synapse_http_server_requests_received", "", ["method", "servlet"]
|
||||||
|
)
|
||||||
|
|
||||||
|
outgoing_responses_counter = Counter(
|
||||||
|
"synapse_http_server_responses", "", ["method", "code"]
|
||||||
|
)
|
||||||
|
|
||||||
|
response_timer = Histogram(
|
||||||
|
"synapse_http_server_response_time_seconds", "sec", ["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
response_ru_utime = Counter(
|
||||||
|
"synapse_http_server_response_ru_utime_seconds", "sec", ["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
response_ru_stime = Counter(
|
||||||
|
"synapse_http_server_response_ru_stime_seconds", "sec", ["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
response_db_txn_count = Counter(
|
||||||
|
"synapse_http_server_response_db_txn_count", "", ["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for db txns, excluding scheduling time, when processing
|
||||||
|
# this request
|
||||||
|
response_db_txn_duration = Counter(
|
||||||
|
"synapse_http_server_response_db_txn_duration_seconds",
|
||||||
|
"",
|
||||||
|
["method", "servlet", "tag"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for a db connection, when processing this request
|
||||||
|
response_db_sched_duration = Counter(
|
||||||
|
"synapse_http_server_response_db_sched_duration_seconds",
|
||||||
|
"",
|
||||||
|
["method", "servlet", "tag"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# size in bytes of the response written
|
||||||
|
response_size = Counter(
|
||||||
|
"synapse_http_server_response_size", "", ["method", "servlet", "tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# In flight metrics are incremented while the requests are in flight, rather
|
||||||
|
# than when the response was written.
|
||||||
|
|
||||||
|
in_flight_requests_ru_utime = Counter(
|
||||||
|
"synapse_http_server_in_flight_requests_ru_utime_seconds",
|
||||||
|
"",
|
||||||
|
["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_ru_stime = Counter(
|
||||||
|
"synapse_http_server_in_flight_requests_ru_stime_seconds",
|
||||||
|
"",
|
||||||
|
["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_txn_count = Counter(
|
||||||
|
"synapse_http_server_in_flight_requests_db_txn_count", "", ["method", "servlet"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for db txns, excluding scheduling time, when processing
|
||||||
|
# this request
|
||||||
|
in_flight_requests_db_txn_duration = Counter(
|
||||||
|
"synapse_http_server_in_flight_requests_db_txn_duration_seconds",
|
||||||
|
"",
|
||||||
|
["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# seconds spent waiting for a db connection, when processing this request
|
||||||
|
in_flight_requests_db_sched_duration = Counter(
|
||||||
|
"synapse_http_server_in_flight_requests_db_sched_duration_seconds",
|
||||||
|
"",
|
||||||
|
["method", "servlet"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# The set of all in flight requests, set[RequestMetrics]
|
||||||
|
_in_flight_requests = set()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_in_flight_counts():
|
||||||
|
"""Returns a count of all in flight requests by (method, server_name)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[tuple[str, str], int]
|
||||||
|
"""
|
||||||
|
for rm in _in_flight_requests:
|
||||||
|
rm.update_metrics()
|
||||||
|
|
||||||
|
# Map from (method, name) -> int, the number of in flight requests of that
|
||||||
|
# type
|
||||||
|
counts = {}
|
||||||
|
for rm in _in_flight_requests:
|
||||||
|
key = (rm.method, rm.name,)
|
||||||
|
counts[key] = counts.get(key, 0) + 1
|
||||||
|
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
LaterGauge(
|
||||||
|
"synapse_http_request_metrics_in_flight_requests_count",
|
||||||
|
"",
|
||||||
|
["method", "servlet"],
|
||||||
|
_get_in_flight_counts,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestMetrics(object):
|
||||||
|
def start(self, time_sec, name, method):
|
||||||
|
self.start = time_sec
|
||||||
|
self.start_context = LoggingContext.current_context()
|
||||||
|
self.name = name
|
||||||
|
self.method = method
|
||||||
|
|
||||||
|
self._request_stats = _RequestStats.from_context(self.start_context)
|
||||||
|
|
||||||
|
_in_flight_requests.add(self)
|
||||||
|
|
||||||
|
def stop(self, time_sec, request):
|
||||||
|
_in_flight_requests.discard(self)
|
||||||
|
|
||||||
|
context = LoggingContext.current_context()
|
||||||
|
|
||||||
|
tag = ""
|
||||||
|
if context:
|
||||||
|
tag = context.tag
|
||||||
|
|
||||||
|
if context != self.start_context:
|
||||||
|
logger.warn(
|
||||||
|
"Context have unexpectedly changed %r, %r",
|
||||||
|
context, self.start_context
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
outgoing_responses_counter.labels(request.method, str(request.code)).inc()
|
||||||
|
|
||||||
|
response_count.labels(request.method, self.name, tag).inc()
|
||||||
|
|
||||||
|
response_timer.labels(request.method, self.name, tag).observe(
|
||||||
|
time_sec - self.start
|
||||||
|
)
|
||||||
|
|
||||||
|
ru_utime, ru_stime = context.get_resource_usage()
|
||||||
|
|
||||||
|
response_ru_utime.labels(request.method, self.name, tag).inc(ru_utime)
|
||||||
|
response_ru_stime.labels(request.method, self.name, tag).inc(ru_stime)
|
||||||
|
response_db_txn_count.labels(request.method, self.name, tag).inc(
|
||||||
|
context.db_txn_count
|
||||||
|
)
|
||||||
|
response_db_txn_duration.labels(request.method, self.name, tag).inc(
|
||||||
|
context.db_txn_duration_sec
|
||||||
|
)
|
||||||
|
response_db_sched_duration.labels(request.method, self.name, tag).inc(
|
||||||
|
context.db_sched_duration_sec
|
||||||
|
)
|
||||||
|
|
||||||
|
response_size.labels(request.method, self.name, tag).inc(request.sentLength)
|
||||||
|
|
||||||
|
# We always call this at the end to ensure that we update the metrics
|
||||||
|
# regardless of whether a call to /metrics while the request was in
|
||||||
|
# flight.
|
||||||
|
self.update_metrics()
|
||||||
|
|
||||||
|
def update_metrics(self):
|
||||||
|
"""Updates the in flight metrics with values from this request.
|
||||||
|
"""
|
||||||
|
diff = self._request_stats.update(self.start_context)
|
||||||
|
|
||||||
|
in_flight_requests_ru_utime.labels(self.method, self.name).inc(diff.ru_utime)
|
||||||
|
in_flight_requests_ru_stime.labels(self.method, self.name).inc(diff.ru_stime)
|
||||||
|
|
||||||
|
in_flight_requests_db_txn_count.labels(self.method, self.name).inc(
|
||||||
|
diff.db_txn_count
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_txn_duration.labels(self.method, self.name).inc(
|
||||||
|
diff.db_txn_duration_sec
|
||||||
|
)
|
||||||
|
|
||||||
|
in_flight_requests_db_sched_duration.labels(self.method, self.name).inc(
|
||||||
|
diff.db_sched_duration_sec
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _RequestStats(object):
|
||||||
|
"""Keeps tracks of various metrics for an in flight request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = [
|
||||||
|
"ru_utime",
|
||||||
|
"ru_stime",
|
||||||
|
"db_txn_count",
|
||||||
|
"db_txn_duration_sec",
|
||||||
|
"db_sched_duration_sec",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, ru_utime, ru_stime, db_txn_count, db_txn_duration_sec, db_sched_duration_sec
|
||||||
|
):
|
||||||
|
self.ru_utime = ru_utime
|
||||||
|
self.ru_stime = ru_stime
|
||||||
|
self.db_txn_count = db_txn_count
|
||||||
|
self.db_txn_duration_sec = db_txn_duration_sec
|
||||||
|
self.db_sched_duration_sec = db_sched_duration_sec
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_context(context):
|
||||||
|
ru_utime, ru_stime = context.get_resource_usage()
|
||||||
|
|
||||||
|
return _RequestStats(
|
||||||
|
ru_utime, ru_stime,
|
||||||
|
context.db_txn_count,
|
||||||
|
context.db_txn_duration_sec,
|
||||||
|
context.db_sched_duration_sec,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self, context):
|
||||||
|
"""Updates the current values and returns the difference between the
|
||||||
|
old and new values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
_RequestStats: The difference between the old and new values
|
||||||
|
"""
|
||||||
|
new = _RequestStats.from_context(context)
|
||||||
|
|
||||||
|
diff = _RequestStats(
|
||||||
|
new.ru_utime - self.ru_utime,
|
||||||
|
new.ru_stime - self.ru_stime,
|
||||||
|
new.db_txn_count - self.db_txn_count,
|
||||||
|
new.db_txn_duration_sec - self.db_txn_duration_sec,
|
||||||
|
new.db_sched_duration_sec - self.db_sched_duration_sec,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ru_utime = new.ru_utime
|
||||||
|
self.ru_stime = new.ru_stime
|
||||||
|
self.db_txn_count = new.db_txn_count
|
||||||
|
self.db_txn_duration_sec = new.db_txn_duration_sec
|
||||||
|
self.db_sched_duration_sec = new.db_sched_duration_sec
|
||||||
|
|
||||||
|
return diff
|
|
@ -13,11 +13,15 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import cgi
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError, Codes
|
cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError, Codes
|
||||||
)
|
)
|
||||||
|
from synapse.http.request_metrics import (
|
||||||
|
requests_counter,
|
||||||
|
)
|
||||||
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
|
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
|
||||||
from synapse.util.caches import intern_dict
|
from synapse.util.caches import intern_dict
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
@ -41,133 +45,38 @@ import simplejson
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
HTML_ERROR_TEMPLATE = """<!DOCTYPE html>
|
||||||
|
<html lang=en>
|
||||||
# total number of responses served, split by method/servlet/tag
|
<head>
|
||||||
response_count = metrics.register_counter(
|
<meta charset="utf-8">
|
||||||
"response_count",
|
<title>Error {code}</title>
|
||||||
labels=["method", "servlet", "tag"],
|
</head>
|
||||||
alternative_names=(
|
<body>
|
||||||
# the following are all deprecated aliases for the same metric
|
<p>{msg}</p>
|
||||||
metrics.name_prefix + x for x in (
|
</body>
|
||||||
"_requests",
|
</html>
|
||||||
"_response_time:count",
|
"""
|
||||||
"_response_ru_utime:count",
|
|
||||||
"_response_ru_stime:count",
|
|
||||||
"_response_db_txn_count:count",
|
|
||||||
"_response_db_txn_duration:count",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
requests_counter = metrics.register_counter(
|
|
||||||
"requests_received",
|
|
||||||
labels=["method", "servlet", ],
|
|
||||||
)
|
|
||||||
|
|
||||||
outgoing_responses_counter = metrics.register_counter(
|
|
||||||
"responses",
|
|
||||||
labels=["method", "code"],
|
|
||||||
)
|
|
||||||
|
|
||||||
response_timer = metrics.register_counter(
|
|
||||||
"response_time_seconds",
|
|
||||||
labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_time:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
response_ru_utime = metrics.register_counter(
|
|
||||||
"response_ru_utime_seconds", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_ru_utime:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
response_ru_stime = metrics.register_counter(
|
|
||||||
"response_ru_stime_seconds", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_ru_stime:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
response_db_txn_count = metrics.register_counter(
|
|
||||||
"response_db_txn_count", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_db_txn_count:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# seconds spent waiting for db txns, excluding scheduling time, when processing
|
|
||||||
# this request
|
|
||||||
response_db_txn_duration = metrics.register_counter(
|
|
||||||
"response_db_txn_duration_seconds", labels=["method", "servlet", "tag"],
|
|
||||||
alternative_names=(
|
|
||||||
metrics.name_prefix + "_response_db_txn_duration:total",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# seconds spent waiting for a db connection, when processing this request
|
|
||||||
response_db_sched_duration = metrics.register_counter(
|
|
||||||
"response_db_sched_duration_seconds", labels=["method", "servlet", "tag"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# size in bytes of the response written
|
|
||||||
response_size = metrics.register_counter(
|
|
||||||
"response_size", labels=["method", "servlet", "tag"]
|
|
||||||
)
|
|
||||||
|
|
||||||
_next_request_id = 0
|
|
||||||
|
|
||||||
|
|
||||||
def request_handler(include_metrics=False):
|
def wrap_json_request_handler(h):
|
||||||
"""Decorator for ``wrap_request_handler``"""
|
"""Wraps a request handler method with exception handling.
|
||||||
return lambda request_handler: wrap_request_handler(request_handler, include_metrics)
|
|
||||||
|
|
||||||
|
Also adds logging as per wrap_request_handler_with_logging.
|
||||||
|
|
||||||
def wrap_request_handler(request_handler, include_metrics=False):
|
The handler method must have a signature of "handle_foo(self, request)",
|
||||||
"""Wraps a method that acts as a request handler with the necessary logging
|
where "self" must have a "clock" attribute (and "request" must be a
|
||||||
and exception handling.
|
SynapseRequest).
|
||||||
|
|
||||||
The method must have a signature of "handle_foo(self, request)". The
|
The handler must return a deferred. If the deferred succeeds we assume that
|
||||||
argument "self" must have "version_string" and "clock" attributes. The
|
|
||||||
argument "request" must be a twisted HTTP request.
|
|
||||||
|
|
||||||
The method must return a deferred. If the deferred succeeds we assume that
|
|
||||||
a response has been sent. If the deferred fails with a SynapseError we use
|
a response has been sent. If the deferred fails with a SynapseError we use
|
||||||
it to send a JSON response with the appropriate HTTP reponse code. If the
|
it to send a JSON response with the appropriate HTTP reponse code. If the
|
||||||
deferred fails with any other type of error we send a 500 reponse.
|
deferred fails with any other type of error we send a 500 reponse.
|
||||||
|
|
||||||
We insert a unique request-id into the logging context for this request and
|
|
||||||
log the response and duration for this request.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def wrapped_request_handler(self, request):
|
def wrapped_request_handler(self, request):
|
||||||
global _next_request_id
|
|
||||||
request_id = "%s-%s" % (request.method, _next_request_id)
|
|
||||||
_next_request_id += 1
|
|
||||||
|
|
||||||
with LoggingContext(request_id) as request_context:
|
|
||||||
with Measure(self.clock, "wrapped_request_handler"):
|
|
||||||
request_metrics = RequestMetrics()
|
|
||||||
# we start the request metrics timer here with an initial stab
|
|
||||||
# at the servlet name. For most requests that name will be
|
|
||||||
# JsonResource (or a subclass), and JsonResource._async_render
|
|
||||||
# will update it once it picks a servlet.
|
|
||||||
servlet_name = self.__class__.__name__
|
|
||||||
request_metrics.start(self.clock, name=servlet_name)
|
|
||||||
|
|
||||||
request_context.request = request_id
|
|
||||||
with request.processing():
|
|
||||||
try:
|
try:
|
||||||
with PreserveLoggingContext(request_context):
|
yield h(self, request)
|
||||||
if include_metrics:
|
|
||||||
yield request_handler(self, request, request_metrics)
|
|
||||||
else:
|
|
||||||
requests_counter.inc(request.method, servlet_name)
|
|
||||||
yield request_handler(self, request)
|
|
||||||
except CodeMessageException as e:
|
except CodeMessageException as e:
|
||||||
code = e.code
|
code = e.code
|
||||||
if isinstance(e, SynapseError):
|
if isinstance(e, SynapseError):
|
||||||
|
@ -176,22 +85,19 @@ def wrap_request_handler(request_handler, include_metrics=False):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
outgoing_responses_counter.inc(request.method, str(code))
|
|
||||||
respond_with_json(
|
respond_with_json(
|
||||||
request, code, cs_exception(e), send_cors=True,
|
request, code, cs_exception(e), send_cors=True,
|
||||||
pretty_print=_request_user_agent_is_curl(request),
|
pretty_print=_request_user_agent_is_curl(request),
|
||||||
version_string=self.version_string,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# failure.Failure() fishes the original Failure out
|
# failure.Failure() fishes the original Failure out
|
||||||
# of our stack, and thus gives us a sensible stack
|
# of our stack, and thus gives us a sensible stack
|
||||||
# trace.
|
# trace.
|
||||||
f = failure.Failure()
|
f = failure.Failure()
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed handle request %s.%s on %r: %r: %s",
|
"Failed handle request via %r: %r: %s",
|
||||||
request_handler.__module__,
|
h,
|
||||||
request_handler.__name__,
|
|
||||||
self,
|
|
||||||
request,
|
request,
|
||||||
f.getTraceback().rstrip(),
|
f.getTraceback().rstrip(),
|
||||||
)
|
)
|
||||||
|
@ -204,15 +110,109 @@ def wrap_request_handler(request_handler, include_metrics=False):
|
||||||
},
|
},
|
||||||
send_cors=True,
|
send_cors=True,
|
||||||
pretty_print=_request_user_agent_is_curl(request),
|
pretty_print=_request_user_agent_is_curl(request),
|
||||||
version_string=self.version_string,
|
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
try:
|
return wrap_request_handler_with_logging(wrapped_request_handler)
|
||||||
request_metrics.stop(
|
|
||||||
self.clock, request
|
|
||||||
|
def wrap_html_request_handler(h):
|
||||||
|
"""Wraps a request handler method with exception handling.
|
||||||
|
|
||||||
|
Also adds logging as per wrap_request_handler_with_logging.
|
||||||
|
|
||||||
|
The handler method must have a signature of "handle_foo(self, request)",
|
||||||
|
where "self" must have a "clock" attribute (and "request" must be a
|
||||||
|
SynapseRequest).
|
||||||
|
"""
|
||||||
|
def wrapped_request_handler(self, request):
|
||||||
|
d = defer.maybeDeferred(h, self, request)
|
||||||
|
d.addErrback(_return_html_error, request)
|
||||||
|
return d
|
||||||
|
|
||||||
|
return wrap_request_handler_with_logging(wrapped_request_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def _return_html_error(f, request):
|
||||||
|
"""Sends an HTML error page corresponding to the given failure
|
||||||
|
|
||||||
|
Args:
|
||||||
|
f (twisted.python.failure.Failure):
|
||||||
|
request (twisted.web.iweb.IRequest):
|
||||||
|
"""
|
||||||
|
if f.check(CodeMessageException):
|
||||||
|
cme = f.value
|
||||||
|
code = cme.code
|
||||||
|
msg = cme.msg
|
||||||
|
|
||||||
|
if isinstance(cme, SynapseError):
|
||||||
|
logger.info(
|
||||||
|
"%s SynapseError: %s - %s", request, code, msg
|
||||||
)
|
)
|
||||||
except Exception as e:
|
else:
|
||||||
logger.warn("Failed to stop metrics: %r", e)
|
logger.error(
|
||||||
|
"Failed handle request %r: %s",
|
||||||
|
request,
|
||||||
|
f.getTraceback().rstrip(),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
code = http_client.INTERNAL_SERVER_ERROR
|
||||||
|
msg = "Internal server error"
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
"Failed handle request %r: %s",
|
||||||
|
request,
|
||||||
|
f.getTraceback().rstrip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
body = HTML_ERROR_TEMPLATE.format(
|
||||||
|
code=code, msg=cgi.escape(msg),
|
||||||
|
).encode("utf-8")
|
||||||
|
request.setResponseCode(code)
|
||||||
|
request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
|
||||||
|
request.setHeader(b"Content-Length", b"%i" % (len(body),))
|
||||||
|
request.write(body)
|
||||||
|
finish_request(request)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_request_handler_with_logging(h):
|
||||||
|
"""Wraps a request handler to provide logging and metrics
|
||||||
|
|
||||||
|
The handler method must have a signature of "handle_foo(self, request)",
|
||||||
|
where "self" must have a "clock" attribute (and "request" must be a
|
||||||
|
SynapseRequest).
|
||||||
|
|
||||||
|
As well as calling `request.processing` (which will log the response and
|
||||||
|
duration for this request), the wrapped request handler will insert the
|
||||||
|
request id into the logging context.
|
||||||
|
"""
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def wrapped_request_handler(self, request):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
self:
|
||||||
|
request (synapse.http.site.SynapseRequest):
|
||||||
|
"""
|
||||||
|
|
||||||
|
request_id = request.get_request_id()
|
||||||
|
with LoggingContext(request_id) as request_context:
|
||||||
|
request_context.request = request_id
|
||||||
|
with Measure(self.clock, "wrapped_request_handler"):
|
||||||
|
# we start the request metrics timer here with an initial stab
|
||||||
|
# at the servlet name. For most requests that name will be
|
||||||
|
# JsonResource (or a subclass), and JsonResource._async_render
|
||||||
|
# will update it once it picks a servlet.
|
||||||
|
servlet_name = self.__class__.__name__
|
||||||
|
with request.processing(servlet_name):
|
||||||
|
with PreserveLoggingContext(request_context):
|
||||||
|
d = defer.maybeDeferred(h, self, request)
|
||||||
|
|
||||||
|
# record the arrival of the request *after*
|
||||||
|
# dispatching to the handler, so that the handler
|
||||||
|
# can update the servlet name in the request
|
||||||
|
# metrics
|
||||||
|
requests_counter.labels(request.method,
|
||||||
|
request.request_metrics.name).inc()
|
||||||
|
yield d
|
||||||
return wrapped_request_handler
|
return wrapped_request_handler
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,7 +262,6 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
self.canonical_json = canonical_json
|
self.canonical_json = canonical_json
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.path_regexs = {}
|
self.path_regexs = {}
|
||||||
self.version_string = hs.version_string
|
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
|
|
||||||
def register_paths(self, method, path_patterns, callback):
|
def register_paths(self, method, path_patterns, callback):
|
||||||
|
@ -278,13 +277,9 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
self._async_render(request)
|
self._async_render(request)
|
||||||
return server.NOT_DONE_YET
|
return server.NOT_DONE_YET
|
||||||
|
|
||||||
# Disable metric reporting because _async_render does its own metrics.
|
@wrap_json_request_handler
|
||||||
# It does its own metric reporting because _async_render dispatches to
|
|
||||||
# a callback and it's the class name of that callback we want to report
|
|
||||||
# against rather than the JsonResource itself.
|
|
||||||
@request_handler(include_metrics=True)
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _async_render(self, request, request_metrics):
|
def _async_render(self, request):
|
||||||
""" This gets called from render() every time someone sends us a request.
|
""" This gets called from render() every time someone sends us a request.
|
||||||
This checks if anyone has registered a callback for that method and
|
This checks if anyone has registered a callback for that method and
|
||||||
path.
|
path.
|
||||||
|
@ -296,9 +291,7 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
servlet_classname = servlet_instance.__class__.__name__
|
servlet_classname = servlet_instance.__class__.__name__
|
||||||
else:
|
else:
|
||||||
servlet_classname = "%r" % callback
|
servlet_classname = "%r" % callback
|
||||||
|
request.request_metrics.name = servlet_classname
|
||||||
request_metrics.name = servlet_classname
|
|
||||||
requests_counter.inc(request.method, servlet_classname)
|
|
||||||
|
|
||||||
# Now trigger the callback. If it returns a response, we send it
|
# Now trigger the callback. If it returns a response, we send it
|
||||||
# here. If it throws an exception, that is handled by the wrapper
|
# here. If it throws an exception, that is handled by the wrapper
|
||||||
|
@ -345,15 +338,12 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
|
|
||||||
def _send_response(self, request, code, response_json_object,
|
def _send_response(self, request, code, response_json_object,
|
||||||
response_code_message=None):
|
response_code_message=None):
|
||||||
outgoing_responses_counter.inc(request.method, str(code))
|
|
||||||
|
|
||||||
# TODO: Only enable CORS for the requests that need it.
|
# TODO: Only enable CORS for the requests that need it.
|
||||||
respond_with_json(
|
respond_with_json(
|
||||||
request, code, response_json_object,
|
request, code, response_json_object,
|
||||||
send_cors=True,
|
send_cors=True,
|
||||||
response_code_message=response_code_message,
|
response_code_message=response_code_message,
|
||||||
pretty_print=_request_user_agent_is_curl(request),
|
pretty_print=_request_user_agent_is_curl(request),
|
||||||
version_string=self.version_string,
|
|
||||||
canonical_json=self.canonical_json,
|
canonical_json=self.canonical_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -386,54 +376,6 @@ def _unrecognised_request_handler(request):
|
||||||
raise UnrecognizedRequestError()
|
raise UnrecognizedRequestError()
|
||||||
|
|
||||||
|
|
||||||
class RequestMetrics(object):
|
|
||||||
def start(self, clock, name):
|
|
||||||
self.start = clock.time_msec()
|
|
||||||
self.start_context = LoggingContext.current_context()
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def stop(self, clock, request):
|
|
||||||
context = LoggingContext.current_context()
|
|
||||||
|
|
||||||
tag = ""
|
|
||||||
if context:
|
|
||||||
tag = context.tag
|
|
||||||
|
|
||||||
if context != self.start_context:
|
|
||||||
logger.warn(
|
|
||||||
"Context have unexpectedly changed %r, %r",
|
|
||||||
context, self.start_context
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
response_count.inc(request.method, self.name, tag)
|
|
||||||
|
|
||||||
response_timer.inc_by(
|
|
||||||
clock.time_msec() - self.start, request.method,
|
|
||||||
self.name, tag
|
|
||||||
)
|
|
||||||
|
|
||||||
ru_utime, ru_stime = context.get_resource_usage()
|
|
||||||
|
|
||||||
response_ru_utime.inc_by(
|
|
||||||
ru_utime, request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_ru_stime.inc_by(
|
|
||||||
ru_stime, request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_db_txn_count.inc_by(
|
|
||||||
context.db_txn_count, request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_db_txn_duration.inc_by(
|
|
||||||
context.db_txn_duration_ms / 1000., request.method, self.name, tag
|
|
||||||
)
|
|
||||||
response_db_sched_duration.inc_by(
|
|
||||||
context.db_sched_duration_ms / 1000., request.method, self.name, tag
|
|
||||||
)
|
|
||||||
|
|
||||||
response_size.inc_by(request.sentLength, request.method, self.name, tag)
|
|
||||||
|
|
||||||
|
|
||||||
class RootRedirect(resource.Resource):
|
class RootRedirect(resource.Resource):
|
||||||
"""Redirects the root '/' path to another path."""
|
"""Redirects the root '/' path to another path."""
|
||||||
|
|
||||||
|
@ -452,7 +394,7 @@ class RootRedirect(resource.Resource):
|
||||||
|
|
||||||
def respond_with_json(request, code, json_object, send_cors=False,
|
def respond_with_json(request, code, json_object, send_cors=False,
|
||||||
response_code_message=None, pretty_print=False,
|
response_code_message=None, pretty_print=False,
|
||||||
version_string="", canonical_json=True):
|
canonical_json=True):
|
||||||
# could alternatively use request.notifyFinish() and flip a flag when
|
# could alternatively use request.notifyFinish() and flip a flag when
|
||||||
# the Deferred fires, but since the flag is RIGHT THERE it seems like
|
# the Deferred fires, but since the flag is RIGHT THERE it seems like
|
||||||
# a waste.
|
# a waste.
|
||||||
|
@ -474,12 +416,11 @@ def respond_with_json(request, code, json_object, send_cors=False,
|
||||||
request, code, json_bytes,
|
request, code, json_bytes,
|
||||||
send_cors=send_cors,
|
send_cors=send_cors,
|
||||||
response_code_message=response_code_message,
|
response_code_message=response_code_message,
|
||||||
version_string=version_string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
||||||
version_string="", response_code_message=None):
|
response_code_message=None):
|
||||||
"""Sends encoded JSON in response to the given request.
|
"""Sends encoded JSON in response to the given request.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -493,7 +434,6 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
|
||||||
|
|
||||||
request.setResponseCode(code, message=response_code_message)
|
request.setResponseCode(code, message=response_code_message)
|
||||||
request.setHeader(b"Content-Type", b"application/json")
|
request.setHeader(b"Content-Type", b"application/json")
|
||||||
request.setHeader(b"Server", version_string)
|
|
||||||
request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))
|
request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))
|
||||||
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")
|
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")
|
||||||
|
|
||||||
|
@ -546,6 +486,6 @@ def _request_user_agent_is_curl(request):
|
||||||
b"User-Agent", default=[]
|
b"User-Agent", default=[]
|
||||||
)
|
)
|
||||||
for user_agent in user_agents:
|
for user_agent in user_agents:
|
||||||
if "curl" in user_agent:
|
if b"curl" in user_agent:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -12,27 +12,51 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from synapse.util.logcontext import LoggingContext
|
|
||||||
from twisted.web.server import Site, Request
|
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from twisted.web.server import Site, Request
|
||||||
|
|
||||||
|
from synapse.http.request_metrics import RequestMetrics
|
||||||
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
ACCESS_TOKEN_RE = re.compile(br'(\?.*access(_|%5[Ff])token=)[^&]*(.*)$')
|
ACCESS_TOKEN_RE = re.compile(br'(\?.*access(_|%5[Ff])token=)[^&]*(.*)$')
|
||||||
|
|
||||||
|
_next_request_seq = 0
|
||||||
|
|
||||||
|
|
||||||
class SynapseRequest(Request):
|
class SynapseRequest(Request):
|
||||||
|
"""Class which encapsulates an HTTP request to synapse.
|
||||||
|
|
||||||
|
All of the requests processed in synapse are of this type.
|
||||||
|
|
||||||
|
It extends twisted's twisted.web.server.Request, and adds:
|
||||||
|
* Unique request ID
|
||||||
|
* Redaction of access_token query-params in __repr__
|
||||||
|
* Logging at start and end
|
||||||
|
* Metrics to record CPU, wallclock and DB time by endpoint.
|
||||||
|
|
||||||
|
It provides a method `processing` which should be called by the Resource
|
||||||
|
which is handling the request, and returns a context manager.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, site, *args, **kw):
|
def __init__(self, site, *args, **kw):
|
||||||
Request.__init__(self, *args, **kw)
|
Request.__init__(self, *args, **kw)
|
||||||
self.site = site
|
self.site = site
|
||||||
self.authenticated_entity = None
|
self.authenticated_entity = None
|
||||||
self.start_time = 0
|
self.start_time = 0
|
||||||
|
|
||||||
|
global _next_request_seq
|
||||||
|
self.request_seq = _next_request_seq
|
||||||
|
_next_request_seq += 1
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
# We overwrite this so that we don't log ``access_token``
|
# We overwrite this so that we don't log ``access_token``
|
||||||
return '<%s at 0x%x method=%s uri=%s clientproto=%s site=%s>' % (
|
return '<%s at 0x%x method=%r uri=%r clientproto=%r site=%r>' % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
id(self),
|
id(self),
|
||||||
self.method,
|
self.method,
|
||||||
|
@ -41,6 +65,9 @@ class SynapseRequest(Request):
|
||||||
self.site.site_tag,
|
self.site.site_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_request_id(self):
|
||||||
|
return "%s-%i" % (self.method, self.request_seq)
|
||||||
|
|
||||||
def get_redacted_uri(self):
|
def get_redacted_uri(self):
|
||||||
return ACCESS_TOKEN_RE.sub(
|
return ACCESS_TOKEN_RE.sub(
|
||||||
br'\1<redacted>\3',
|
br'\1<redacted>\3',
|
||||||
|
@ -50,7 +77,18 @@ class SynapseRequest(Request):
|
||||||
def get_user_agent(self):
|
def get_user_agent(self):
|
||||||
return self.requestHeaders.getRawHeaders(b"User-Agent", [None])[-1]
|
return self.requestHeaders.getRawHeaders(b"User-Agent", [None])[-1]
|
||||||
|
|
||||||
def started_processing(self):
|
def render(self, resrc):
|
||||||
|
# override the Server header which is set by twisted
|
||||||
|
self.setHeader("Server", self.site.server_version_string)
|
||||||
|
return Request.render(self, resrc)
|
||||||
|
|
||||||
|
def _started_processing(self, servlet_name):
|
||||||
|
self.start_time = time.time()
|
||||||
|
self.request_metrics = RequestMetrics()
|
||||||
|
self.request_metrics.start(
|
||||||
|
self.start_time, name=servlet_name, method=self.method,
|
||||||
|
)
|
||||||
|
|
||||||
self.site.access_logger.info(
|
self.site.access_logger.info(
|
||||||
"%s - %s - Received request: %s %s",
|
"%s - %s - Received request: %s %s",
|
||||||
self.getClientIP(),
|
self.getClientIP(),
|
||||||
|
@ -58,32 +96,32 @@ class SynapseRequest(Request):
|
||||||
self.method,
|
self.method,
|
||||||
self.get_redacted_uri()
|
self.get_redacted_uri()
|
||||||
)
|
)
|
||||||
self.start_time = int(time.time() * 1000)
|
|
||||||
|
|
||||||
def finished_processing(self):
|
|
||||||
|
|
||||||
|
def _finished_processing(self):
|
||||||
try:
|
try:
|
||||||
context = LoggingContext.current_context()
|
context = LoggingContext.current_context()
|
||||||
ru_utime, ru_stime = context.get_resource_usage()
|
ru_utime, ru_stime = context.get_resource_usage()
|
||||||
db_txn_count = context.db_txn_count
|
db_txn_count = context.db_txn_count
|
||||||
db_txn_duration_ms = context.db_txn_duration_ms
|
db_txn_duration_sec = context.db_txn_duration_sec
|
||||||
db_sched_duration_ms = context.db_sched_duration_ms
|
db_sched_duration_sec = context.db_sched_duration_sec
|
||||||
except Exception:
|
except Exception:
|
||||||
ru_utime, ru_stime = (0, 0)
|
ru_utime, ru_stime = (0, 0)
|
||||||
db_txn_count, db_txn_duration_ms = (0, 0)
|
db_txn_count, db_txn_duration_sec = (0, 0)
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
|
||||||
self.site.access_logger.info(
|
self.site.access_logger.info(
|
||||||
"%s - %s - {%s}"
|
"%s - %s - {%s}"
|
||||||
" Processed request: %dms (%dms, %dms) (%dms/%dms/%d)"
|
" Processed request: %.3fsec (%.3fsec, %.3fsec) (%.3fsec/%.3fsec/%d)"
|
||||||
" %sB %s \"%s %s %s\" \"%s\"",
|
" %sB %s \"%s %s %s\" \"%s\"",
|
||||||
self.getClientIP(),
|
self.getClientIP(),
|
||||||
self.site.site_tag,
|
self.site.site_tag,
|
||||||
self.authenticated_entity,
|
self.authenticated_entity,
|
||||||
int(time.time() * 1000) - self.start_time,
|
end_time - self.start_time,
|
||||||
int(ru_utime * 1000),
|
ru_utime,
|
||||||
int(ru_stime * 1000),
|
ru_stime,
|
||||||
db_sched_duration_ms,
|
db_sched_duration_sec,
|
||||||
db_txn_duration_ms,
|
db_txn_duration_sec,
|
||||||
int(db_txn_count),
|
int(db_txn_count),
|
||||||
self.sentLength,
|
self.sentLength,
|
||||||
self.code,
|
self.code,
|
||||||
|
@ -93,11 +131,38 @@ class SynapseRequest(Request):
|
||||||
self.get_user_agent(),
|
self.get_user_agent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.request_metrics.stop(end_time, self)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn("Failed to stop metrics: %r", e)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def processing(self):
|
def processing(self, servlet_name):
|
||||||
self.started_processing()
|
"""Record the fact that we are processing this request.
|
||||||
|
|
||||||
|
Returns a context manager; the correct way to use this is:
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def handle_request(request):
|
||||||
|
with request.processing("FooServlet"):
|
||||||
|
yield really_handle_the_request()
|
||||||
|
|
||||||
|
This will log the request's arrival. Once the context manager is
|
||||||
|
closed, the completion of the request will be logged, and the various
|
||||||
|
metrics will be updated.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
servlet_name (str): the name of the servlet which will be
|
||||||
|
processing this request. This is used in the metrics.
|
||||||
|
|
||||||
|
It is possible to update this afterwards by updating
|
||||||
|
self.request_metrics.servlet_name.
|
||||||
|
"""
|
||||||
|
# TODO: we should probably just move this into render() and finish(),
|
||||||
|
# to save having to call a separate method.
|
||||||
|
self._started_processing(servlet_name)
|
||||||
yield
|
yield
|
||||||
self.finished_processing()
|
self._finished_processing()
|
||||||
|
|
||||||
|
|
||||||
class XForwardedForRequest(SynapseRequest):
|
class XForwardedForRequest(SynapseRequest):
|
||||||
|
@ -135,7 +200,8 @@ class SynapseSite(Site):
|
||||||
Subclass of a twisted http Site that does access logging with python's
|
Subclass of a twisted http Site that does access logging with python's
|
||||||
standard logging
|
standard logging
|
||||||
"""
|
"""
|
||||||
def __init__(self, logger_name, site_tag, config, resource, *args, **kwargs):
|
def __init__(self, logger_name, site_tag, config, resource,
|
||||||
|
server_version_string, *args, **kwargs):
|
||||||
Site.__init__(self, resource, *args, **kwargs)
|
Site.__init__(self, resource, *args, **kwargs)
|
||||||
|
|
||||||
self.site_tag = site_tag
|
self.site_tag = site_tag
|
||||||
|
@ -143,6 +209,7 @@ class SynapseSite(Site):
|
||||||
proxied = config.get("x_forwarded", False)
|
proxied = config.get("x_forwarded", False)
|
||||||
self.requestFactory = SynapseRequestFactory(self, proxied)
|
self.requestFactory = SynapseRequestFactory(self, proxied)
|
||||||
self.access_logger = logging.getLogger(logger_name)
|
self.access_logger = logging.getLogger(logger_name)
|
||||||
|
self.server_version_string = server_version_string
|
||||||
|
|
||||||
def log(self, request):
|
def log(self, request):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -17,165 +17,178 @@ import logging
|
||||||
import functools
|
import functools
|
||||||
import time
|
import time
|
||||||
import gc
|
import gc
|
||||||
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import attr
|
||||||
|
|
||||||
|
from prometheus_client import Gauge, Histogram, Counter
|
||||||
|
from prometheus_client.core import GaugeMetricFamily, REGISTRY
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
|
|
||||||
from .metric import (
|
|
||||||
CounterMetric, CallbackMetric, DistributionMetric, CacheMetric,
|
|
||||||
MemoryUsageMetric, GaugeMetric,
|
|
||||||
)
|
|
||||||
from .process_collector import register_process_collector
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
running_on_pypy = platform.python_implementation() == "PyPy"
|
||||||
running_on_pypy = platform.python_implementation() == 'PyPy'
|
|
||||||
all_metrics = []
|
all_metrics = []
|
||||||
all_collectors = []
|
all_collectors = []
|
||||||
|
all_gauges = {}
|
||||||
|
|
||||||
|
HAVE_PROC_SELF_STAT = os.path.exists("/proc/self/stat")
|
||||||
|
|
||||||
|
|
||||||
class Metrics(object):
|
class RegistryProxy(object):
|
||||||
""" A single Metrics object gives a (mutable) slice view of the all_metrics
|
|
||||||
dict, allowing callers to easily register new metrics that are namespaced
|
|
||||||
nicely."""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
@staticmethod
|
||||||
self.name_prefix = name
|
def collect():
|
||||||
|
for metric in REGISTRY.collect():
|
||||||
def make_subspace(self, name):
|
if not metric.name.startswith("__"):
|
||||||
return Metrics("%s_%s" % (self.name_prefix, name))
|
yield metric
|
||||||
|
|
||||||
def register_collector(self, func):
|
|
||||||
all_collectors.append(func)
|
|
||||||
|
|
||||||
def _register(self, metric_class, name, *args, **kwargs):
|
|
||||||
full_name = "%s_%s" % (self.name_prefix, name)
|
|
||||||
|
|
||||||
metric = metric_class(full_name, *args, **kwargs)
|
|
||||||
|
|
||||||
all_metrics.append(metric)
|
|
||||||
return metric
|
|
||||||
|
|
||||||
def register_counter(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
CounterMetric
|
|
||||||
"""
|
|
||||||
return self._register(CounterMetric, *args, **kwargs)
|
|
||||||
|
|
||||||
def register_gauge(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
GaugeMetric
|
|
||||||
"""
|
|
||||||
return self._register(GaugeMetric, *args, **kwargs)
|
|
||||||
|
|
||||||
def register_callback(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
CallbackMetric
|
|
||||||
"""
|
|
||||||
return self._register(CallbackMetric, *args, **kwargs)
|
|
||||||
|
|
||||||
def register_distribution(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
DistributionMetric
|
|
||||||
"""
|
|
||||||
return self._register(DistributionMetric, *args, **kwargs)
|
|
||||||
|
|
||||||
def register_cache(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
CacheMetric
|
|
||||||
"""
|
|
||||||
return self._register(CacheMetric, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def register_memory_metrics(hs):
|
@attr.s(hash=True)
|
||||||
|
class LaterGauge(object):
|
||||||
|
|
||||||
|
name = attr.ib()
|
||||||
|
desc = attr.ib()
|
||||||
|
labels = attr.ib(hash=False)
|
||||||
|
caller = attr.ib()
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
|
||||||
|
g = GaugeMetricFamily(self.name, self.desc, labels=self.labels)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psutil
|
calls = self.caller()
|
||||||
process = psutil.Process()
|
|
||||||
process.memory_info().rss
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
logger.warn(
|
|
||||||
"psutil is not installed or incorrect version."
|
|
||||||
" Disabling memory metrics."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
metric = MemoryUsageMetric(hs, psutil)
|
|
||||||
all_metrics.append(metric)
|
|
||||||
|
|
||||||
|
|
||||||
def get_metrics_for(pkg_name):
|
|
||||||
""" Returns a Metrics instance for conveniently creating metrics
|
|
||||||
namespaced with the given name prefix. """
|
|
||||||
|
|
||||||
# Convert a "package.name" to "package_name" because Prometheus doesn't
|
|
||||||
# let us use . in metric names
|
|
||||||
return Metrics(pkg_name.replace(".", "_"))
|
|
||||||
|
|
||||||
|
|
||||||
def render_all():
|
|
||||||
strs = []
|
|
||||||
|
|
||||||
for collector in all_collectors:
|
|
||||||
collector()
|
|
||||||
|
|
||||||
for metric in all_metrics:
|
|
||||||
try:
|
|
||||||
strs += metric.render()
|
|
||||||
except Exception:
|
except Exception:
|
||||||
strs += ["# FAILED to render"]
|
logger.exception(
|
||||||
logger.exception("Failed to render metric")
|
"Exception running callback for LaterGuage(%s)",
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
|
yield g
|
||||||
|
return
|
||||||
|
|
||||||
strs.append("") # to generate a final CRLF
|
if isinstance(calls, dict):
|
||||||
|
for k, v in calls.items():
|
||||||
|
g.add_metric(k, v)
|
||||||
|
else:
|
||||||
|
g.add_metric([], calls)
|
||||||
|
|
||||||
return "\n".join(strs)
|
yield g
|
||||||
|
|
||||||
|
def __attrs_post_init__(self):
|
||||||
|
self._register()
|
||||||
|
|
||||||
|
def _register(self):
|
||||||
|
if self.name in all_gauges.keys():
|
||||||
|
logger.warning("%s already registered, reregistering" % (self.name,))
|
||||||
|
REGISTRY.unregister(all_gauges.pop(self.name))
|
||||||
|
|
||||||
|
REGISTRY.register(self)
|
||||||
|
all_gauges[self.name] = self
|
||||||
|
|
||||||
|
|
||||||
register_process_collector(get_metrics_for("process"))
|
#
|
||||||
|
# Detailed CPU metrics
|
||||||
|
#
|
||||||
|
|
||||||
|
class CPUMetrics(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
ticks_per_sec = 100
|
||||||
|
try:
|
||||||
|
# Try and get the system config
|
||||||
|
ticks_per_sec = os.sysconf('SC_CLK_TCK')
|
||||||
|
except (ValueError, TypeError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.ticks_per_sec = ticks_per_sec
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
if not HAVE_PROC_SELF_STAT:
|
||||||
|
return
|
||||||
|
|
||||||
|
with open("/proc/self/stat") as s:
|
||||||
|
line = s.read()
|
||||||
|
raw_stats = line.split(") ", 1)[1].split(" ")
|
||||||
|
|
||||||
|
user = GaugeMetricFamily("process_cpu_user_seconds_total", "")
|
||||||
|
user.add_metric([], float(raw_stats[11]) / self.ticks_per_sec)
|
||||||
|
yield user
|
||||||
|
|
||||||
|
sys = GaugeMetricFamily("process_cpu_system_seconds_total", "")
|
||||||
|
sys.add_metric([], float(raw_stats[12]) / self.ticks_per_sec)
|
||||||
|
yield sys
|
||||||
|
|
||||||
|
|
||||||
python_metrics = get_metrics_for("python")
|
REGISTRY.register(CPUMetrics())
|
||||||
|
|
||||||
gc_time = python_metrics.register_distribution("gc_time", labels=["gen"])
|
#
|
||||||
gc_unreachable = python_metrics.register_counter("gc_unreachable_total", labels=["gen"])
|
# Python GC metrics
|
||||||
python_metrics.register_callback(
|
#
|
||||||
"gc_counts", lambda: {(i,): v for i, v in enumerate(gc.get_count())}, labels=["gen"]
|
|
||||||
|
gc_unreachable = Gauge("python_gc_unreachable_total", "Unreachable GC objects", ["gen"])
|
||||||
|
gc_time = Histogram(
|
||||||
|
"python_gc_time",
|
||||||
|
"Time taken to GC (sec)",
|
||||||
|
["gen"],
|
||||||
|
buckets=[0.0025, 0.005, 0.01, 0.025, 0.05, 0.10, 0.25, 0.50, 1.00, 2.50,
|
||||||
|
5.00, 7.50, 15.00, 30.00, 45.00, 60.00],
|
||||||
)
|
)
|
||||||
|
|
||||||
reactor_metrics = get_metrics_for("python.twisted.reactor")
|
|
||||||
tick_time = reactor_metrics.register_distribution("tick_time")
|
|
||||||
pending_calls_metric = reactor_metrics.register_distribution("pending_calls")
|
|
||||||
|
|
||||||
synapse_metrics = get_metrics_for("synapse")
|
class GCCounts(object):
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
cm = GaugeMetricFamily("python_gc_counts", "GC cycle counts", labels=["gen"])
|
||||||
|
for n, m in enumerate(gc.get_count()):
|
||||||
|
cm.add_metric([str(n)], m)
|
||||||
|
|
||||||
|
yield cm
|
||||||
|
|
||||||
|
|
||||||
|
REGISTRY.register(GCCounts())
|
||||||
|
|
||||||
|
#
|
||||||
|
# Twisted reactor metrics
|
||||||
|
#
|
||||||
|
|
||||||
|
tick_time = Histogram(
|
||||||
|
"python_twisted_reactor_tick_time",
|
||||||
|
"Tick time of the Twisted reactor (sec)",
|
||||||
|
buckets=[0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.5, 1, 2, 5],
|
||||||
|
)
|
||||||
|
pending_calls_metric = Histogram(
|
||||||
|
"python_twisted_reactor_pending_calls",
|
||||||
|
"Pending calls",
|
||||||
|
buckets=[1, 2, 5, 10, 25, 50, 100, 250, 500, 1000],
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Federation Metrics
|
||||||
|
#
|
||||||
|
|
||||||
|
sent_edus_counter = Counter("synapse_federation_client_sent_edus", "")
|
||||||
|
|
||||||
|
sent_transactions_counter = Counter("synapse_federation_client_sent_transactions", "")
|
||||||
|
|
||||||
|
events_processed_counter = Counter("synapse_federation_client_events_processed", "")
|
||||||
|
|
||||||
# Used to track where various components have processed in the event stream,
|
# Used to track where various components have processed in the event stream,
|
||||||
# e.g. federation sending, appservice sending, etc.
|
# e.g. federation sending, appservice sending, etc.
|
||||||
event_processing_positions = synapse_metrics.register_gauge(
|
event_processing_positions = Gauge("synapse_event_processing_positions", "", ["name"])
|
||||||
"event_processing_positions", labels=["name"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Used to track the current max events stream position
|
# Used to track the current max events stream position
|
||||||
event_persisted_position = synapse_metrics.register_gauge(
|
event_persisted_position = Gauge("synapse_event_persisted_position", "")
|
||||||
"event_persisted_position",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Used to track the received_ts of the last event processed by various
|
# Used to track the received_ts of the last event processed by various
|
||||||
# components
|
# components
|
||||||
event_processing_last_ts = synapse_metrics.register_gauge(
|
event_processing_last_ts = Gauge("synapse_event_processing_last_ts", "", ["name"])
|
||||||
"event_processing_last_ts", labels=["name"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Used to track the lag processing events. This is the time difference
|
# Used to track the lag processing events. This is the time difference
|
||||||
# between the last processed event's received_ts and the time it was
|
# between the last processed event's received_ts and the time it was
|
||||||
# finished being processed.
|
# finished being processed.
|
||||||
event_processing_lag = synapse_metrics.register_gauge(
|
event_processing_lag = Gauge("synapse_event_processing_lag", "", ["name"])
|
||||||
"event_processing_lag", labels=["name"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def runUntilCurrentTimer(func):
|
def runUntilCurrentTimer(func):
|
||||||
|
@ -197,17 +210,17 @@ def runUntilCurrentTimer(func):
|
||||||
num_pending += 1
|
num_pending += 1
|
||||||
|
|
||||||
num_pending += len(reactor.threadCallQueue)
|
num_pending += len(reactor.threadCallQueue)
|
||||||
start = time.time() * 1000
|
start = time.time()
|
||||||
ret = func(*args, **kwargs)
|
ret = func(*args, **kwargs)
|
||||||
end = time.time() * 1000
|
end = time.time()
|
||||||
|
|
||||||
# record the amount of wallclock time spent running pending calls.
|
# record the amount of wallclock time spent running pending calls.
|
||||||
# This is a proxy for the actual amount of time between reactor polls,
|
# This is a proxy for the actual amount of time between reactor polls,
|
||||||
# since about 25% of time is actually spent running things triggered by
|
# since about 25% of time is actually spent running things triggered by
|
||||||
# I/O events, but that is harder to capture without rewriting half the
|
# I/O events, but that is harder to capture without rewriting half the
|
||||||
# reactor.
|
# reactor.
|
||||||
tick_time.inc_by(end - start)
|
tick_time.observe(end - start)
|
||||||
pending_calls_metric.inc_by(num_pending)
|
pending_calls_metric.observe(num_pending)
|
||||||
|
|
||||||
if running_on_pypy:
|
if running_on_pypy:
|
||||||
return ret
|
return ret
|
||||||
|
@ -220,12 +233,12 @@ def runUntilCurrentTimer(func):
|
||||||
if threshold[i] < counts[i]:
|
if threshold[i] < counts[i]:
|
||||||
logger.info("Collecting gc %d", i)
|
logger.info("Collecting gc %d", i)
|
||||||
|
|
||||||
start = time.time() * 1000
|
start = time.time()
|
||||||
unreachable = gc.collect(i)
|
unreachable = gc.collect(i)
|
||||||
end = time.time() * 1000
|
end = time.time()
|
||||||
|
|
||||||
gc_time.inc_by(end - start, i)
|
gc_time.labels(i).observe(end - start)
|
||||||
gc_unreachable.inc_by(unreachable, i)
|
gc_unreachable.labels(i).set(unreachable)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -1,301 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
|
|
||||||
from itertools import chain
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def flatten(items):
|
|
||||||
"""Flatten a list of lists
|
|
||||||
|
|
||||||
Args:
|
|
||||||
items: iterable[iterable[X]]
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[X]: flattened list
|
|
||||||
"""
|
|
||||||
return list(chain.from_iterable(items))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMetric(object):
|
|
||||||
"""Base class for metrics which report a single value per label set
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, labels=[], alternative_names=[]):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
name (str): principal name for this metric
|
|
||||||
labels (list(str)): names of the labels which will be reported
|
|
||||||
for this metric
|
|
||||||
alternative_names (iterable(str)): list of alternative names for
|
|
||||||
this metric. This can be useful to provide a migration path
|
|
||||||
when renaming metrics.
|
|
||||||
"""
|
|
||||||
self._names = [name] + list(alternative_names)
|
|
||||||
self.labels = labels # OK not to clone as we never write it
|
|
||||||
|
|
||||||
def dimension(self):
|
|
||||||
return len(self.labels)
|
|
||||||
|
|
||||||
def is_scalar(self):
|
|
||||||
return not len(self.labels)
|
|
||||||
|
|
||||||
def _render_labelvalue(self, value):
|
|
||||||
# TODO: escape backslashes, quotes and newlines
|
|
||||||
return '"%s"' % (value)
|
|
||||||
|
|
||||||
def _render_key(self, values):
|
|
||||||
if self.is_scalar():
|
|
||||||
return ""
|
|
||||||
return "{%s}" % (
|
|
||||||
",".join(["%s=%s" % (k, self._render_labelvalue(v))
|
|
||||||
for k, v in zip(self.labels, values)])
|
|
||||||
)
|
|
||||||
|
|
||||||
def _render_for_labels(self, label_values, value):
|
|
||||||
"""Render this metric for a single set of labels
|
|
||||||
|
|
||||||
Args:
|
|
||||||
label_values (list[str]): values for each of the labels
|
|
||||||
value: value of the metric at with these labels
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
iterable[str]: rendered metric
|
|
||||||
"""
|
|
||||||
rendered_labels = self._render_key(label_values)
|
|
||||||
return (
|
|
||||||
"%s%s %.12g" % (name, rendered_labels, value)
|
|
||||||
for name in self._names
|
|
||||||
)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
"""Render this metric
|
|
||||||
|
|
||||||
Each metric is rendered as:
|
|
||||||
|
|
||||||
name{label1="val1",label2="val2"} value
|
|
||||||
|
|
||||||
https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-details
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
iterable[str]: rendered metrics
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class CounterMetric(BaseMetric):
|
|
||||||
"""The simplest kind of metric; one that stores a monotonically-increasing
|
|
||||||
value that counts events or running totals.
|
|
||||||
|
|
||||||
Example use cases for Counters:
|
|
||||||
- Number of requests processed
|
|
||||||
- Number of items that were inserted into a queue
|
|
||||||
- Total amount of data that a system has processed
|
|
||||||
Counters can only go up (and be reset when the process restarts).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(CounterMetric, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# dict[list[str]]: value for each set of label values. the keys are the
|
|
||||||
# label values, in the same order as the labels in self.labels.
|
|
||||||
#
|
|
||||||
# (if the metric is a scalar, the (single) key is the empty tuple).
|
|
||||||
self.counts = {}
|
|
||||||
|
|
||||||
# Scalar metrics are never empty
|
|
||||||
if self.is_scalar():
|
|
||||||
self.counts[()] = 0.
|
|
||||||
|
|
||||||
def inc_by(self, incr, *values):
|
|
||||||
if len(values) != self.dimension():
|
|
||||||
raise ValueError(
|
|
||||||
"Expected as many values to inc() as labels (%d)" % (self.dimension())
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: should assert that the tag values are all strings
|
|
||||||
|
|
||||||
if values not in self.counts:
|
|
||||||
self.counts[values] = incr
|
|
||||||
else:
|
|
||||||
self.counts[values] += incr
|
|
||||||
|
|
||||||
def inc(self, *values):
|
|
||||||
self.inc_by(1, *values)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return flatten(
|
|
||||||
self._render_for_labels(k, self.counts[k])
|
|
||||||
for k in sorted(self.counts.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GaugeMetric(BaseMetric):
|
|
||||||
"""A metric that can go up or down
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(GaugeMetric, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# dict[list[str]]: value for each set of label values. the keys are the
|
|
||||||
# label values, in the same order as the labels in self.labels.
|
|
||||||
#
|
|
||||||
# (if the metric is a scalar, the (single) key is the empty tuple).
|
|
||||||
self.guages = {}
|
|
||||||
|
|
||||||
def set(self, v, *values):
|
|
||||||
if len(values) != self.dimension():
|
|
||||||
raise ValueError(
|
|
||||||
"Expected as many values to inc() as labels (%d)" % (self.dimension())
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: should assert that the tag values are all strings
|
|
||||||
|
|
||||||
self.guages[values] = v
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return flatten(
|
|
||||||
self._render_for_labels(k, self.guages[k])
|
|
||||||
for k in sorted(self.guages.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CallbackMetric(BaseMetric):
|
|
||||||
"""A metric that returns the numeric value returned by a callback whenever
|
|
||||||
it is rendered. Typically this is used to implement gauges that yield the
|
|
||||||
size or other state of some in-memory object by actively querying it."""
|
|
||||||
|
|
||||||
def __init__(self, name, callback, labels=[]):
|
|
||||||
super(CallbackMetric, self).__init__(name, labels=labels)
|
|
||||||
|
|
||||||
self.callback = callback
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
try:
|
|
||||||
value = self.callback()
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Failed to render %s", self.name)
|
|
||||||
return ["# FAILED to render " + self.name]
|
|
||||||
|
|
||||||
if self.is_scalar():
|
|
||||||
return list(self._render_for_labels([], value))
|
|
||||||
|
|
||||||
return flatten(
|
|
||||||
self._render_for_labels(k, value[k])
|
|
||||||
for k in sorted(value.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DistributionMetric(object):
|
|
||||||
"""A combination of an event counter and an accumulator, which counts
|
|
||||||
both the number of events and accumulates the total value. Typically this
|
|
||||||
could be used to keep track of method-running times, or other distributions
|
|
||||||
of values that occur in discrete occurances.
|
|
||||||
|
|
||||||
TODO(paul): Try to export some heatmap-style stats?
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, *args, **kwargs):
|
|
||||||
self.counts = CounterMetric(name + ":count", **kwargs)
|
|
||||||
self.totals = CounterMetric(name + ":total", **kwargs)
|
|
||||||
|
|
||||||
def inc_by(self, inc, *values):
|
|
||||||
self.counts.inc(*values)
|
|
||||||
self.totals.inc_by(inc, *values)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return self.counts.render() + self.totals.render()
|
|
||||||
|
|
||||||
|
|
||||||
class CacheMetric(object):
|
|
||||||
__slots__ = (
|
|
||||||
"name", "cache_name", "hits", "misses", "evicted_size", "size_callback",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, name, size_callback, cache_name):
|
|
||||||
self.name = name
|
|
||||||
self.cache_name = cache_name
|
|
||||||
|
|
||||||
self.hits = 0
|
|
||||||
self.misses = 0
|
|
||||||
self.evicted_size = 0
|
|
||||||
|
|
||||||
self.size_callback = size_callback
|
|
||||||
|
|
||||||
def inc_hits(self):
|
|
||||||
self.hits += 1
|
|
||||||
|
|
||||||
def inc_misses(self):
|
|
||||||
self.misses += 1
|
|
||||||
|
|
||||||
def inc_evictions(self, size=1):
|
|
||||||
self.evicted_size += size
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
size = self.size_callback()
|
|
||||||
hits = self.hits
|
|
||||||
total = self.misses + self.hits
|
|
||||||
|
|
||||||
return [
|
|
||||||
"""%s:hits{name="%s"} %d""" % (self.name, self.cache_name, hits),
|
|
||||||
"""%s:total{name="%s"} %d""" % (self.name, self.cache_name, total),
|
|
||||||
"""%s:size{name="%s"} %d""" % (self.name, self.cache_name, size),
|
|
||||||
"""%s:evicted_size{name="%s"} %d""" % (
|
|
||||||
self.name, self.cache_name, self.evicted_size
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryUsageMetric(object):
|
|
||||||
"""Keeps track of the current memory usage, using psutil.
|
|
||||||
|
|
||||||
The class will keep the current min/max/sum/counts of rss over the last
|
|
||||||
WINDOW_SIZE_SEC, by polling UPDATE_HZ times per second
|
|
||||||
"""
|
|
||||||
|
|
||||||
UPDATE_HZ = 2 # number of times to get memory per second
|
|
||||||
WINDOW_SIZE_SEC = 30 # the size of the window in seconds
|
|
||||||
|
|
||||||
def __init__(self, hs, psutil):
|
|
||||||
clock = hs.get_clock()
|
|
||||||
self.memory_snapshots = []
|
|
||||||
|
|
||||||
self.process = psutil.Process()
|
|
||||||
|
|
||||||
clock.looping_call(self._update_curr_values, 1000 / self.UPDATE_HZ)
|
|
||||||
|
|
||||||
def _update_curr_values(self):
|
|
||||||
max_size = self.UPDATE_HZ * self.WINDOW_SIZE_SEC
|
|
||||||
self.memory_snapshots.append(self.process.memory_info().rss)
|
|
||||||
self.memory_snapshots[:] = self.memory_snapshots[-max_size:]
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
if not self.memory_snapshots:
|
|
||||||
return []
|
|
||||||
|
|
||||||
max_rss = max(self.memory_snapshots)
|
|
||||||
min_rss = min(self.memory_snapshots)
|
|
||||||
sum_rss = sum(self.memory_snapshots)
|
|
||||||
len_rss = len(self.memory_snapshots)
|
|
||||||
|
|
||||||
return [
|
|
||||||
"process_psutil_rss:max %d" % max_rss,
|
|
||||||
"process_psutil_rss:min %d" % min_rss,
|
|
||||||
"process_psutil_rss:total %d" % sum_rss,
|
|
||||||
"process_psutil_rss:count %d" % len_rss,
|
|
||||||
]
|
|
|
@ -1,122 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
TICKS_PER_SEC = 100
|
|
||||||
BYTES_PER_PAGE = 4096
|
|
||||||
|
|
||||||
HAVE_PROC_STAT = os.path.exists("/proc/stat")
|
|
||||||
HAVE_PROC_SELF_STAT = os.path.exists("/proc/self/stat")
|
|
||||||
HAVE_PROC_SELF_LIMITS = os.path.exists("/proc/self/limits")
|
|
||||||
HAVE_PROC_SELF_FD = os.path.exists("/proc/self/fd")
|
|
||||||
|
|
||||||
# Field indexes from /proc/self/stat, taken from the proc(5) manpage
|
|
||||||
STAT_FIELDS = {
|
|
||||||
"utime": 14,
|
|
||||||
"stime": 15,
|
|
||||||
"starttime": 22,
|
|
||||||
"vsize": 23,
|
|
||||||
"rss": 24,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
stats = {}
|
|
||||||
|
|
||||||
# In order to report process_start_time_seconds we need to know the
|
|
||||||
# machine's boot time, because the value in /proc/self/stat is relative to
|
|
||||||
# this
|
|
||||||
boot_time = None
|
|
||||||
if HAVE_PROC_STAT:
|
|
||||||
with open("/proc/stat") as _procstat:
|
|
||||||
for line in _procstat:
|
|
||||||
if line.startswith("btime "):
|
|
||||||
boot_time = int(line.split()[1])
|
|
||||||
|
|
||||||
|
|
||||||
def update_resource_metrics():
|
|
||||||
if HAVE_PROC_SELF_STAT:
|
|
||||||
global stats
|
|
||||||
with open("/proc/self/stat") as s:
|
|
||||||
line = s.read()
|
|
||||||
# line is PID (command) more stats go here ...
|
|
||||||
raw_stats = line.split(") ", 1)[1].split(" ")
|
|
||||||
|
|
||||||
for (name, index) in STAT_FIELDS.iteritems():
|
|
||||||
# subtract 3 from the index, because proc(5) is 1-based, and
|
|
||||||
# we've lost the first two fields in PID and COMMAND above
|
|
||||||
stats[name] = int(raw_stats[index - 3])
|
|
||||||
|
|
||||||
|
|
||||||
def _count_fds():
|
|
||||||
# Not every OS will have a /proc/self/fd directory
|
|
||||||
if not HAVE_PROC_SELF_FD:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return len(os.listdir("/proc/self/fd"))
|
|
||||||
|
|
||||||
|
|
||||||
def register_process_collector(process_metrics):
|
|
||||||
process_metrics.register_collector(update_resource_metrics)
|
|
||||||
|
|
||||||
if HAVE_PROC_SELF_STAT:
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"cpu_user_seconds_total",
|
|
||||||
lambda: float(stats["utime"]) / TICKS_PER_SEC
|
|
||||||
)
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"cpu_system_seconds_total",
|
|
||||||
lambda: float(stats["stime"]) / TICKS_PER_SEC
|
|
||||||
)
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"cpu_seconds_total",
|
|
||||||
lambda: (float(stats["utime"] + stats["stime"])) / TICKS_PER_SEC
|
|
||||||
)
|
|
||||||
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"virtual_memory_bytes",
|
|
||||||
lambda: int(stats["vsize"])
|
|
||||||
)
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"resident_memory_bytes",
|
|
||||||
lambda: int(stats["rss"]) * BYTES_PER_PAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"start_time_seconds",
|
|
||||||
lambda: boot_time + int(stats["starttime"]) / TICKS_PER_SEC
|
|
||||||
)
|
|
||||||
|
|
||||||
if HAVE_PROC_SELF_FD:
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"open_fds",
|
|
||||||
lambda: _count_fds()
|
|
||||||
)
|
|
||||||
|
|
||||||
if HAVE_PROC_SELF_LIMITS:
|
|
||||||
def _get_max_fds():
|
|
||||||
with open("/proc/self/limits") as limits:
|
|
||||||
for line in limits:
|
|
||||||
if not line.startswith("Max open files "):
|
|
||||||
continue
|
|
||||||
# Line is Max open files $SOFT $HARD
|
|
||||||
return int(line.split()[3])
|
|
||||||
return None
|
|
||||||
|
|
||||||
process_metrics.register_callback(
|
|
||||||
"max_fds",
|
|
||||||
lambda: _get_max_fds()
|
|
||||||
)
|
|
|
@ -13,27 +13,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.web.resource import Resource
|
from prometheus_client.twisted import MetricsResource
|
||||||
|
|
||||||
import synapse.metrics
|
|
||||||
|
|
||||||
|
|
||||||
METRICS_PREFIX = "/_synapse/metrics"
|
METRICS_PREFIX = "/_synapse/metrics"
|
||||||
|
|
||||||
|
__all__ = ["MetricsResource", "METRICS_PREFIX"]
|
||||||
class MetricsResource(Resource):
|
|
||||||
isLeaf = True
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
|
||||||
Resource.__init__(self) # Resource is old-style, so no super()
|
|
||||||
|
|
||||||
self.hs = hs
|
|
||||||
|
|
||||||
def render_GET(self, request):
|
|
||||||
response = synapse.metrics.render_all()
|
|
||||||
|
|
||||||
request.setHeader("Content-Type", "text/plain")
|
|
||||||
request.setHeader("Content-Length", str(len(response)))
|
|
||||||
|
|
||||||
# Encode as UTF-8 (default)
|
|
||||||
return response.encode()
|
|
||||||
|
|
|
@ -14,33 +14,34 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
from synapse.api.errors import AuthError
|
from synapse.api.errors import AuthError
|
||||||
from synapse.handlers.presence import format_user_presence_state
|
from synapse.handlers.presence import format_user_presence_state
|
||||||
|
|
||||||
from synapse.util import DeferredTimedOutError
|
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.util.async import ObservableDeferred
|
from synapse.util.async import (
|
||||||
from synapse.util.logcontext import PreserveLoggingContext, preserve_fn
|
ObservableDeferred, add_timeout_to_deferred,
|
||||||
|
DeferredTimeoutError,
|
||||||
|
)
|
||||||
|
from synapse.util.logcontext import PreserveLoggingContext, run_in_background
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
from synapse.types import StreamToken
|
from synapse.types import StreamToken
|
||||||
from synapse.visibility import filter_events_for_client
|
from synapse.visibility import filter_events_for_client
|
||||||
import synapse.metrics
|
from synapse.metrics import LaterGauge
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from prometheus_client import Counter
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
metrics = synapse.metrics.get_metrics_for(__name__)
|
notified_events_counter = Counter("synapse_notifier_notified_events", "")
|
||||||
|
|
||||||
notified_events_counter = metrics.register_counter("notified_events")
|
users_woken_by_stream_counter = Counter(
|
||||||
|
"synapse_notifier_users_woken_by_stream", "", ["stream"])
|
||||||
users_woken_by_stream_counter = metrics.register_counter(
|
|
||||||
"users_woken_by_stream", labels=["stream"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(paul): Should be shared somewhere
|
# TODO(paul): Should be shared somewhere
|
||||||
|
@ -105,7 +106,7 @@ class _NotifierUserStream(object):
|
||||||
self.last_notified_ms = time_now_ms
|
self.last_notified_ms = time_now_ms
|
||||||
noify_deferred = self.notify_deferred
|
noify_deferred = self.notify_deferred
|
||||||
|
|
||||||
users_woken_by_stream_counter.inc(stream_key)
|
users_woken_by_stream_counter.labels(stream_key).inc()
|
||||||
|
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
self.notify_deferred = ObservableDeferred(defer.Deferred())
|
self.notify_deferred = ObservableDeferred(defer.Deferred())
|
||||||
|
@ -194,14 +195,14 @@ class Notifier(object):
|
||||||
all_user_streams.add(x)
|
all_user_streams.add(x)
|
||||||
|
|
||||||
return sum(stream.count_listeners() for stream in all_user_streams)
|
return sum(stream.count_listeners() for stream in all_user_streams)
|
||||||
metrics.register_callback("listeners", count_listeners)
|
LaterGauge("synapse_notifier_listeners", "", [], count_listeners)
|
||||||
|
|
||||||
metrics.register_callback(
|
LaterGauge(
|
||||||
"rooms",
|
"synapse_notifier_rooms", "", [],
|
||||||
lambda: count(bool, self.room_to_user_streams.values()),
|
lambda: count(bool, self.room_to_user_streams.values()),
|
||||||
)
|
)
|
||||||
metrics.register_callback(
|
LaterGauge(
|
||||||
"users",
|
"synapse_notifier_users", "", [],
|
||||||
lambda: len(self.user_to_user_stream),
|
lambda: len(self.user_to_user_stream),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -251,9 +252,7 @@ class Notifier(object):
|
||||||
def _on_new_room_event(self, event, room_stream_id, extra_users=[]):
|
def _on_new_room_event(self, event, room_stream_id, extra_users=[]):
|
||||||
"""Notify any user streams that are interested in this room event"""
|
"""Notify any user streams that are interested in this room event"""
|
||||||
# poke any interested application service.
|
# poke any interested application service.
|
||||||
preserve_fn(self.appservice_handler.notify_interested_services)(
|
run_in_background(self._notify_app_services, room_stream_id)
|
||||||
room_stream_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.federation_sender:
|
if self.federation_sender:
|
||||||
self.federation_sender.notify_new_events(room_stream_id)
|
self.federation_sender.notify_new_events(room_stream_id)
|
||||||
|
@ -267,6 +266,13 @@ class Notifier(object):
|
||||||
rooms=[event.room_id],
|
rooms=[event.room_id],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _notify_app_services(self, room_stream_id):
|
||||||
|
try:
|
||||||
|
yield self.appservice_handler.notify_interested_services(room_stream_id)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error notifying application services of event")
|
||||||
|
|
||||||
def on_new_event(self, stream_key, new_token, users=[], rooms=[]):
|
def on_new_event(self, stream_key, new_token, users=[], rooms=[]):
|
||||||
""" Used to inform listeners that something has happend event wise.
|
""" Used to inform listeners that something has happend event wise.
|
||||||
|
|
||||||
|
@ -331,11 +337,12 @@ class Notifier(object):
|
||||||
# Now we wait for the _NotifierUserStream to be told there
|
# Now we wait for the _NotifierUserStream to be told there
|
||||||
# is a new token.
|
# is a new token.
|
||||||
listener = user_stream.new_listener(prev_token)
|
listener = user_stream.new_listener(prev_token)
|
||||||
with PreserveLoggingContext():
|
add_timeout_to_deferred(
|
||||||
yield self.clock.time_bound_deferred(
|
|
||||||
listener.deferred,
|
listener.deferred,
|
||||||
time_out=(end_time - now) / 1000.
|
(end_time - now) / 1000.,
|
||||||
)
|
)
|
||||||
|
with PreserveLoggingContext():
|
||||||
|
yield listener.deferred
|
||||||
|
|
||||||
current_token = user_stream.current_token
|
current_token = user_stream.current_token
|
||||||
|
|
||||||
|
@ -346,7 +353,7 @@ class Notifier(object):
|
||||||
# Update the prev_token to the current_token since nothing
|
# Update the prev_token to the current_token since nothing
|
||||||
# has happened between the old prev_token and the current_token
|
# has happened between the old prev_token and the current_token
|
||||||
prev_token = current_token
|
prev_token = current_token
|
||||||
except DeferredTimedOutError:
|
except DeferredTimeoutError:
|
||||||
break
|
break
|
||||||
except defer.CancelledError:
|
except defer.CancelledError:
|
||||||
break
|
break
|
||||||
|
@ -551,13 +558,14 @@ class Notifier(object):
|
||||||
if end_time <= now:
|
if end_time <= now:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
add_timeout_to_deferred(
|
||||||
|
listener.deferred.addTimeout,
|
||||||
|
(end_time - now) / 1000.,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
yield self.clock.time_bound_deferred(
|
yield listener.deferred
|
||||||
listener.deferred,
|
except DeferredTimeoutError:
|
||||||
time_out=(end_time - now) / 1000.
|
|
||||||
)
|
|
||||||
except DeferredTimedOutError:
|
|
||||||
break
|
break
|
||||||
except defer.CancelledError:
|
except defer.CancelledError:
|
||||||
break
|
break
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue