mirror of
https://github.com/element-hq/synapse.git
synced 2025-12-13 01:50:46 +00:00
Compare commits
1 Commits
v0.27.0-rc
...
erikj/init
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
171829bb94 |
47
.github/ISSUE_TEMPLATE.md
vendored
47
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,47 +0,0 @@
|
|||||||
<!--
|
|
||||||
|
|
||||||
**IF YOU HAVE SUPPORT QUESTIONS ABOUT RUNNING OR CONFIGURING YOUR OWN HOME SERVER**:
|
|
||||||
You will likely get better support more quickly if you ask in ** #matrix:matrix.org ** ;)
|
|
||||||
|
|
||||||
|
|
||||||
This is a bug report template. By following the instructions below and
|
|
||||||
filling out the sections with your information, you will help the us to get all
|
|
||||||
the necessary data to fix your issue.
|
|
||||||
|
|
||||||
You can also preview your report before submitting it. You may remove sections
|
|
||||||
that aren't relevant to your particular case.
|
|
||||||
|
|
||||||
Text between <!-- and --> marks will be invisible in the report.
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
Describe here the problem that you are experiencing, or the feature you are requesting.
|
|
||||||
|
|
||||||
### Steps to reproduce
|
|
||||||
|
|
||||||
- For bugs, list the steps
|
|
||||||
- that reproduce the bug
|
|
||||||
- using hyphens as bullet points
|
|
||||||
|
|
||||||
Describe how what happens differs from what you expected.
|
|
||||||
|
|
||||||
If you can identify any relevant log snippets from _homeserver.log_, please include
|
|
||||||
those here (please be careful to remove any personal or private data):
|
|
||||||
|
|
||||||
### Version information
|
|
||||||
|
|
||||||
<!-- IMPORTANT: please answer the following questions, to help us narrow down the problem -->
|
|
||||||
|
|
||||||
- **Homeserver**: Was this issue identified on matrix.org or another homeserver?
|
|
||||||
|
|
||||||
If not matrix.org:
|
|
||||||
- **Version**: What version of Synapse is running? <!--
|
|
||||||
You can find the Synapse version by inspecting the server headers (replace matrix.org with
|
|
||||||
your own homeserver domain):
|
|
||||||
$ curl -v https://matrix.org/_matrix/client/versions 2>&1 | grep "Server:"
|
|
||||||
-->
|
|
||||||
- **Install method**: package manager/git clone/pip
|
|
||||||
- **Platform**: Tell us about the environment in which your homeserver is operating
|
|
||||||
- distro, hardware, if it's running in a vm/container, etc.
|
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -24,10 +24,10 @@ homeserver*.yaml
|
|||||||
.coverage
|
.coverage
|
||||||
htmlcov
|
htmlcov
|
||||||
|
|
||||||
demo/*/*.db
|
demo/*.db
|
||||||
demo/*/*.log
|
demo/*.log
|
||||||
demo/*/*.log.*
|
demo/*.log.*
|
||||||
demo/*/*.pid
|
demo/*.pid
|
||||||
demo/media_store.*
|
demo/media_store.*
|
||||||
demo/etc
|
demo/etc
|
||||||
|
|
||||||
@@ -42,9 +42,3 @@ build/
|
|||||||
|
|
||||||
localhost-800*/
|
localhost-800*/
|
||||||
static/client/register/register_config.js
|
static/client/register/register_config.js
|
||||||
.tox
|
|
||||||
|
|
||||||
env/
|
|
||||||
*.config
|
|
||||||
|
|
||||||
.vscode/
|
|
||||||
|
|||||||
17
.travis.yml
17
.travis.yml
@@ -1,17 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: python
|
|
||||||
python: 2.7
|
|
||||||
|
|
||||||
# tell travis to cache ~/.cache/pip
|
|
||||||
cache: pip
|
|
||||||
|
|
||||||
env:
|
|
||||||
- TOX_ENV=packaging
|
|
||||||
- TOX_ENV=pep8
|
|
||||||
- TOX_ENV=py27
|
|
||||||
|
|
||||||
install:
|
|
||||||
- pip install tox
|
|
||||||
|
|
||||||
script:
|
|
||||||
- tox -e $TOX_ENV
|
|
||||||
25
AUTHORS.rst
25
AUTHORS.rst
@@ -35,28 +35,3 @@ Turned to Dust <dwinslow86 at gmail.com>
|
|||||||
|
|
||||||
Brabo <brabo at riseup.net>
|
Brabo <brabo at riseup.net>
|
||||||
* Installation instruction fixes
|
* Installation instruction fixes
|
||||||
|
|
||||||
Ivan Shapovalov <intelfx100 at gmail.com>
|
|
||||||
* contrib/systemd: a sample systemd unit file and a logger configuration
|
|
||||||
|
|
||||||
Eric Myhre <hash at exultant.us>
|
|
||||||
* Fix bug where ``media_store_path`` config option was ignored by v0 content
|
|
||||||
repository API.
|
|
||||||
|
|
||||||
Muthu Subramanian <muthu.subramanian.karunanidhi at ericsson.com>
|
|
||||||
* Add SAML2 support for registration and login.
|
|
||||||
|
|
||||||
Steven Hammerton <steven.hammerton at openmarket.com>
|
|
||||||
* Add CAS support for registration and login.
|
|
||||||
|
|
||||||
Mads Robin Christensen <mads at v42 dot dk>
|
|
||||||
* CentOS 7 installation instructions.
|
|
||||||
|
|
||||||
Florent Violleau <floviolleau at gmail dot com>
|
|
||||||
* Add Raspberry Pi installation instructions and general troubleshooting items
|
|
||||||
|
|
||||||
Niklas Riekenbrauck <nikriek at gmail dot.com>
|
|
||||||
* Add JWT support for registration and login
|
|
||||||
|
|
||||||
Christoph Witzany <christoph at web.crofting.com>
|
|
||||||
* Add LDAP support for authentication
|
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ https://developers.google.com/recaptcha/
|
|||||||
Setting ReCaptcha Keys
|
Setting ReCaptcha Keys
|
||||||
----------------------
|
----------------------
|
||||||
The keys are a config option on the home server config. If they are not
|
The keys are a config option on the home server config. If they are not
|
||||||
visible, you can generate them via --generate-config. Set the following value::
|
visible, you can generate them via --generate-config. Set the following value:
|
||||||
|
|
||||||
recaptcha_public_key: YOUR_PUBLIC_KEY
|
recaptcha_public_key: YOUR_PUBLIC_KEY
|
||||||
recaptcha_private_key: YOUR_PRIVATE_KEY
|
recaptcha_private_key: YOUR_PRIVATE_KEY
|
||||||
|
|
||||||
In addition, you MUST enable captchas via::
|
In addition, you MUST enable captchas via:
|
||||||
|
|
||||||
enable_registration_captcha: true
|
enable_registration_captcha: true
|
||||||
|
|
||||||
@@ -25,5 +25,7 @@ Configuring IP used for auth
|
|||||||
The ReCaptcha API requires that the IP address of the user who solved the
|
The ReCaptcha API requires that the IP address of the user who solved the
|
||||||
captcha is sent. If the client is connecting through a proxy or load balancer,
|
captcha is sent. If the client is connecting through a proxy or load balancer,
|
||||||
it may be required to use the X-Forwarded-For (XFF) header instead of the origin
|
it may be required to use the X-Forwarded-For (XFF) header instead of the origin
|
||||||
IP address. This can be configured using the x_forwarded directive in the
|
IP address. This can be configured as an option on the home server like so:
|
||||||
listeners section of the homeserver.yaml configuration file.
|
|
||||||
|
captcha_ip_origin_is_x_forwarded: true
|
||||||
|
|
||||||
1929
CHANGES.rst
1929
CHANGES.rst
File diff suppressed because it is too large
Load Diff
@@ -30,12 +30,8 @@ use github's pull request workflow to review the contribution, and either ask
|
|||||||
you to make any refinements needed or merge it and make them ourselves. The
|
you to make any refinements needed or merge it and make them ourselves. The
|
||||||
changes will then land on master when we next do a release.
|
changes will then land on master when we next do a release.
|
||||||
|
|
||||||
We use `Jenkins <http://matrix.org/jenkins>`_ and
|
We use Jenkins for continuous integration (http://matrix.org/jenkins), and
|
||||||
`Travis <https://travis-ci.org/matrix-org/synapse>`_ for continuous
|
typically all pull requests get automatically tested Jenkins: if your change breaks the build, Jenkins will yell about it in #matrix-dev:matrix.org so please lurk there and keep an eye open.
|
||||||
integration. All pull requests to synapse get automatically tested by Travis;
|
|
||||||
the Jenkins builds require an adminstrator to start them. If your change
|
|
||||||
breaks the build, this will be shown in github, so please keep an eye on the
|
|
||||||
pull request for feedback.
|
|
||||||
|
|
||||||
Code style
|
Code style
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|||||||
23
MANIFEST.in
23
MANIFEST.in
@@ -3,29 +3,12 @@ include LICENSE
|
|||||||
include VERSION
|
include VERSION
|
||||||
include *.rst
|
include *.rst
|
||||||
include demo/README
|
include demo/README
|
||||||
include demo/demo.tls.dh
|
|
||||||
include demo/*.py
|
|
||||||
include demo/*.sh
|
|
||||||
|
|
||||||
recursive-include synapse/storage/schema *.sql
|
recursive-include synapse/storage/schema *.sql
|
||||||
recursive-include synapse/storage/schema *.py
|
|
||||||
|
|
||||||
|
recursive-include demo *.dh
|
||||||
|
recursive-include demo *.py
|
||||||
|
recursive-include demo *.sh
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
recursive-include res *
|
|
||||||
recursive-include scripts *
|
recursive-include scripts *
|
||||||
recursive-include scripts-dev *
|
|
||||||
recursive-include synapse *.pyi
|
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
|
|
||||||
recursive-include synapse/static *.css
|
|
||||||
recursive-include synapse/static *.gif
|
|
||||||
recursive-include synapse/static *.html
|
|
||||||
recursive-include synapse/static *.js
|
|
||||||
|
|
||||||
exclude jenkins.sh
|
|
||||||
exclude jenkins*.sh
|
|
||||||
exclude jenkins*
|
|
||||||
recursive-exclude jenkins *.sh
|
|
||||||
|
|
||||||
prune .github
|
|
||||||
prune demo/etc
|
|
||||||
|
|||||||
1067
README.rst
1067
README.rst
File diff suppressed because it is too large
Load Diff
74
UPGRADE.rst
74
UPGRADE.rst
@@ -1,76 +1,4 @@
|
|||||||
Upgrading Synapse
|
Upgrading to v0.x.x
|
||||||
=================
|
|
||||||
|
|
||||||
Before upgrading check if any special steps are required to upgrade from the
|
|
||||||
what you currently have installed to current version of synapse. The extra
|
|
||||||
instructions that may be required are listed later in this document.
|
|
||||||
|
|
||||||
1. If synapse was installed in a virtualenv then active that virtualenv before
|
|
||||||
upgrading. If synapse is installed in a virtualenv in ``~/.synapse/`` then
|
|
||||||
run:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
source ~/.synapse/bin/activate
|
|
||||||
|
|
||||||
2. If synapse was installed using pip then upgrade to the latest version by
|
|
||||||
running:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
pip install --upgrade --process-dependency-links https://github.com/matrix-org/synapse/tarball/master
|
|
||||||
|
|
||||||
# restart synapse
|
|
||||||
synctl restart
|
|
||||||
|
|
||||||
|
|
||||||
If synapse was installed using git then upgrade to the latest version by
|
|
||||||
running:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
# Pull the latest version of the master branch.
|
|
||||||
git pull
|
|
||||||
# Update the versions of synapse's python dependencies.
|
|
||||||
python synapse/python_dependencies.py | xargs pip install --upgrade
|
|
||||||
|
|
||||||
# restart synapse
|
|
||||||
./synctl restart
|
|
||||||
|
|
||||||
|
|
||||||
To check whether your update was sucessful, you can check the Server header
|
|
||||||
returned by the Client-Server API:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
# replace <host.name> with the hostname of your synapse homeserver.
|
|
||||||
# You may need to specify a port (eg, :8448) if your server is not
|
|
||||||
# configured on port 443.
|
|
||||||
curl -kv https://<host.name>/_matrix/client/versions 2>&1 | grep "Server:"
|
|
||||||
|
|
||||||
Upgrading to v0.15.0
|
|
||||||
====================
|
|
||||||
|
|
||||||
If you want to use the new URL previewing API (/_matrix/media/r0/preview_url)
|
|
||||||
then you have to explicitly enable it in the config and update your dependencies
|
|
||||||
dependencies. See README.rst for details.
|
|
||||||
|
|
||||||
|
|
||||||
Upgrading to v0.11.0
|
|
||||||
====================
|
|
||||||
|
|
||||||
This release includes the option to send anonymous usage stats to matrix.org,
|
|
||||||
and requires that administrators explictly opt in or out by setting the
|
|
||||||
``report_stats`` option to either ``true`` or ``false``.
|
|
||||||
|
|
||||||
We would really appreciate it if you could help our project out by reporting
|
|
||||||
anonymized usage statistics from your homeserver. Only very basic aggregate
|
|
||||||
data (e.g. number of users) will be reported, but it helps us to track the
|
|
||||||
growth of the Matrix community, and helps us to make Matrix a success, as well
|
|
||||||
as to convince other networks that they should peer with us.
|
|
||||||
|
|
||||||
|
|
||||||
Upgrading to v0.9.0
|
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Application services have had a breaking API change in this version.
|
Application services have had a breaking API change in this version.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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.
|
||||||
@@ -32,7 +32,7 @@ import urlparse
|
|||||||
import nacl.signing
|
import nacl.signing
|
||||||
import nacl.encoding
|
import nacl.encoding
|
||||||
|
|
||||||
from signedjson.sign import verify_signed_json, SignatureVerifyException
|
from syutil.crypto.jsonsign import verify_signed_json, SignatureVerifyException
|
||||||
|
|
||||||
CONFIG_JSON = "cmdclient_config.json"
|
CONFIG_JSON = "cmdclient_config.json"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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.
|
||||||
@@ -36,13 +36,15 @@ class HttpClient(object):
|
|||||||
the request body. This will be encoded as JSON.
|
the request body. This will be encoded as JSON.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred: Succeeds when we get a 2xx HTTP response. The result
|
Deferred: Succeeds when we get *any* HTTP response.
|
||||||
will be the decoded JSON body.
|
|
||||||
|
The result of the deferred is a tuple of `(code, response)`,
|
||||||
|
where `response` is a dict representing the decoded JSON body.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_json(self, url, args=None):
|
def get_json(self, url, args=None):
|
||||||
""" Gets some json from the given host homeserver and path
|
""" Get's some json from the given host homeserver and path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url (str): The URL to GET data from.
|
url (str): The URL to GET data from.
|
||||||
@@ -52,8 +54,10 @@ class HttpClient(object):
|
|||||||
and *not* a string.
|
and *not* a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred: Succeeds when we get a 2xx HTTP response. The result
|
Deferred: Succeeds when we get *any* HTTP response.
|
||||||
will be the decoded JSON body.
|
|
||||||
|
The result of the deferred is a tuple of `(code, response)`,
|
||||||
|
where `response` is a dict representing the decoded JSON body.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# Example log_config file for synapse. To enable, point `log_config` to it in
|
|
||||||
# `homeserver.yaml`, and restart synapse.
|
|
||||||
#
|
|
||||||
# This configuration will produce similar results to the defaults within
|
|
||||||
# synapse, but can be edited to give more flexibility.
|
|
||||||
|
|
||||||
version: 1
|
|
||||||
|
|
||||||
formatters:
|
|
||||||
fmt:
|
|
||||||
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s- %(message)s'
|
|
||||||
|
|
||||||
filters:
|
|
||||||
context:
|
|
||||||
(): synapse.util.logcontext.LoggingContextFilter
|
|
||||||
request: ""
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
# example output to console
|
|
||||||
console:
|
|
||||||
class: logging.StreamHandler
|
|
||||||
filters: [context]
|
|
||||||
|
|
||||||
# example output to file - to enable, edit 'root' config below.
|
|
||||||
file:
|
|
||||||
class: logging.handlers.RotatingFileHandler
|
|
||||||
formatter: fmt
|
|
||||||
filename: /var/log/synapse/homeserver.log
|
|
||||||
maxBytes: 100000000
|
|
||||||
backupCount: 3
|
|
||||||
filters: [context]
|
|
||||||
|
|
||||||
|
|
||||||
root:
|
|
||||||
level: INFO
|
|
||||||
handlers: [console] # to use file handler instead, switch to [file]
|
|
||||||
|
|
||||||
loggers:
|
|
||||||
synapse:
|
|
||||||
level: INFO
|
|
||||||
|
|
||||||
synapse.storage.SQL:
|
|
||||||
# beware: increasing this to DEBUG will make synapse log sensitive
|
|
||||||
# information such as access tokens.
|
|
||||||
level: INFO
|
|
||||||
|
|
||||||
# example of enabling debugging for a component:
|
|
||||||
#
|
|
||||||
# synapse.federation.transport.server:
|
|
||||||
# level: DEBUG
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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.
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
# Copyright 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 pydot
|
|
||||||
import cgi
|
|
||||||
import simplejson as json
|
|
||||||
import datetime
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
from synapse.events import FrozenEvent
|
|
||||||
from synapse.util.frozenutils import unfreeze
|
|
||||||
|
|
||||||
|
|
||||||
def make_graph(file_name, room_id, file_prefix, limit):
|
|
||||||
print "Reading lines"
|
|
||||||
with open(file_name) as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
print "Read lines"
|
|
||||||
|
|
||||||
events = [FrozenEvent(json.loads(line)) for line in lines]
|
|
||||||
|
|
||||||
print "Loaded events."
|
|
||||||
|
|
||||||
events.sort(key=lambda e: e.depth)
|
|
||||||
|
|
||||||
print "Sorted events"
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
events = events[-int(limit):]
|
|
||||||
|
|
||||||
node_map = {}
|
|
||||||
|
|
||||||
graph = pydot.Dot(graph_name="Test")
|
|
||||||
|
|
||||||
for event in events:
|
|
||||||
t = datetime.datetime.fromtimestamp(
|
|
||||||
float(event.origin_server_ts) / 1000
|
|
||||||
).strftime('%Y-%m-%d %H:%M:%S,%f')
|
|
||||||
|
|
||||||
content = json.dumps(unfreeze(event.get_dict()["content"]), indent=4)
|
|
||||||
content = content.replace("\n", "<br/>\n")
|
|
||||||
|
|
||||||
print content
|
|
||||||
content = []
|
|
||||||
for key, value in unfreeze(event.get_dict()["content"]).items():
|
|
||||||
if value is None:
|
|
||||||
value = "<null>"
|
|
||||||
elif isinstance(value, basestring):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
value = json.dumps(value)
|
|
||||||
|
|
||||||
content.append(
|
|
||||||
"<b>%s</b>: %s," % (
|
|
||||||
cgi.escape(key, quote=True).encode("ascii", 'xmlcharrefreplace'),
|
|
||||||
cgi.escape(value, quote=True).encode("ascii", 'xmlcharrefreplace'),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
content = "<br/>\n".join(content)
|
|
||||||
|
|
||||||
print content
|
|
||||||
|
|
||||||
label = (
|
|
||||||
"<"
|
|
||||||
"<b>%(name)s </b><br/>"
|
|
||||||
"Type: <b>%(type)s </b><br/>"
|
|
||||||
"State key: <b>%(state_key)s </b><br/>"
|
|
||||||
"Content: <b>%(content)s </b><br/>"
|
|
||||||
"Time: <b>%(time)s </b><br/>"
|
|
||||||
"Depth: <b>%(depth)s </b><br/>"
|
|
||||||
">"
|
|
||||||
) % {
|
|
||||||
"name": event.event_id,
|
|
||||||
"type": event.type,
|
|
||||||
"state_key": event.get("state_key", None),
|
|
||||||
"content": content,
|
|
||||||
"time": t,
|
|
||||||
"depth": event.depth,
|
|
||||||
}
|
|
||||||
|
|
||||||
node = pydot.Node(
|
|
||||||
name=event.event_id,
|
|
||||||
label=label,
|
|
||||||
)
|
|
||||||
|
|
||||||
node_map[event.event_id] = node
|
|
||||||
graph.add_node(node)
|
|
||||||
|
|
||||||
print "Created Nodes"
|
|
||||||
|
|
||||||
for event in events:
|
|
||||||
for prev_id, _ in event.prev_events:
|
|
||||||
try:
|
|
||||||
end_node = node_map[prev_id]
|
|
||||||
except:
|
|
||||||
end_node = pydot.Node(
|
|
||||||
name=prev_id,
|
|
||||||
label="<<b>%s</b>>" % (prev_id,),
|
|
||||||
)
|
|
||||||
|
|
||||||
node_map[prev_id] = end_node
|
|
||||||
graph.add_node(end_node)
|
|
||||||
|
|
||||||
edge = pydot.Edge(node_map[event.event_id], end_node)
|
|
||||||
graph.add_edge(edge)
|
|
||||||
|
|
||||||
print "Created edges"
|
|
||||||
|
|
||||||
graph.write('%s.dot' % file_prefix, format='raw', prog='dot')
|
|
||||||
|
|
||||||
print "Created Dot"
|
|
||||||
|
|
||||||
graph.write_svg("%s.svg" % file_prefix, prog='dot')
|
|
||||||
|
|
||||||
print "Created svg"
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Generate a PDU graph for a given room by reading "
|
|
||||||
"from a file with line deliminated events. \n"
|
|
||||||
"Requires pydot."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-p", "--prefix", dest="prefix",
|
|
||||||
help="String to prefix output files with",
|
|
||||||
default="graph_output"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-l", "--limit",
|
|
||||||
help="Only retrieve the last N events.",
|
|
||||||
)
|
|
||||||
parser.add_argument('event_file')
|
|
||||||
parser.add_argument('room')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
make_graph(args.event_file, args.room, args.prefix, args.limit)
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
This directory contains some sample monitoring config for using the
|
|
||||||
'Prometheus' monitoring server against synapse.
|
|
||||||
|
|
||||||
To use it, first install prometheus by following the instructions at
|
|
||||||
|
|
||||||
http://prometheus.io/
|
|
||||||
|
|
||||||
### for Prometheus v1
|
|
||||||
Add a new job to the main prometheus.conf file:
|
|
||||||
|
|
||||||
job: {
|
|
||||||
name: "synapse"
|
|
||||||
|
|
||||||
target_group: {
|
|
||||||
target: "http://SERVER.LOCATION.HERE:PORT/_synapse/metrics"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### for Prometheus v2
|
|
||||||
Add a new job to the main prometheus.yml file:
|
|
||||||
|
|
||||||
- job_name: "synapse"
|
|
||||||
metrics_path: "/_synapse/metrics"
|
|
||||||
# when endpoint uses https:
|
|
||||||
scheme: "https"
|
|
||||||
|
|
||||||
static_configs:
|
|
||||||
- targets: ['SERVER.LOCATION:PORT']
|
|
||||||
|
|
||||||
To use `synapse.rules` add
|
|
||||||
|
|
||||||
rule_files:
|
|
||||||
- "/PATH/TO/synapse-v2.rules"
|
|
||||||
|
|
||||||
Metrics are disabled by default when running synapse; they must be enabled
|
|
||||||
with the 'enable-metrics' option, either in the synapse config file or as a
|
|
||||||
command-line option.
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
{{ template "head" . }}
|
|
||||||
|
|
||||||
{{ template "prom_content_head" . }}
|
|
||||||
<h1>System Resources</h1>
|
|
||||||
|
|
||||||
<h3>CPU</h3>
|
|
||||||
<div id="process_resource_utime"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#process_resource_utime"),
|
|
||||||
expr: "rate(process_cpu_seconds_total[2m]) * 100",
|
|
||||||
name: "[[job]]",
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
renderer: "line",
|
|
||||||
height: 150,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "%",
|
|
||||||
yTitle: "CPU Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Memory</h3>
|
|
||||||
<div id="process_resource_maxrss"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#process_resource_maxrss"),
|
|
||||||
expr: "process_psutil_rss:max",
|
|
||||||
name: "Maxrss",
|
|
||||||
min: 0,
|
|
||||||
renderer: "line",
|
|
||||||
height: 150,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "bytes",
|
|
||||||
yTitle: "Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>File descriptors</h3>
|
|
||||||
<div id="process_fds"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#process_fds"),
|
|
||||||
expr: "process_open_fds{job='synapse'}",
|
|
||||||
name: "FDs",
|
|
||||||
min: 0,
|
|
||||||
renderer: "line",
|
|
||||||
height: 150,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "",
|
|
||||||
yTitle: "Descriptors"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Reactor</h1>
|
|
||||||
|
|
||||||
<h3>Total reactor time</h3>
|
|
||||||
<div id="reactor_total_time"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#reactor_total_time"),
|
|
||||||
expr: "rate(python_twisted_reactor_tick_time:total[2m]) / 1000",
|
|
||||||
name: "time",
|
|
||||||
max: 1,
|
|
||||||
min: 0,
|
|
||||||
renderer: "area",
|
|
||||||
height: 150,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/s",
|
|
||||||
yTitle: "Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Average reactor tick time</h3>
|
|
||||||
<div id="reactor_average_time"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#reactor_average_time"),
|
|
||||||
expr: "rate(python_twisted_reactor_tick_time:total[2m]) / rate(python_twisted_reactor_tick_time:count[2m]) / 1000",
|
|
||||||
name: "time",
|
|
||||||
min: 0,
|
|
||||||
renderer: "line",
|
|
||||||
height: 150,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s",
|
|
||||||
yTitle: "Time"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Pending calls per tick</h3>
|
|
||||||
<div id="reactor_pending_calls"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#reactor_pending_calls"),
|
|
||||||
expr: "rate(python_twisted_reactor_pending_calls:total[30s])/rate(python_twisted_reactor_pending_calls:count[30s])",
|
|
||||||
name: "calls",
|
|
||||||
min: 0,
|
|
||||||
renderer: "line",
|
|
||||||
height: 150,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yTitle: "Pending Cals"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Storage</h1>
|
|
||||||
|
|
||||||
<h3>Queries</h3>
|
|
||||||
<div id="synapse_storage_query_time"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_storage_query_time"),
|
|
||||||
expr: "rate(synapse_storage_query_time:count[2m])",
|
|
||||||
name: "[[verb]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "queries/s",
|
|
||||||
yTitle: "Queries"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Transactions</h3>
|
|
||||||
<div id="synapse_storage_transaction_time"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_storage_transaction_time"),
|
|
||||||
expr: "rate(synapse_storage_transaction_time:count[2m])",
|
|
||||||
name: "[[desc]]",
|
|
||||||
min: 0,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "txn/s",
|
|
||||||
yTitle: "Transactions"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Transaction execution time</h3>
|
|
||||||
<div id="synapse_storage_transactions_time_msec"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_storage_transactions_time_msec"),
|
|
||||||
expr: "rate(synapse_storage_transaction_time:total[2m]) / 1000",
|
|
||||||
name: "[[desc]]",
|
|
||||||
min: 0,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/s",
|
|
||||||
yTitle: "Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Database scheduling latency</h3>
|
|
||||||
<div id="synapse_storage_schedule_time"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_storage_schedule_time"),
|
|
||||||
expr: "rate(synapse_storage_schedule_time:total[2m]) / 1000",
|
|
||||||
name: "Total latency",
|
|
||||||
min: 0,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/s",
|
|
||||||
yTitle: "Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Cache hit ratio</h3>
|
|
||||||
<div id="synapse_cache_ratio"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_cache_ratio"),
|
|
||||||
expr: "rate(synapse_util_caches_cache:total[2m]) * 100",
|
|
||||||
name: "[[name]]",
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "%",
|
|
||||||
yTitle: "Percentage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Cache size</h3>
|
|
||||||
<div id="synapse_cache_size"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_cache_size"),
|
|
||||||
expr: "synapse_util_caches_cache:size",
|
|
||||||
name: "[[name]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "",
|
|
||||||
yTitle: "Items"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Requests</h1>
|
|
||||||
|
|
||||||
<h3>Requests by Servlet</h3>
|
|
||||||
<div id="synapse_http_server_requests_servlet"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_requests_servlet"),
|
|
||||||
expr: "rate(synapse_http_server_requests:servlet[2m])",
|
|
||||||
name: "[[servlet]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "req/s",
|
|
||||||
yTitle: "Requests"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<h4> (without <tt>EventStreamRestServlet</tt> or <tt>SyncRestServlet</tt>)</h4>
|
|
||||||
<div id="synapse_http_server_requests_servlet_minus_events"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_requests_servlet_minus_events"),
|
|
||||||
expr: "rate(synapse_http_server_requests:servlet{servlet!=\"EventStreamRestServlet\", servlet!=\"SyncRestServlet\"}[2m])",
|
|
||||||
name: "[[servlet]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "req/s",
|
|
||||||
yTitle: "Requests"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Average response times</h3>
|
|
||||||
<div id="synapse_http_server_response_time_avg"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_response_time_avg"),
|
|
||||||
expr: "rate(synapse_http_server_response_time:total[2m]) / rate(synapse_http_server_response_time:count[2m]) / 1000",
|
|
||||||
name: "[[servlet]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/req",
|
|
||||||
yTitle: "Response time"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>All responses by code</h3>
|
|
||||||
<div id="synapse_http_server_responses"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_responses"),
|
|
||||||
expr: "rate(synapse_http_server_responses[2m])",
|
|
||||||
name: "[[method]] / [[code]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "req/s",
|
|
||||||
yTitle: "Requests"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Error responses by code</h3>
|
|
||||||
<div id="synapse_http_server_responses_err"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_responses_err"),
|
|
||||||
expr: "rate(synapse_http_server_responses{code=~\"[45]..\"}[2m])",
|
|
||||||
name: "[[method]] / [[code]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "req/s",
|
|
||||||
yTitle: "Requests"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<h3>CPU Usage</h3>
|
|
||||||
<div id="synapse_http_server_response_ru_utime"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_response_ru_utime"),
|
|
||||||
expr: "rate(synapse_http_server_response_ru_utime:total[2m])",
|
|
||||||
name: "[[servlet]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/s",
|
|
||||||
yTitle: "CPU Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<h3>DB Usage</h3>
|
|
||||||
<div id="synapse_http_server_response_db_txn_duration"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_response_db_txn_duration"),
|
|
||||||
expr: "rate(synapse_http_server_response_db_txn_duration:total[2m])",
|
|
||||||
name: "[[servlet]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/s",
|
|
||||||
yTitle: "DB Usage"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Average event send times</h3>
|
|
||||||
<div id="synapse_http_server_send_time_avg"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_http_server_send_time_avg"),
|
|
||||||
expr: "rate(synapse_http_server_response_time:total{servlet='RoomSendEventRestServlet'}[2m]) / rate(synapse_http_server_response_time:count{servlet='RoomSendEventRestServlet'}[2m]) / 1000",
|
|
||||||
name: "[[servlet]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "s/req",
|
|
||||||
yTitle: "Response time"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Federation</h1>
|
|
||||||
|
|
||||||
<h3>Sent Messages</h3>
|
|
||||||
<div id="synapse_federation_client_sent"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_federation_client_sent"),
|
|
||||||
expr: "rate(synapse_federation_client_sent[2m])",
|
|
||||||
name: "[[type]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "req/s",
|
|
||||||
yTitle: "Requests"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Received Messages</h3>
|
|
||||||
<div id="synapse_federation_server_received"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_federation_server_received"),
|
|
||||||
expr: "rate(synapse_federation_server_received[2m])",
|
|
||||||
name: "[[type]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "req/s",
|
|
||||||
yTitle: "Requests"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Pending</h3>
|
|
||||||
<div id="synapse_federation_transaction_queue_pending"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_federation_transaction_queue_pending"),
|
|
||||||
expr: "synapse_federation_transaction_queue_pending",
|
|
||||||
name: "[[type]]",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "",
|
|
||||||
yTitle: "Units"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Clients</h1>
|
|
||||||
|
|
||||||
<h3>Notifiers</h3>
|
|
||||||
<div id="synapse_notifier_listeners"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_notifier_listeners"),
|
|
||||||
expr: "synapse_notifier_listeners",
|
|
||||||
name: "listeners",
|
|
||||||
min: 0,
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
|
|
||||||
yUnits: "",
|
|
||||||
yTitle: "Listeners"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Notified Events</h3>
|
|
||||||
<div id="synapse_notifier_notified_events"></div>
|
|
||||||
<script>
|
|
||||||
new PromConsole.Graph({
|
|
||||||
node: document.querySelector("#synapse_notifier_notified_events"),
|
|
||||||
expr: "rate(synapse_notifier_notified_events[2m])",
|
|
||||||
name: "events",
|
|
||||||
yAxisFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yHoverFormatter: PromConsole.NumberFormatter.humanize,
|
|
||||||
yUnits: "events/s",
|
|
||||||
yTitle: "Event rate"
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{ template "prom_content_tail" . }}
|
|
||||||
|
|
||||||
{{ template "tail" }}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
synapse_federation_transaction_queue_pendingEdus:total = sum(synapse_federation_transaction_queue_pendingEdus or absent(synapse_federation_transaction_queue_pendingEdus)*0)
|
|
||||||
synapse_federation_transaction_queue_pendingPdus:total = sum(synapse_federation_transaction_queue_pendingPdus or absent(synapse_federation_transaction_queue_pendingPdus)*0)
|
|
||||||
|
|
||||||
synapse_http_server_requests:method{servlet=""} = sum(synapse_http_server_requests) by (method)
|
|
||||||
synapse_http_server_requests:servlet{method=""} = sum(synapse_http_server_requests) by (servlet)
|
|
||||||
|
|
||||||
synapse_http_server_requests:total{servlet=""} = sum(synapse_http_server_requests:by_method) by (servlet)
|
|
||||||
|
|
||||||
synapse_cache:hit_ratio_5m = rate(synapse_util_caches_cache:hits[5m]) / rate(synapse_util_caches_cache:total[5m])
|
|
||||||
synapse_cache:hit_ratio_30s = rate(synapse_util_caches_cache:hits[30s]) / rate(synapse_util_caches_cache:total[30s])
|
|
||||||
|
|
||||||
synapse_federation_client_sent{type="EDU"} = synapse_federation_client_sent_edus + 0
|
|
||||||
synapse_federation_client_sent{type="PDU"} = synapse_federation_client_sent_pdu_destinations:count + 0
|
|
||||||
synapse_federation_client_sent{type="Query"} = sum(synapse_federation_client_sent_queries) by (job)
|
|
||||||
|
|
||||||
synapse_federation_server_received{type="EDU"} = synapse_federation_server_received_edus + 0
|
|
||||||
synapse_federation_server_received{type="PDU"} = synapse_federation_server_received_pdus + 0
|
|
||||||
synapse_federation_server_received{type="Query"} = sum(synapse_federation_server_received_queries) by (job)
|
|
||||||
|
|
||||||
synapse_federation_transaction_queue_pending{type="EDU"} = synapse_federation_transaction_queue_pending_edus + 0
|
|
||||||
synapse_federation_transaction_queue_pending{type="PDU"} = synapse_federation_transaction_queue_pending_pdus + 0
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
groups:
|
|
||||||
- name: synapse
|
|
||||||
rules:
|
|
||||||
- record: "synapse_federation_transaction_queue_pendingEdus:total"
|
|
||||||
expr: "sum(synapse_federation_transaction_queue_pendingEdus or absent(synapse_federation_transaction_queue_pendingEdus)*0)"
|
|
||||||
- record: "synapse_federation_transaction_queue_pendingPdus:total"
|
|
||||||
expr: "sum(synapse_federation_transaction_queue_pendingPdus or absent(synapse_federation_transaction_queue_pendingPdus)*0)"
|
|
||||||
- record: 'synapse_http_server_requests:method'
|
|
||||||
labels:
|
|
||||||
servlet: ""
|
|
||||||
expr: "sum(synapse_http_server_requests) by (method)"
|
|
||||||
- record: 'synapse_http_server_requests:servlet'
|
|
||||||
labels:
|
|
||||||
method: ""
|
|
||||||
expr: 'sum(synapse_http_server_requests) by (servlet)'
|
|
||||||
|
|
||||||
- record: 'synapse_http_server_requests:total'
|
|
||||||
labels:
|
|
||||||
servlet: ""
|
|
||||||
expr: 'sum(synapse_http_server_requests:by_method) by (servlet)'
|
|
||||||
|
|
||||||
- record: 'synapse_cache:hit_ratio_5m'
|
|
||||||
expr: 'rate(synapse_util_caches_cache:hits[5m]) / rate(synapse_util_caches_cache:total[5m])'
|
|
||||||
- record: 'synapse_cache:hit_ratio_30s'
|
|
||||||
expr: 'rate(synapse_util_caches_cache:hits[30s]) / rate(synapse_util_caches_cache:total[30s])'
|
|
||||||
|
|
||||||
- record: 'synapse_federation_client_sent'
|
|
||||||
labels:
|
|
||||||
type: "EDU"
|
|
||||||
expr: 'synapse_federation_client_sent_edus + 0'
|
|
||||||
- record: 'synapse_federation_client_sent'
|
|
||||||
labels:
|
|
||||||
type: "PDU"
|
|
||||||
expr: 'synapse_federation_client_sent_pdu_destinations:count + 0'
|
|
||||||
- record: 'synapse_federation_client_sent'
|
|
||||||
labels:
|
|
||||||
type: "Query"
|
|
||||||
expr: 'sum(synapse_federation_client_sent_queries) by (job)'
|
|
||||||
|
|
||||||
- record: 'synapse_federation_server_received'
|
|
||||||
labels:
|
|
||||||
type: "EDU"
|
|
||||||
expr: 'synapse_federation_server_received_edus + 0'
|
|
||||||
- record: 'synapse_federation_server_received'
|
|
||||||
labels:
|
|
||||||
type: "PDU"
|
|
||||||
expr: 'synapse_federation_server_received_pdus + 0'
|
|
||||||
- record: 'synapse_federation_server_received'
|
|
||||||
labels:
|
|
||||||
type: "Query"
|
|
||||||
expr: 'sum(synapse_federation_server_received_queries) by (job)'
|
|
||||||
|
|
||||||
- record: 'synapse_federation_transaction_queue_pending'
|
|
||||||
labels:
|
|
||||||
type: "EDU"
|
|
||||||
expr: 'synapse_federation_transaction_queue_pending_edus + 0'
|
|
||||||
- record: 'synapse_federation_transaction_queue_pending'
|
|
||||||
labels:
|
|
||||||
type: "PDU"
|
|
||||||
expr: 'synapse_federation_transaction_queue_pending_pdus + 0'
|
|
||||||
@@ -21,5 +21,3 @@ handlers:
|
|||||||
root:
|
root:
|
||||||
level: INFO
|
level: INFO
|
||||||
handlers: [journal]
|
handlers: [journal]
|
||||||
|
|
||||||
disable_existing_loggers: False
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This assumes that Synapse has been installed as a system package
|
# This assumes that Synapse has been installed as a system package
|
||||||
# (e.g. https://www.archlinux.org/packages/community/any/matrix-synapse/ for ArchLinux)
|
# (e.g. https://aur.archlinux.org/packages/matrix-synapse/ for ArchLinux)
|
||||||
# rather than in a user home directory or similar under virtualenv.
|
# rather than in a user home directory or similar under virtualenv.
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -10,9 +10,7 @@ Type=simple
|
|||||||
User=synapse
|
User=synapse
|
||||||
Group=synapse
|
Group=synapse
|
||||||
WorkingDirectory=/var/lib/synapse
|
WorkingDirectory=/var/lib/synapse
|
||||||
ExecStart=/usr/bin/python2.7 -m synapse.app.homeserver --config-path=/etc/synapse/homeserver.yaml
|
ExecStart=/usr/bin/python2.7 -m synapse.app.homeserver --config-path=/etc/synapse/homeserver.yaml --log-config=/etc/synapse/log_config.yaml
|
||||||
ExecStop=/usr/bin/synctl stop /etc/synapse/homeserver.yaml
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
|||||||
@@ -126,26 +126,12 @@ sub on_unknown_event
|
|||||||
if (!$bridgestate->{$room_id}->{gathered_candidates}) {
|
if (!$bridgestate->{$room_id}->{gathered_candidates}) {
|
||||||
$bridgestate->{$room_id}->{gathered_candidates} = 1;
|
$bridgestate->{$room_id}->{gathered_candidates} = 1;
|
||||||
my $offer = $bridgestate->{$room_id}->{offer};
|
my $offer = $bridgestate->{$room_id}->{offer};
|
||||||
my $candidate_block = {
|
my $candidate_block = "";
|
||||||
audio => '',
|
|
||||||
video => '',
|
|
||||||
};
|
|
||||||
foreach (@{$event->{content}->{candidates}}) {
|
foreach (@{$event->{content}->{candidates}}) {
|
||||||
if ($_->{sdpMid}) {
|
$candidate_block .= "a=" . $_->{candidate} . "\r\n";
|
||||||
$candidate_block->{$_->{sdpMid}} .= "a=" . $_->{candidate} . "\r\n";
|
|
||||||
}
|
}
|
||||||
else {
|
# XXX: collate using the right m= line - for now assume audio call
|
||||||
$candidate_block->{audio} .= "a=" . $_->{candidate} . "\r\n";
|
$offer =~ s/(a=rtcp.*[\r\n]+)/$1$candidate_block/;
|
||||||
$candidate_block->{video} .= "a=" . $_->{candidate} . "\r\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# XXX: assumes audio comes first
|
|
||||||
#$offer =~ s/(a=rtcp-mux[\r\n]+)/$1$candidate_block->{audio}/;
|
|
||||||
#$offer =~ s/(a=rtcp-mux[\r\n]+)/$1$candidate_block->{video}/;
|
|
||||||
|
|
||||||
$offer =~ s/(m=video)/$candidate_block->{audio}$1/;
|
|
||||||
$offer =~ s/(.$)/$1\n$candidate_block->{video}$1/;
|
|
||||||
|
|
||||||
my $f = send_verto_json_request("verto.invite", {
|
my $f = send_verto_json_request("verto.invite", {
|
||||||
"sdp" => $offer,
|
"sdp" => $offer,
|
||||||
@@ -186,18 +172,22 @@ sub on_room_message
|
|||||||
warn "[Matrix] in $room_id: $from: " . $content->{body} . "\n";
|
warn "[Matrix] in $room_id: $from: " . $content->{body} . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $verto_connecting = $loop->new_future;
|
||||||
|
$bot_verto->connect(
|
||||||
|
%{ $CONFIG{"verto-bot"} },
|
||||||
|
on_connect_error => sub { die "Cannot connect to verto - $_[-1]" },
|
||||||
|
on_resolve_error => sub { die "Cannot resolve to verto - $_[-1]" },
|
||||||
|
)->then( sub {
|
||||||
|
warn("[Verto] connected to websocket");
|
||||||
|
$verto_connecting->done($bot_verto) if not $verto_connecting->is_done;
|
||||||
|
});
|
||||||
|
|
||||||
Future->needs_all(
|
Future->needs_all(
|
||||||
$bot_matrix->login( %{ $CONFIG{"matrix-bot"} } )->then( sub {
|
$bot_matrix->login( %{ $CONFIG{"matrix-bot"} } )->then( sub {
|
||||||
$bot_matrix->start;
|
$bot_matrix->start;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
$bot_verto->connect(
|
$verto_connecting,
|
||||||
%{ $CONFIG{"verto-bot"} },
|
|
||||||
on_connect_error => sub { die "Cannot connect to verto - $_[-1]" },
|
|
||||||
on_resolve_error => sub { die "Cannot resolve to verto - $_[-1]" },
|
|
||||||
)->on_done( sub {
|
|
||||||
warn("[Verto] connected to websocket");
|
|
||||||
}),
|
|
||||||
)->get;
|
)->get;
|
||||||
|
|
||||||
$loop->attach_signal(
|
$loop->attach_signal(
|
||||||
|
|||||||
@@ -11,4 +11,7 @@ requires 'YAML', 0;
|
|||||||
requires 'JSON', 0;
|
requires 'JSON', 0;
|
||||||
requires 'Getopt::Long', 0;
|
requires 'Getopt::Long', 0;
|
||||||
|
|
||||||
|
on 'test' => sub {
|
||||||
|
requires 'Test::More', '>= 0.98';
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ if [ -f $PID_FILE ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for port in 8080 8081 8082; do
|
find "$DIR" -name "*.log" -delete
|
||||||
rm -rf $DIR/$port
|
find "$DIR" -name "*.db" -delete
|
||||||
rm -rf $DIR/media_store.$port
|
|
||||||
done
|
|
||||||
|
|
||||||
rm -rf $DIR/etc
|
rm -rf $DIR/etc
|
||||||
|
|||||||
@@ -8,49 +8,38 @@ cd "$DIR/.."
|
|||||||
|
|
||||||
mkdir -p demo/etc
|
mkdir -p demo/etc
|
||||||
|
|
||||||
export PYTHONPATH=$(readlink -f $(pwd))
|
# Check the --no-rate-limit param
|
||||||
|
PARAMS=""
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
echo $PYTHONPATH
|
if [ $1 = "--no-rate-limit" ]; then
|
||||||
|
PARAMS="--rc-messages-per-second 1000 --rc-message-burst-count 1000"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
for port in 8080 8081 8082; do
|
for port in 8080 8081 8082; do
|
||||||
echo "Starting server on port $port... "
|
echo "Starting server on port $port... "
|
||||||
|
|
||||||
https_port=$((port + 400))
|
https_port=$((port + 400))
|
||||||
mkdir -p demo/$port
|
|
||||||
pushd demo/$port
|
|
||||||
|
|
||||||
#rm $DIR/etc/$port.config
|
|
||||||
python -m synapse.app.homeserver \
|
python -m synapse.app.homeserver \
|
||||||
--generate-config \
|
--generate-config \
|
||||||
|
--config-path "demo/etc/$port.config" \
|
||||||
|
-p "$https_port" \
|
||||||
|
--unsecure-port "$port" \
|
||||||
-H "localhost:$https_port" \
|
-H "localhost:$https_port" \
|
||||||
--config-path "$DIR/etc/$port.config" \
|
-f "$DIR/$port.log" \
|
||||||
--report-stats no
|
-d "$DIR/$port.db" \
|
||||||
|
-D --pid-file "$DIR/$port.pid" \
|
||||||
# Check script parameters
|
--manhole $((port + 1000)) \
|
||||||
if [ $# -eq 1 ]; then
|
--tls-dh-params-path "demo/demo.tls.dh" \
|
||||||
if [ $1 = "--no-rate-limit" ]; then
|
--media-store-path "demo/media_store.$port" \
|
||||||
# Set high limits in config file to disable rate limiting
|
$PARAMS $SYNAPSE_PARAMS \
|
||||||
perl -p -i -e 's/rc_messages_per_second.*/rc_messages_per_second: 1000/g' $DIR/etc/$port.config
|
--enable-registration
|
||||||
perl -p -i -e 's/rc_message_burst_count.*/rc_message_burst_count: 1000/g' $DIR/etc/$port.config
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
perl -p -i -e 's/^enable_registration:.*/enable_registration: true/g' $DIR/etc/$port.config
|
|
||||||
|
|
||||||
if ! grep -F "full_twisted_stacktraces" -q $DIR/etc/$port.config; then
|
|
||||||
echo "full_twisted_stacktraces: true" >> $DIR/etc/$port.config
|
|
||||||
fi
|
|
||||||
if ! grep -F "report_stats" -q $DIR/etc/$port.config ; then
|
|
||||||
echo "report_stats: false" >> $DIR/etc/$port.config
|
|
||||||
fi
|
|
||||||
|
|
||||||
python -m synapse.app.homeserver \
|
python -m synapse.app.homeserver \
|
||||||
--config-path "$DIR/etc/$port.config" \
|
--config-path "demo/etc/$port.config" \
|
||||||
-D \
|
|
||||||
-vv \
|
-vv \
|
||||||
|
|
||||||
popd
|
|
||||||
done
|
done
|
||||||
|
|
||||||
cd "$CWD"
|
cd "$CWD"
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
Admin APIs
|
|
||||||
==========
|
|
||||||
|
|
||||||
This directory includes documentation for the various synapse specific admin
|
|
||||||
APIs available.
|
|
||||||
|
|
||||||
Only users that are server admins can use these APIs. A user can be marked as a
|
|
||||||
server admin by updating the database directly, e.g.:
|
|
||||||
|
|
||||||
``UPDATE users SET admin = 1 WHERE name = '@foo:bar.com'``
|
|
||||||
|
|
||||||
Restarting may be required for the changes to register.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# List all media in a room
|
|
||||||
|
|
||||||
This API gets a list of known media in a room.
|
|
||||||
|
|
||||||
The API is:
|
|
||||||
```
|
|
||||||
GET /_matrix/client/r0/admin/room/<room_id>/media
|
|
||||||
```
|
|
||||||
including an `access_token` of a server admin.
|
|
||||||
|
|
||||||
It returns a JSON body like the following:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"local": [
|
|
||||||
"mxc://localhost/xwvutsrqponmlkjihgfedcba",
|
|
||||||
"mxc://localhost/abcdefghijklmnopqrstuvwx"
|
|
||||||
],
|
|
||||||
"remote": [
|
|
||||||
"mxc://matrix.org/xwvutsrqponmlkjihgfedcba",
|
|
||||||
"mxc://matrix.org/abcdefghijklmnopqrstuvwx"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
Purge History API
|
|
||||||
=================
|
|
||||||
|
|
||||||
The purge history API allows server admins to purge historic events from their
|
|
||||||
database, reclaiming disk space.
|
|
||||||
|
|
||||||
Depending on the amount of history being purged a call to the API may take
|
|
||||||
several minutes or longer. During this period users will not be able to
|
|
||||||
paginate further back in the room from the point being purged from.
|
|
||||||
|
|
||||||
The API is:
|
|
||||||
|
|
||||||
``POST /_matrix/client/r0/admin/purge_history/<room_id>[/<event_id>]``
|
|
||||||
|
|
||||||
including an ``access_token`` of a server admin.
|
|
||||||
|
|
||||||
By default, events sent by local users are not deleted, as they may represent
|
|
||||||
the only copies of this content in existence. (Events sent by remote users are
|
|
||||||
deleted.)
|
|
||||||
|
|
||||||
Room state data (such as joins, leaves, topic) is always preserved.
|
|
||||||
|
|
||||||
To delete local message events as well, set ``delete_local_events`` in the body:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"delete_local_events": true
|
|
||||||
}
|
|
||||||
|
|
||||||
The caller must specify the point in the room to purge up to. This can be
|
|
||||||
specified by including an event_id in the URI, or by setting a
|
|
||||||
``purge_up_to_event_id`` or ``purge_up_to_ts`` in the request body. If an event
|
|
||||||
id is given, that event (and others at the same graph depth) will be retained.
|
|
||||||
If ``purge_up_to_ts`` is given, it should be a timestamp since the unix epoch,
|
|
||||||
in milliseconds.
|
|
||||||
|
|
||||||
The API starts the purge running, and returns immediately with a JSON body with
|
|
||||||
a purge id:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"purge_id": "<opaque id>"
|
|
||||||
}
|
|
||||||
|
|
||||||
Purge status query
|
|
||||||
------------------
|
|
||||||
|
|
||||||
It is possible to poll for updates on recent purges with a second API;
|
|
||||||
|
|
||||||
``GET /_matrix/client/r0/admin/purge_history_status/<purge_id>``
|
|
||||||
|
|
||||||
(again, with a suitable ``access_token``). This API returns a JSON body like
|
|
||||||
the following:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "active"
|
|
||||||
}
|
|
||||||
|
|
||||||
The status will be one of ``active``, ``complete``, or ``failed``.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
Purge Remote Media API
|
|
||||||
======================
|
|
||||||
|
|
||||||
The purge remote media API allows server admins to purge old cached remote
|
|
||||||
media.
|
|
||||||
|
|
||||||
The API is::
|
|
||||||
|
|
||||||
POST /_matrix/client/r0/admin/purge_media_cache?before_ts=<unix_timestamp_in_ms>&access_token=<access_token>
|
|
||||||
|
|
||||||
{}
|
|
||||||
|
|
||||||
Which will remove all cached media that was last accessed before
|
|
||||||
``<unix_timestamp_in_ms>``.
|
|
||||||
|
|
||||||
If the user re-requests purged remote media, synapse will re-request the media
|
|
||||||
from the originating server.
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
Query Account
|
|
||||||
=============
|
|
||||||
|
|
||||||
This API returns information about a specific user account.
|
|
||||||
|
|
||||||
The api is::
|
|
||||||
|
|
||||||
GET /_matrix/client/r0/admin/whois/<user_id>
|
|
||||||
|
|
||||||
including an ``access_token`` of a server admin.
|
|
||||||
|
|
||||||
It returns a JSON body like the following:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"user_id": "<user_id>",
|
|
||||||
"devices": {
|
|
||||||
"": {
|
|
||||||
"sessions": [
|
|
||||||
{
|
|
||||||
"connections": [
|
|
||||||
{
|
|
||||||
"ip": "1.2.3.4",
|
|
||||||
"last_seen": 1417222374433,
|
|
||||||
"user_agent": "Mozilla/5.0 ..."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ip": "1.2.3.10",
|
|
||||||
"last_seen": 1417222374500,
|
|
||||||
"user_agent": "Dalvik/2.1.0 ..."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
``last_seen`` is measured in milliseconds since the Unix epoch.
|
|
||||||
|
|
||||||
Deactivate Account
|
|
||||||
==================
|
|
||||||
|
|
||||||
This API deactivates an account. It removes active access tokens, resets the
|
|
||||||
password, and deletes third-party IDs (to prevent the user requesting a
|
|
||||||
password reset).
|
|
||||||
|
|
||||||
The api is::
|
|
||||||
|
|
||||||
POST /_matrix/client/r0/admin/deactivate/<user_id>
|
|
||||||
|
|
||||||
including an ``access_token`` of a server admin, and an empty request body.
|
|
||||||
|
|
||||||
|
|
||||||
Reset password
|
|
||||||
==============
|
|
||||||
|
|
||||||
Changes the password of another user.
|
|
||||||
|
|
||||||
The api is::
|
|
||||||
|
|
||||||
POST /_matrix/client/r0/admin/reset_password/<user_id>
|
|
||||||
|
|
||||||
with a body of:
|
|
||||||
|
|
||||||
.. code:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"new_password": "<secret>"
|
|
||||||
}
|
|
||||||
|
|
||||||
including an ``access_token`` of a server admin.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
Registering an Application Service
|
|
||||||
==================================
|
|
||||||
|
|
||||||
The registration of new application services depends on the homeserver used.
|
|
||||||
In synapse, you need to create a new configuration file for your AS and add it
|
|
||||||
to the list specified under the ``app_service_config_files`` config
|
|
||||||
option in your synapse config.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
app_service_config_files:
|
|
||||||
- /home/matrix/.synapse/<your-AS>.yaml
|
|
||||||
|
|
||||||
|
|
||||||
The format of the AS configuration file is as follows:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
url: <base url of AS>
|
|
||||||
as_token: <token AS will add to requests to HS>
|
|
||||||
hs_token: <token HS will add to requests to AS>
|
|
||||||
sender_localpart: <localpart of AS user>
|
|
||||||
namespaces:
|
|
||||||
users: # List of users we're interested in
|
|
||||||
- exclusive: <bool>
|
|
||||||
regex: <regex>
|
|
||||||
- ...
|
|
||||||
aliases: [] # List of aliases we're interested in
|
|
||||||
rooms: [] # List of room ids we're interested in
|
|
||||||
|
|
||||||
See the spec_ for further details on how application services work.
|
|
||||||
|
|
||||||
.. _spec: https://matrix.org/docs/spec/application_service/unstable.html
|
|
||||||
@@ -1,13 +1,26 @@
|
|||||||
- Everything should comply with PEP8. Code should pass
|
Basically, PEP8
|
||||||
``pep8 --max-line-length=100`` without any warnings.
|
|
||||||
|
|
||||||
- **Indenting**:
|
|
||||||
|
|
||||||
- NEVER tabs. 4 spaces to indent.
|
- NEVER tabs. 4 spaces to indent.
|
||||||
|
- Max line width: 79 chars (with flexibility to overflow by a "few chars" if
|
||||||
- follow PEP8; either hanging indent or multiline-visual indent depending
|
the overflowing content is not semantically significant and avoids an
|
||||||
on the size and shape of the arguments and what makes more sense to the
|
explosion of vertical whitespace).
|
||||||
author. In other words, both this::
|
- Use camel case for class and type names
|
||||||
|
- Use underscores for functions and variables.
|
||||||
|
- Use double quotes.
|
||||||
|
- Use parentheses instead of '\\' for line continuation where ever possible
|
||||||
|
(which is pretty much everywhere)
|
||||||
|
- There should be max a single new line between:
|
||||||
|
- statements
|
||||||
|
- functions in a class
|
||||||
|
- There should be two new lines between:
|
||||||
|
- definitions in a module (e.g., between different classes)
|
||||||
|
- There should be spaces where spaces should be and not where there shouldn't be:
|
||||||
|
- a single space after a comma
|
||||||
|
- a single space before and after for '=' when used as assignment
|
||||||
|
- no spaces before and after for '=' for default values and keyword arguments.
|
||||||
|
- Indenting must follow PEP8; either hanging indent or multiline-visual indent
|
||||||
|
depending on the size and shape of the arguments and what makes more sense to
|
||||||
|
the author. In other words, both this::
|
||||||
|
|
||||||
print("I am a fish %s" % "moo")
|
print("I am a fish %s" % "moo")
|
||||||
|
|
||||||
@@ -20,100 +33,17 @@
|
|||||||
|
|
||||||
print(
|
print(
|
||||||
"I am a fish %s" %
|
"I am a fish %s" %
|
||||||
"moo",
|
"moo"
|
||||||
)
|
)
|
||||||
|
|
||||||
...are valid, although given each one takes up 2x more vertical space than
|
...are valid, although given each one takes up 2x more vertical space than
|
||||||
the previous, it's up to the author's discretion as to which layout makes
|
the previous, it's up to the author's discretion as to which layout makes most
|
||||||
most sense for their function invocation. (e.g. if they want to add
|
sense for their function invocation. (e.g. if they want to add comments
|
||||||
comments per-argument, or put expressions in the arguments, or group
|
per-argument, or put expressions in the arguments, or group related arguments
|
||||||
related arguments together, or want to deliberately extend or preserve
|
together, or want to deliberately extend or preserve vertical/horizontal
|
||||||
vertical/horizontal space)
|
space)
|
||||||
|
|
||||||
- **Line length**:
|
Comments should follow the google code style. This is so that we can generate
|
||||||
|
documentation with sphinx (http://sphinxcontrib-napoleon.readthedocs.org/en/latest/)
|
||||||
|
|
||||||
Max line length is 79 chars (with flexibility to overflow by a "few chars" if
|
Code should pass pep8 --max-line-length=100 without any warnings.
|
||||||
the overflowing content is not semantically significant and avoids an
|
|
||||||
explosion of vertical whitespace).
|
|
||||||
|
|
||||||
Use parentheses instead of ``\`` for line continuation where ever possible
|
|
||||||
(which is pretty much everywhere).
|
|
||||||
|
|
||||||
- **Naming**:
|
|
||||||
|
|
||||||
- Use camel case for class and type names
|
|
||||||
- Use underscores for functions and variables.
|
|
||||||
|
|
||||||
- Use double quotes ``"foo"`` rather than single quotes ``'foo'``.
|
|
||||||
|
|
||||||
- **Blank lines**:
|
|
||||||
|
|
||||||
- There should be max a single new line between:
|
|
||||||
|
|
||||||
- statements
|
|
||||||
- functions in a class
|
|
||||||
|
|
||||||
- There should be two new lines between:
|
|
||||||
|
|
||||||
- definitions in a module (e.g., between different classes)
|
|
||||||
|
|
||||||
- **Whitespace**:
|
|
||||||
|
|
||||||
There should be spaces where spaces should be and not where there shouldn't
|
|
||||||
be:
|
|
||||||
|
|
||||||
- a single space after a comma
|
|
||||||
- a single space before and after for '=' when used as assignment
|
|
||||||
- no spaces before and after for '=' for default values and keyword arguments.
|
|
||||||
|
|
||||||
- **Comments**: should follow the `google code style
|
|
||||||
<http://google.github.io/styleguide/pyguide.html?showone=Comments#Comments>`_.
|
|
||||||
This is so that we can generate documentation with `sphinx
|
|
||||||
<http://sphinxcontrib-napoleon.readthedocs.org/en/latest/>`_. See the
|
|
||||||
`examples
|
|
||||||
<http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html>`_
|
|
||||||
in the sphinx documentation.
|
|
||||||
|
|
||||||
- **Imports**:
|
|
||||||
|
|
||||||
- Prefer to import classes and functions than packages or modules.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
from synapse.types import UserID
|
|
||||||
...
|
|
||||||
user_id = UserID(local, server)
|
|
||||||
|
|
||||||
is preferred over::
|
|
||||||
|
|
||||||
from synapse import types
|
|
||||||
...
|
|
||||||
user_id = types.UserID(local, server)
|
|
||||||
|
|
||||||
(or any other variant).
|
|
||||||
|
|
||||||
This goes against the advice in the Google style guide, but it means that
|
|
||||||
errors in the name are caught early (at import time).
|
|
||||||
|
|
||||||
- Multiple imports from the same package can be combined onto one line::
|
|
||||||
|
|
||||||
from synapse.types import GroupID, RoomID, UserID
|
|
||||||
|
|
||||||
An effort should be made to keep the individual imports in alphabetical
|
|
||||||
order.
|
|
||||||
|
|
||||||
If the list becomes long, wrap it with parentheses and split it over
|
|
||||||
multiple lines.
|
|
||||||
|
|
||||||
- As per `PEP-8 <https://www.python.org/dev/peps/pep-0008/#imports>`_,
|
|
||||||
imports should be grouped in the following order, with a blank line between
|
|
||||||
each group:
|
|
||||||
|
|
||||||
1. standard library imports
|
|
||||||
2. related third party imports
|
|
||||||
3. local application/library specific imports
|
|
||||||
|
|
||||||
- Imports within each group should be sorted alphabetically by module name.
|
|
||||||
|
|
||||||
- Avoid wildcard imports (``from synapse.types import *``) and relative
|
|
||||||
imports (``from .types import UserID``).
|
|
||||||
|
|||||||
@@ -1,442 +0,0 @@
|
|||||||
Log contexts
|
|
||||||
============
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
|
|
||||||
To help track the processing of individual requests, synapse uses a
|
|
||||||
'log context' to track which request it is handling at any given moment. This
|
|
||||||
is done via a thread-local variable; a ``logging.Filter`` is then used to fish
|
|
||||||
the information back out of the thread-local variable and add it to each log
|
|
||||||
record.
|
|
||||||
|
|
||||||
Logcontexts are also used for CPU and database accounting, so that we can track
|
|
||||||
which requests were responsible for high CPU use or database activity.
|
|
||||||
|
|
||||||
The ``synapse.util.logcontext`` module provides a facilities for managing the
|
|
||||||
current log context (as well as providing the ``LoggingContextFilter`` class).
|
|
||||||
|
|
||||||
Deferreds make the whole thing complicated, so this document describes how it
|
|
||||||
all works, and how to write code which follows the rules.
|
|
||||||
|
|
||||||
Logcontexts without Deferreds
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
In the absence of any Deferred voodoo, things are simple enough. As with any
|
|
||||||
code of this nature, the rule is that our function should leave things as it
|
|
||||||
found them:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from synapse.util import logcontext # omitted from future snippets
|
|
||||||
|
|
||||||
def handle_request(request_id):
|
|
||||||
request_context = logcontext.LoggingContext()
|
|
||||||
|
|
||||||
calling_context = logcontext.LoggingContext.current_context()
|
|
||||||
logcontext.LoggingContext.set_current_context(request_context)
|
|
||||||
try:
|
|
||||||
request_context.request = request_id
|
|
||||||
do_request_handling()
|
|
||||||
logger.debug("finished")
|
|
||||||
finally:
|
|
||||||
logcontext.LoggingContext.set_current_context(calling_context)
|
|
||||||
|
|
||||||
def do_request_handling():
|
|
||||||
logger.debug("phew") # this will be logged against request_id
|
|
||||||
|
|
||||||
|
|
||||||
LoggingContext implements the context management methods, so the above can be
|
|
||||||
written much more succinctly as:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def handle_request(request_id):
|
|
||||||
with logcontext.LoggingContext() as request_context:
|
|
||||||
request_context.request = request_id
|
|
||||||
do_request_handling()
|
|
||||||
logger.debug("finished")
|
|
||||||
|
|
||||||
def do_request_handling():
|
|
||||||
logger.debug("phew")
|
|
||||||
|
|
||||||
|
|
||||||
Using logcontexts with Deferreds
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Deferreds — and in particular, ``defer.inlineCallbacks`` — break
|
|
||||||
the linear flow of code so that there is no longer a single entry point where
|
|
||||||
we should set the logcontext and a single exit point where we should remove it.
|
|
||||||
|
|
||||||
Consider the example above, where ``do_request_handling`` needs to do some
|
|
||||||
blocking operation, and returns a deferred:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def handle_request(request_id):
|
|
||||||
with logcontext.LoggingContext() as request_context:
|
|
||||||
request_context.request = request_id
|
|
||||||
yield do_request_handling()
|
|
||||||
logger.debug("finished")
|
|
||||||
|
|
||||||
|
|
||||||
In the above flow:
|
|
||||||
|
|
||||||
* The logcontext is set
|
|
||||||
* ``do_request_handling`` is called, and returns a deferred
|
|
||||||
* ``handle_request`` yields the deferred
|
|
||||||
* The ``inlineCallbacks`` wrapper of ``handle_request`` returns a deferred
|
|
||||||
|
|
||||||
So we have stopped processing the request (and will probably go on to start
|
|
||||||
processing the next), without clearing the logcontext.
|
|
||||||
|
|
||||||
To circumvent this problem, synapse code assumes that, wherever you have a
|
|
||||||
deferred, you will want to yield on it. To that end, whereever functions return
|
|
||||||
a deferred, we adopt the following conventions:
|
|
||||||
|
|
||||||
**Rules for functions returning deferreds:**
|
|
||||||
|
|
||||||
* If the deferred is already complete, the function returns with the same
|
|
||||||
logcontext it started with.
|
|
||||||
* If the deferred is incomplete, the function clears the logcontext before
|
|
||||||
returning; when the deferred completes, it restores the logcontext before
|
|
||||||
running any callbacks.
|
|
||||||
|
|
||||||
That sounds complicated, but actually it means a lot of code (including the
|
|
||||||
example above) "just works". There are two cases:
|
|
||||||
|
|
||||||
* If ``do_request_handling`` returns a completed deferred, then the logcontext
|
|
||||||
will still be in place. In this case, execution will continue immediately
|
|
||||||
after the ``yield``; the "finished" line will be logged against the right
|
|
||||||
context, and the ``with`` block restores the original context before we
|
|
||||||
return to the caller.
|
|
||||||
|
|
||||||
* If the returned deferred is incomplete, ``do_request_handling`` clears the
|
|
||||||
logcontext before returning. The logcontext is therefore clear when
|
|
||||||
``handle_request`` yields the deferred. At that point, the ``inlineCallbacks``
|
|
||||||
wrapper adds a callback to the deferred, and returns another (incomplete)
|
|
||||||
deferred to the caller, and it is safe to begin processing the next request.
|
|
||||||
|
|
||||||
Once ``do_request_handling``'s deferred completes, it will reinstate the
|
|
||||||
logcontext, before running the callback added by the ``inlineCallbacks``
|
|
||||||
wrapper. That callback runs the second half of ``handle_request``, so again
|
|
||||||
the "finished" line will be logged against the right
|
|
||||||
context, and the ``with`` block restores the original context.
|
|
||||||
|
|
||||||
As an aside, it's worth noting that ``handle_request`` follows our rules -
|
|
||||||
though that only matters if the caller has its own logcontext which it cares
|
|
||||||
about.
|
|
||||||
|
|
||||||
The following sections describe pitfalls and helpful patterns when implementing
|
|
||||||
these rules.
|
|
||||||
|
|
||||||
Always yield your deferreds
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Whenever you get a deferred back from a function, you should ``yield`` on it
|
|
||||||
as soon as possible. (Returning it directly to your caller is ok too, if you're
|
|
||||||
not doing ``inlineCallbacks``.) Do not pass go; do not do any logging; do not
|
|
||||||
call any other functions.
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def fun():
|
|
||||||
logger.debug("starting")
|
|
||||||
yield do_some_stuff() # just like this
|
|
||||||
|
|
||||||
d = more_stuff()
|
|
||||||
result = yield d # also fine, of course
|
|
||||||
|
|
||||||
defer.returnValue(result)
|
|
||||||
|
|
||||||
def nonInlineCallbacksFun():
|
|
||||||
logger.debug("just a wrapper really")
|
|
||||||
return do_some_stuff() # this is ok too - the caller will yield on
|
|
||||||
# it anyway.
|
|
||||||
|
|
||||||
Provided this pattern is followed all the way back up to the callchain to where
|
|
||||||
the logcontext was set, this will make things work out ok: provided
|
|
||||||
``do_some_stuff`` and ``more_stuff`` follow the rules above, then so will
|
|
||||||
``fun`` (as wrapped by ``inlineCallbacks``) and ``nonInlineCallbacksFun``.
|
|
||||||
|
|
||||||
It's all too easy to forget to ``yield``: for instance if we forgot that
|
|
||||||
``do_some_stuff`` returned a deferred, we might plough on regardless. This
|
|
||||||
leads to a mess; it will probably work itself out eventually, but not before
|
|
||||||
a load of stuff has been logged against the wrong content. (Normally, other
|
|
||||||
things will break, more obviously, if you forget to ``yield``, so this tends
|
|
||||||
not to be a major problem in practice.)
|
|
||||||
|
|
||||||
Of course sometimes you need to do something a bit fancier with your Deferreds
|
|
||||||
- not all code follows the linear A-then-B-then-C pattern. Notes on
|
|
||||||
implementing more complex patterns are in later sections.
|
|
||||||
|
|
||||||
Where you create a new Deferred, make it follow the rules
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
Most of the time, a Deferred comes from another synapse function. Sometimes,
|
|
||||||
though, we need to make up a new Deferred, or we get a Deferred back from
|
|
||||||
external code. We need to make it follow our rules.
|
|
||||||
|
|
||||||
The easy way to do it is with a combination of ``defer.inlineCallbacks``, and
|
|
||||||
``logcontext.PreserveLoggingContext``. Suppose we want to implement ``sleep``,
|
|
||||||
which returns a deferred which will run its callbacks after a given number of
|
|
||||||
seconds. That might look like:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
# not a logcontext-rules-compliant function
|
|
||||||
def get_sleep_deferred(seconds):
|
|
||||||
d = defer.Deferred()
|
|
||||||
reactor.callLater(seconds, d.callback, None)
|
|
||||||
return d
|
|
||||||
|
|
||||||
That doesn't follow the rules, but we can fix it by wrapping it with
|
|
||||||
``PreserveLoggingContext`` and ``yield`` ing on it:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def sleep(seconds):
|
|
||||||
with PreserveLoggingContext():
|
|
||||||
yield get_sleep_deferred(seconds)
|
|
||||||
|
|
||||||
This technique works equally for external functions which return deferreds,
|
|
||||||
or deferreds we have made ourselves.
|
|
||||||
|
|
||||||
You can also use ``logcontext.make_deferred_yieldable``, which just does the
|
|
||||||
boilerplate for you, so the above could be written:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def sleep(seconds):
|
|
||||||
return logcontext.make_deferred_yieldable(get_sleep_deferred(seconds))
|
|
||||||
|
|
||||||
|
|
||||||
Fire-and-forget
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Sometimes you want to fire off a chain of execution, but not wait for its
|
|
||||||
result. That might look a bit like this:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def do_request_handling():
|
|
||||||
yield foreground_operation()
|
|
||||||
|
|
||||||
# *don't* do this
|
|
||||||
background_operation()
|
|
||||||
|
|
||||||
logger.debug("Request handling complete")
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def background_operation():
|
|
||||||
yield first_background_step()
|
|
||||||
logger.debug("Completed first step")
|
|
||||||
yield second_background_step()
|
|
||||||
logger.debug("Completed second step")
|
|
||||||
|
|
||||||
The above code does a couple of steps in the background after
|
|
||||||
``do_request_handling`` has finished. The log lines are still logged against
|
|
||||||
the ``request_context`` logcontext, which may or may not be desirable. There
|
|
||||||
are two big problems with the above, however. The first problem is that, if
|
|
||||||
``background_operation`` returns an incomplete Deferred, it will expect its
|
|
||||||
caller to ``yield`` immediately, so will have cleared the logcontext. In this
|
|
||||||
example, that means that 'Request handling complete' will be logged without any
|
|
||||||
context.
|
|
||||||
|
|
||||||
The second problem, which is potentially even worse, is that when the Deferred
|
|
||||||
returned by ``background_operation`` completes, it will restore the original
|
|
||||||
logcontext. There is nothing waiting on that Deferred, so the logcontext will
|
|
||||||
leak into the reactor and possibly get attached to some arbitrary future
|
|
||||||
operation.
|
|
||||||
|
|
||||||
There are two potential solutions to this.
|
|
||||||
|
|
||||||
One option is to surround the call to ``background_operation`` with a
|
|
||||||
``PreserveLoggingContext`` call. That will reset the logcontext before
|
|
||||||
starting ``background_operation`` (so the context restored when the deferred
|
|
||||||
completes will be the empty logcontext), and will restore the current
|
|
||||||
logcontext before continuing the foreground process:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def do_request_handling():
|
|
||||||
yield foreground_operation()
|
|
||||||
|
|
||||||
# start background_operation off in the empty logcontext, to
|
|
||||||
# avoid leaking the current context into the reactor.
|
|
||||||
with PreserveLoggingContext():
|
|
||||||
background_operation()
|
|
||||||
|
|
||||||
# this will now be logged against the request context
|
|
||||||
logger.debug("Request handling complete")
|
|
||||||
|
|
||||||
Obviously that option means that the operations done in
|
|
||||||
``background_operation`` would be not be logged against a logcontext (though
|
|
||||||
that might be fixed by setting a different logcontext via a ``with
|
|
||||||
LoggingContext(...)`` in ``background_operation``).
|
|
||||||
|
|
||||||
The second option is to use ``logcontext.run_in_background``, which wraps a
|
|
||||||
function so that it doesn't reset the logcontext even when it returns an
|
|
||||||
incomplete deferred, and adds a callback to the returned deferred to reset the
|
|
||||||
logcontext. In other words, it turns a function that follows the Synapse rules
|
|
||||||
about logcontexts and Deferreds into one which behaves more like an external
|
|
||||||
function — the opposite operation to that described in the previous section.
|
|
||||||
It can be used like this:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def do_request_handling():
|
|
||||||
yield foreground_operation()
|
|
||||||
|
|
||||||
logcontext.run_in_background(background_operation)
|
|
||||||
|
|
||||||
# this will now be logged against the request context
|
|
||||||
logger.debug("Request handling complete")
|
|
||||||
|
|
||||||
Passing synapse deferreds into third-party functions
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
A typical example of this is where we want to collect together two or more
|
|
||||||
deferred via ``defer.gatherResults``:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
d1 = operation1()
|
|
||||||
d2 = operation2()
|
|
||||||
d3 = defer.gatherResults([d1, d2])
|
|
||||||
|
|
||||||
This is really a variation of the fire-and-forget problem above, in that we are
|
|
||||||
firing off ``d1`` and ``d2`` without yielding on them. The difference
|
|
||||||
is that we now have third-party code attached to their callbacks. Anyway either
|
|
||||||
technique given in the `Fire-and-forget`_ section will work.
|
|
||||||
|
|
||||||
Of course, the new Deferred returned by ``gatherResults`` needs to be wrapped
|
|
||||||
in order to make it follow the logcontext rules before we can yield it, as
|
|
||||||
described in `Where you create a new Deferred, make it follow the rules`_.
|
|
||||||
|
|
||||||
So, option one: reset the logcontext before starting the operations to be
|
|
||||||
gathered:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def do_request_handling():
|
|
||||||
with PreserveLoggingContext():
|
|
||||||
d1 = operation1()
|
|
||||||
d2 = operation2()
|
|
||||||
result = yield defer.gatherResults([d1, d2])
|
|
||||||
|
|
||||||
In this case particularly, though, option two, of using
|
|
||||||
``logcontext.preserve_fn`` almost certainly makes more sense, so that
|
|
||||||
``operation1`` and ``operation2`` are both logged against the original
|
|
||||||
logcontext. This looks like:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def do_request_handling():
|
|
||||||
d1 = logcontext.preserve_fn(operation1)()
|
|
||||||
d2 = logcontext.preserve_fn(operation2)()
|
|
||||||
|
|
||||||
with PreserveLoggingContext():
|
|
||||||
result = yield defer.gatherResults([d1, d2])
|
|
||||||
|
|
||||||
|
|
||||||
Was all this really necessary?
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
The conventions used work fine for a linear flow where everything happens in
|
|
||||||
series via ``defer.inlineCallbacks`` and ``yield``, but are certainly tricky to
|
|
||||||
follow for any more exotic flows. It's hard not to wonder if we could have done
|
|
||||||
something else.
|
|
||||||
|
|
||||||
We're not going to rewrite Synapse now, so the following is entirely of
|
|
||||||
academic interest, but I'd like to record some thoughts on an alternative
|
|
||||||
approach.
|
|
||||||
|
|
||||||
I briefly prototyped some code following an alternative set of rules. I think
|
|
||||||
it would work, but I certainly didn't get as far as thinking how it would
|
|
||||||
interact with concepts as complicated as the cache descriptors.
|
|
||||||
|
|
||||||
My alternative rules were:
|
|
||||||
|
|
||||||
* functions always preserve the logcontext of their caller, whether or not they
|
|
||||||
are returning a Deferred.
|
|
||||||
|
|
||||||
* Deferreds returned by synapse functions run their callbacks in the same
|
|
||||||
context as the function was orignally called in.
|
|
||||||
|
|
||||||
The main point of this scheme is that everywhere that sets the logcontext is
|
|
||||||
responsible for clearing it before returning control to the reactor.
|
|
||||||
|
|
||||||
So, for example, if you were the function which started a ``with
|
|
||||||
LoggingContext`` block, you wouldn't ``yield`` within it — instead you'd start
|
|
||||||
off the background process, and then leave the ``with`` block to wait for it:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def handle_request(request_id):
|
|
||||||
with logcontext.LoggingContext() as request_context:
|
|
||||||
request_context.request = request_id
|
|
||||||
d = do_request_handling()
|
|
||||||
|
|
||||||
def cb(r):
|
|
||||||
logger.debug("finished")
|
|
||||||
|
|
||||||
d.addCallback(cb)
|
|
||||||
return d
|
|
||||||
|
|
||||||
(in general, mixing ``with LoggingContext`` blocks and
|
|
||||||
``defer.inlineCallbacks`` in the same function leads to slighly
|
|
||||||
counter-intuitive code, under this scheme).
|
|
||||||
|
|
||||||
Because we leave the original ``with`` block as soon as the Deferred is
|
|
||||||
returned (as opposed to waiting for it to be resolved, as we do today), the
|
|
||||||
logcontext is cleared before control passes back to the reactor; so if there is
|
|
||||||
some code within ``do_request_handling`` which needs to wait for a Deferred to
|
|
||||||
complete, there is no need for it to worry about clearing the logcontext before
|
|
||||||
doing so:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def handle_request():
|
|
||||||
r = do_some_stuff()
|
|
||||||
r.addCallback(do_some_more_stuff)
|
|
||||||
return r
|
|
||||||
|
|
||||||
— and provided ``do_some_stuff`` follows the rules of returning a Deferred which
|
|
||||||
runs its callbacks in the original logcontext, all is happy.
|
|
||||||
|
|
||||||
The business of a Deferred which runs its callbacks in the original logcontext
|
|
||||||
isn't hard to achieve — we have it today, in the shape of
|
|
||||||
``logcontext._PreservingContextDeferred``:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def do_some_stuff():
|
|
||||||
deferred = do_some_io()
|
|
||||||
pcd = _PreservingContextDeferred(LoggingContext.current_context())
|
|
||||||
deferred.chainDeferred(pcd)
|
|
||||||
return pcd
|
|
||||||
|
|
||||||
It turns out that, thanks to the way that Deferreds chain together, we
|
|
||||||
automatically get the property of a context-preserving deferred with
|
|
||||||
``defer.inlineCallbacks``, provided the final Defered the function ``yields``
|
|
||||||
on has that property. So we can just write:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def handle_request():
|
|
||||||
yield do_some_stuff()
|
|
||||||
yield do_some_more_stuff()
|
|
||||||
|
|
||||||
To conclude: I think this scheme would have worked equally well, with less
|
|
||||||
danger of messing it up, and probably made some more esoteric code easier to
|
|
||||||
write. But again — changing the conventions of the entire Synapse codebase is
|
|
||||||
not a sensible option for the marginal improvement offered.
|
|
||||||
@@ -1,115 +1,50 @@
|
|||||||
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.
|
Simply setting a (local) port number will enable it. Pick a port.
|
||||||
prometheus itself defaults to 9090, so starting just above that for
|
prometheus itself defaults to 9090, so starting just above that for
|
||||||
locally monitored services seems reasonable. E.g. 9092:
|
locally monitored services seems reasonable. E.g. 9092:
|
||||||
|
|
||||||
Add to homeserver.yaml::
|
Add to homeserver.yaml
|
||||||
|
|
||||||
metrics_port: 9092
|
metrics_port: 9092
|
||||||
|
|
||||||
Also ensure that ``enable_metrics`` is set to ``True``.
|
Restart synapse
|
||||||
|
|
||||||
Restart synapse.
|
3: Check out synapse-prometheus-config
|
||||||
|
https://github.com/matrix-org/synapse-prometheus-config
|
||||||
|
|
||||||
3. Add a prometheus target for synapse.
|
4: Add ``synapse.html`` and ``synapse.rules``
|
||||||
|
The ``.html`` file needs to appear in prometheus's ``consoles`` directory,
|
||||||
|
and the ``.rules`` file needs to be invoked somewhere in the main config
|
||||||
|
file. A symlink to each from the git checkout into the prometheus directory
|
||||||
|
might be easiest to ensure ``git pull`` keeps it updated.
|
||||||
|
|
||||||
It needs to set the ``metrics_path`` to a non-default value (under ``scrape_configs``)::
|
5: Add a prometheus target for synapse
|
||||||
|
This is easiest if prometheus runs on the same machine as synapse, as it can
|
||||||
|
then just use localhost::
|
||||||
|
|
||||||
- job_name: "synapse"
|
global: {
|
||||||
metrics_path: "/_synapse/metrics"
|
rule_file: "synapse.rules"
|
||||||
static_configs:
|
}
|
||||||
- targets: ["my.server.here:9092"]
|
|
||||||
|
|
||||||
If your prometheus is older than 1.5.2, you will need to replace
|
job: {
|
||||||
``static_configs`` in the above with ``target_groups``.
|
name: "synapse"
|
||||||
|
|
||||||
Restart prometheus.
|
target_group: {
|
||||||
|
target: "http://localhost:9092/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
6: Start prometheus::
|
||||||
|
|
||||||
Block and response metrics renamed for 0.27.0
|
./prometheus -config.file=prometheus.conf
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Synapse 0.27.0 begins the process of rationalising the duplicate ``*:count``
|
7: Wait a few seconds for it to start and perform the first scrape,
|
||||||
metrics reported for the resource tracking for code blocks and HTTP requests.
|
then visit the console:
|
||||||
|
|
||||||
At the same time, the corresponding ``*:total`` metrics are being renamed, as
|
http://server-where-prometheus-runs:9090/consoles/synapse.html
|
||||||
the ``:total`` suffix no longer makes sense in the absence of a corresponding
|
|
||||||
``:count`` metric.
|
|
||||||
|
|
||||||
To enable a graceful migration path, this release just adds new names for the
|
|
||||||
metrics being renamed. A future release will remove the old ones.
|
|
||||||
|
|
||||||
The following table shows the new metrics, and the old metrics which they are
|
|
||||||
replacing.
|
|
||||||
|
|
||||||
==================================================== ===================================================
|
|
||||||
New name Old name
|
|
||||||
==================================================== ===================================================
|
|
||||||
synapse_util_metrics_block_count synapse_util_metrics_block_timer:count
|
|
||||||
synapse_util_metrics_block_count synapse_util_metrics_block_ru_utime:count
|
|
||||||
synapse_util_metrics_block_count synapse_util_metrics_block_ru_stime:count
|
|
||||||
synapse_util_metrics_block_count synapse_util_metrics_block_db_txn_count:count
|
|
||||||
synapse_util_metrics_block_count synapse_util_metrics_block_db_txn_duration:count
|
|
||||||
|
|
||||||
synapse_util_metrics_block_time_seconds synapse_util_metrics_block_timer:total
|
|
||||||
synapse_util_metrics_block_ru_utime_seconds synapse_util_metrics_block_ru_utime:total
|
|
||||||
synapse_util_metrics_block_ru_stime_seconds synapse_util_metrics_block_ru_stime:total
|
|
||||||
synapse_util_metrics_block_db_txn_count synapse_util_metrics_block_db_txn_count:total
|
|
||||||
synapse_util_metrics_block_db_txn_duration_seconds synapse_util_metrics_block_db_txn_duration:total
|
|
||||||
|
|
||||||
synapse_http_server_response_count synapse_http_server_requests
|
|
||||||
synapse_http_server_response_count synapse_http_server_response_time:count
|
|
||||||
synapse_http_server_response_count synapse_http_server_response_ru_utime:count
|
|
||||||
synapse_http_server_response_count synapse_http_server_response_ru_stime:count
|
|
||||||
synapse_http_server_response_count synapse_http_server_response_db_txn_count:count
|
|
||||||
synapse_http_server_response_count synapse_http_server_response_db_txn_duration:count
|
|
||||||
|
|
||||||
synapse_http_server_response_time_seconds synapse_http_server_response_time:total
|
|
||||||
synapse_http_server_response_ru_utime_seconds synapse_http_server_response_ru_utime:total
|
|
||||||
synapse_http_server_response_ru_stime_seconds synapse_http_server_response_ru_stime:total
|
|
||||||
synapse_http_server_response_db_txn_count synapse_http_server_response_db_txn_count:total
|
|
||||||
synapse_http_server_response_db_txn_duration_seconds synapse_http_server_response_db_txn_duration:total
|
|
||||||
==================================================== ===================================================
|
|
||||||
|
|
||||||
|
|
||||||
Standard Metric Names
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
As of synapse version 0.18.2, the format of the process-wide metrics has been
|
|
||||||
changed to fit prometheus standard naming conventions. Additionally the units
|
|
||||||
have been changed to seconds, from miliseconds.
|
|
||||||
|
|
||||||
================================== =============================
|
|
||||||
New name Old name
|
|
||||||
================================== =============================
|
|
||||||
process_cpu_user_seconds_total process_resource_utime / 1000
|
|
||||||
process_cpu_system_seconds_total process_resource_stime / 1000
|
|
||||||
process_open_fds (no 'type' label) process_fds
|
|
||||||
================================== =============================
|
|
||||||
|
|
||||||
The python-specific counts of garbage collector performance have been renamed.
|
|
||||||
|
|
||||||
=========================== ======================
|
|
||||||
New name Old name
|
|
||||||
=========================== ======================
|
|
||||||
python_gc_time reactor_gc_time
|
|
||||||
python_gc_unreachable_total reactor_gc_unreachable
|
|
||||||
python_gc_counts reactor_gc_counts
|
|
||||||
=========================== ======================
|
|
||||||
|
|
||||||
The twisted-specific reactor metrics have been renamed.
|
|
||||||
|
|
||||||
==================================== =====================
|
|
||||||
New name Old name
|
|
||||||
==================================== =====================
|
|
||||||
python_twisted_reactor_pending_calls reactor_pending_calls
|
|
||||||
python_twisted_reactor_tick_time reactor_tick_time
|
|
||||||
==================================== =====================
|
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
Password auth provider modules
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Password auth providers offer a way for server administrators to integrate
|
|
||||||
their Synapse installation with an existing authentication system.
|
|
||||||
|
|
||||||
A password auth provider is a Python class which is dynamically loaded into
|
|
||||||
Synapse, and provides a number of methods by which it can integrate with the
|
|
||||||
authentication system.
|
|
||||||
|
|
||||||
This document serves as a reference for those looking to implement their own
|
|
||||||
password auth providers.
|
|
||||||
|
|
||||||
Required methods
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Password auth provider classes must provide the following methods:
|
|
||||||
|
|
||||||
*class* ``SomeProvider.parse_config``\(*config*)
|
|
||||||
|
|
||||||
This method is passed the ``config`` object for this module from the
|
|
||||||
homeserver configuration file.
|
|
||||||
|
|
||||||
It should perform any appropriate sanity checks on the provided
|
|
||||||
configuration, and return an object which is then passed into ``__init__``.
|
|
||||||
|
|
||||||
*class* ``SomeProvider``\(*config*, *account_handler*)
|
|
||||||
|
|
||||||
The constructor is passed the config object returned by ``parse_config``,
|
|
||||||
and a ``synapse.module_api.ModuleApi`` object which allows the
|
|
||||||
password provider to check if accounts exist and/or create new ones.
|
|
||||||
|
|
||||||
Optional methods
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Password auth provider classes may optionally provide the following methods.
|
|
||||||
|
|
||||||
*class* ``SomeProvider.get_db_schema_files``\()
|
|
||||||
|
|
||||||
This method, if implemented, should return an Iterable of ``(name,
|
|
||||||
stream)`` pairs of database schema files. Each file is applied in turn at
|
|
||||||
initialisation, and a record is then made in the database so that it is
|
|
||||||
not re-applied on the next start.
|
|
||||||
|
|
||||||
``someprovider.get_supported_login_types``\()
|
|
||||||
|
|
||||||
This method, if implemented, should return a ``dict`` mapping from a login
|
|
||||||
type identifier (such as ``m.login.password``) to an iterable giving the
|
|
||||||
fields which must be provided by the user in the submission to the
|
|
||||||
``/login`` api. These fields are passed in the ``login_dict`` dictionary
|
|
||||||
to ``check_auth``.
|
|
||||||
|
|
||||||
For example, if a password auth provider wants to implement a custom login
|
|
||||||
type of ``com.example.custom_login``, where the client is expected to pass
|
|
||||||
the fields ``secret1`` and ``secret2``, the provider should implement this
|
|
||||||
method and return the following dict::
|
|
||||||
|
|
||||||
{"com.example.custom_login": ("secret1", "secret2")}
|
|
||||||
|
|
||||||
``someprovider.check_auth``\(*username*, *login_type*, *login_dict*)
|
|
||||||
|
|
||||||
This method is the one that does the real work. If implemented, it will be
|
|
||||||
called for each login attempt where the login type matches one of the keys
|
|
||||||
returned by ``get_supported_login_types``.
|
|
||||||
|
|
||||||
It is passed the (possibly UNqualified) ``user`` provided by the client,
|
|
||||||
the login type, and a dictionary of login secrets passed by the client.
|
|
||||||
|
|
||||||
The method should return a Twisted ``Deferred`` object, which resolves to
|
|
||||||
the canonical ``@localpart:domain`` user id if authentication is successful,
|
|
||||||
and ``None`` if not.
|
|
||||||
|
|
||||||
Alternatively, the ``Deferred`` can resolve to a ``(str, func)`` tuple, in
|
|
||||||
which case the second field is a callback which will be called with the
|
|
||||||
result from the ``/login`` call (including ``access_token``, ``device_id``,
|
|
||||||
etc.)
|
|
||||||
|
|
||||||
``someprovider.check_password``\(*user_id*, *password*)
|
|
||||||
|
|
||||||
This method provides a simpler interface than ``get_supported_login_types``
|
|
||||||
and ``check_auth`` for password auth providers that just want to provide a
|
|
||||||
mechanism for validating ``m.login.password`` logins.
|
|
||||||
|
|
||||||
Iif implemented, it will be called to check logins with an
|
|
||||||
``m.login.password`` login type. It is passed a qualified
|
|
||||||
``@localpart:domain`` user id, and the password provided by the user.
|
|
||||||
|
|
||||||
The method should return a Twisted ``Deferred`` object, which resolves to
|
|
||||||
``True`` if authentication is successful, and ``False`` if not.
|
|
||||||
|
|
||||||
``someprovider.on_logged_out``\(*user_id*, *device_id*, *access_token*)
|
|
||||||
|
|
||||||
This method, if implemented, is called when a user logs out. It is passed
|
|
||||||
the qualified user ID, the ID of the deactivated device (if any: access
|
|
||||||
tokens are occasionally created without an associated device ID), and the
|
|
||||||
(now deactivated) access token.
|
|
||||||
|
|
||||||
It may return a Twisted ``Deferred`` object; the logout request will wait
|
|
||||||
for the deferred to complete but the result is ignored.
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
Using Postgres
|
Using Postgres
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Postgres version 9.4 or later is known to work.
|
|
||||||
|
|
||||||
Set up database
|
Set up database
|
||||||
===============
|
===============
|
||||||
|
|
||||||
@@ -20,8 +18,8 @@ encoding use, e.g.::
|
|||||||
This would create an appropriate database named ``synapse`` owned by the
|
This would create an appropriate database named ``synapse`` owned by the
|
||||||
``synapse_user`` user (which must already exist).
|
``synapse_user`` user (which must already exist).
|
||||||
|
|
||||||
Set up client in Debian/Ubuntu
|
Set up client
|
||||||
===========================
|
=============
|
||||||
|
|
||||||
Postgres support depends on the postgres python connector ``psycopg2``. In the
|
Postgres support depends on the postgres python connector ``psycopg2``. In the
|
||||||
virtual env::
|
virtual env::
|
||||||
@@ -29,19 +27,6 @@ virtual env::
|
|||||||
sudo apt-get install libpq-dev
|
sudo apt-get install libpq-dev
|
||||||
pip install psycopg2
|
pip install psycopg2
|
||||||
|
|
||||||
Set up client in RHEL/CentOs 7
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Make sure you have the appropriate version of postgres-devel installed. For a
|
|
||||||
postgres 9.4, use the postgres 9.4 packages from
|
|
||||||
[here](https://wiki.postgresql.org/wiki/YUM_Installation).
|
|
||||||
|
|
||||||
As with Debian/Ubuntu, postgres support depends on the postgres python connector
|
|
||||||
``psycopg2``. In the virtual env::
|
|
||||||
|
|
||||||
sudo yum install postgresql-devel libpqxx-devel.x86_64
|
|
||||||
export PATH=/usr/pgsql-9.4/bin/:$PATH
|
|
||||||
pip install psycopg2
|
|
||||||
|
|
||||||
Synapse config
|
Synapse config
|
||||||
==============
|
==============
|
||||||
@@ -49,7 +34,11 @@ Synapse config
|
|||||||
When you are ready to start using PostgreSQL, add the following line to your
|
When you are ready to start using PostgreSQL, add the following line to your
|
||||||
config file::
|
config file::
|
||||||
|
|
||||||
database:
|
database_config: <db_config_file>
|
||||||
|
|
||||||
|
Where ``<db_config_file>`` is the file name that points to a yaml file of the
|
||||||
|
following form::
|
||||||
|
|
||||||
name: psycopg2
|
name: psycopg2
|
||||||
args:
|
args:
|
||||||
user: <user>
|
user: <user>
|
||||||
@@ -70,8 +59,9 @@ Porting from SQLite
|
|||||||
Overview
|
Overview
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
|
||||||
The script ``synapse_port_db`` allows porting an existing synapse server
|
The script ``port_from_sqlite_to_postgres.py`` allows porting an existing
|
||||||
backed by SQLite to using PostgreSQL. This is done in as a two phase process:
|
synapse server backed by SQLite to using PostgreSQL. This is done in as a two
|
||||||
|
phase process:
|
||||||
|
|
||||||
1. Copy the existing SQLite database to a separate location (while the server
|
1. Copy the existing SQLite database to a separate location (while the server
|
||||||
is down) and running the port script against that offline database.
|
is down) and running the port script against that offline database.
|
||||||
@@ -96,12 +86,13 @@ 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*)
|
Assuming your database config file (as described in the section *Synapse
|
||||||
is named ``homeserver-postgres.yaml`` and the SQLite snapshot is at
|
config*) is named ``database_config.yaml`` and the SQLite snapshot is at
|
||||||
``homeserver.db.snapshot`` then simply run::
|
``homeserver.db.snapshot`` then simply run::
|
||||||
|
|
||||||
synapse_port_db --sqlite-database homeserver.db.snapshot \
|
python scripts/port_from_sqlite_to_postgres.py \
|
||||||
--postgres-config homeserver-postgres.yaml
|
--sqlite-database homeserver.db.snapshot \
|
||||||
|
--postgres-config database_config.yaml
|
||||||
|
|
||||||
The flag ``--curses`` displays a coloured curses progress UI.
|
The flag ``--curses`` displays a coloured curses progress UI.
|
||||||
|
|
||||||
@@ -113,10 +104,11 @@ To complete the conversion shut down the synapse server and run the port
|
|||||||
script one last time, e.g. if the SQLite database is at ``homeserver.db``
|
script one last time, e.g. if the SQLite database is at ``homeserver.db``
|
||||||
run::
|
run::
|
||||||
|
|
||||||
synapse_port_db --sqlite-database homeserver.db \
|
python scripts/port_from_sqlite_to_postgres.py \
|
||||||
--postgres-config homeserver-postgres.yaml
|
--sqlite-database homeserver.db \
|
||||||
|
--postgres-config database_config.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 using the ``database_config`` parameter (see
|
||||||
``homeserver.yaml``) and restart synapse. Synapse should now be running against
|
`Synapse Config`_) and restart synapse. Synapse should now be running against
|
||||||
PostgreSQL.
|
PostgreSQL.
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
Replication Architecture
|
|
||||||
========================
|
|
||||||
|
|
||||||
Motivation
|
|
||||||
----------
|
|
||||||
|
|
||||||
We'd like to be able to split some of the work that synapse does into multiple
|
|
||||||
python processes. In theory multiple synapse processes could share a single
|
|
||||||
postgresql database and we'd scale up by running more synapse processes.
|
|
||||||
However much of synapse assumes that only one process is interacting with the
|
|
||||||
database, both for assigning unique identifiers when inserting into tables,
|
|
||||||
notifying components about new updates, and for invalidating its caches.
|
|
||||||
|
|
||||||
So running multiple copies of the current code isn't an option. One way to
|
|
||||||
run multiple processes would be to have a single writer process and multiple
|
|
||||||
reader processes connected to the same database. In order to do this we'd need
|
|
||||||
a way for the reader process to invalidate its in-memory caches when an update
|
|
||||||
happens on the writer. One way to do this is for the writer to present an
|
|
||||||
append-only log of updates which the readers can consume to invalidate their
|
|
||||||
caches and to push updates to listening clients or pushers.
|
|
||||||
|
|
||||||
Synapse already stores much of its data as an append-only log so that it can
|
|
||||||
correctly respond to /sync requests so the amount of code changes needed to
|
|
||||||
expose the append-only log to the readers should be fairly minimal.
|
|
||||||
|
|
||||||
Architecture
|
|
||||||
------------
|
|
||||||
|
|
||||||
The Replication Protocol
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
See ``tcp_replication.rst``
|
|
||||||
|
|
||||||
|
|
||||||
The Slaved DataStore
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
There are read-only version of the synapse storage layer in
|
|
||||||
``synapse/replication/slave/storage`` that use the response of the replication
|
|
||||||
API to invalidate their caches.
|
|
||||||
@@ -50,7 +50,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Synapse'
|
project = u'Synapse'
|
||||||
copyright = u'Copyright 2014-2017 OpenMarket Ltd, 2017 Vector Creations Ltd, 2017 New Vector Ltd'
|
copyright = u'2014, TNG'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
TCP Replication
|
|
||||||
===============
|
|
||||||
|
|
||||||
Motivation
|
|
||||||
----------
|
|
||||||
|
|
||||||
Previously the workers used an HTTP long poll mechanism to get updates from the
|
|
||||||
master, which had the problem of causing a lot of duplicate work on the server.
|
|
||||||
This TCP protocol replaces those APIs with the aim of increased efficiency.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
|
|
||||||
The protocol is based on fire and forget, line based commands. An example flow
|
|
||||||
would be (where '>' indicates master to worker and '<' worker to master flows)::
|
|
||||||
|
|
||||||
> SERVER example.com
|
|
||||||
< REPLICATE events 53
|
|
||||||
> RDATA events 54 ["$foo1:bar.com", ...]
|
|
||||||
> RDATA events 55 ["$foo4:bar.com", ...]
|
|
||||||
|
|
||||||
The example shows the server accepting a new connection and sending its identity
|
|
||||||
with the ``SERVER`` command, followed by the client asking to subscribe to the
|
|
||||||
``events`` stream from the token ``53``. The server then periodically sends ``RDATA``
|
|
||||||
commands which have the format ``RDATA <stream_name> <token> <row>``, where the
|
|
||||||
format of ``<row>`` is defined by the individual streams.
|
|
||||||
|
|
||||||
Error reporting happens by either the client or server sending an `ERROR`
|
|
||||||
command, and usually the connection will be closed.
|
|
||||||
|
|
||||||
|
|
||||||
Since the protocol is a simple line based, its possible to manually connect to
|
|
||||||
the server using a tool like netcat. A few things should be noted when manually
|
|
||||||
using the protocol:
|
|
||||||
|
|
||||||
* When subscribing to a stream using ``REPLICATE``, the special token ``NOW`` can
|
|
||||||
be used to get all future updates. The special stream name ``ALL`` can be used
|
|
||||||
with ``NOW`` to subscribe to all available streams.
|
|
||||||
* The federation stream is only available if federation sending has been
|
|
||||||
disabled on the main process.
|
|
||||||
* The server will only time connections out that have sent a ``PING`` command.
|
|
||||||
If a ping is sent then the connection will be closed if no further commands
|
|
||||||
are receieved within 15s. Both the client and server protocol implementations
|
|
||||||
will send an initial PING on connection and ensure at least one command every
|
|
||||||
5s is sent (not necessarily ``PING``).
|
|
||||||
* ``RDATA`` commands *usually* include a numeric token, however if the stream
|
|
||||||
has multiple rows to replicate per token the server will send multiple
|
|
||||||
``RDATA`` commands, with all but the last having a token of ``batch``. See
|
|
||||||
the documentation on ``commands.RdataCommand`` for further details.
|
|
||||||
|
|
||||||
|
|
||||||
Architecture
|
|
||||||
------------
|
|
||||||
|
|
||||||
The basic structure of the protocol is line based, where the initial word of
|
|
||||||
each line specifies the command. The rest of the line is parsed based on the
|
|
||||||
command. For example, the `RDATA` command is defined as::
|
|
||||||
|
|
||||||
RDATA <stream_name> <token> <row_json>
|
|
||||||
|
|
||||||
(Note that `<row_json>` may contains spaces, but cannot contain newlines.)
|
|
||||||
|
|
||||||
Blank lines are ignored.
|
|
||||||
|
|
||||||
|
|
||||||
Keep alives
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
Both sides are expected to send at least one command every 5s or so, and
|
|
||||||
should send a ``PING`` command if necessary. If either side do not receive a
|
|
||||||
command within e.g. 15s then the connection should be closed.
|
|
||||||
|
|
||||||
Because the server may be connected to manually using e.g. netcat, the timeouts
|
|
||||||
aren't enabled until an initial ``PING`` command is seen. Both the client and
|
|
||||||
server implementations below send a ``PING`` command immediately on connection to
|
|
||||||
ensure the timeouts are enabled.
|
|
||||||
|
|
||||||
This ensures that both sides can quickly realize if the tcp connection has gone
|
|
||||||
and handle the situation appropriately.
|
|
||||||
|
|
||||||
|
|
||||||
Start up
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
When a new connection is made, the server:
|
|
||||||
|
|
||||||
* Sends a ``SERVER`` command, which includes the identity of the server, allowing
|
|
||||||
the client to detect if its connected to the expected server
|
|
||||||
* Sends a ``PING`` command as above, to enable the client to time out connections
|
|
||||||
promptly.
|
|
||||||
|
|
||||||
The client:
|
|
||||||
|
|
||||||
* Sends a ``NAME`` command, allowing the server to associate a human friendly
|
|
||||||
name with the connection. This is optional.
|
|
||||||
* Sends a ``PING`` as above
|
|
||||||
* For each stream the client wishes to subscribe to it sends a ``REPLICATE``
|
|
||||||
with the stream_name and token it wants to subscribe from.
|
|
||||||
* On receipt of a ``SERVER`` command, checks that the server name matches the
|
|
||||||
expected server name.
|
|
||||||
|
|
||||||
|
|
||||||
Error handling
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If either side detects an error it can send an ``ERROR`` command and close the
|
|
||||||
connection.
|
|
||||||
|
|
||||||
If the client side loses the connection to the server it should reconnect,
|
|
||||||
following the steps above.
|
|
||||||
|
|
||||||
|
|
||||||
Congestion
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
If the server sends messages faster than the client can consume them the server
|
|
||||||
will first buffer a (fairly large) number of commands and then disconnect the
|
|
||||||
client. This ensures that we don't queue up an unbounded number of commands in
|
|
||||||
memory and gives us a potential oppurtunity to squawk loudly. When/if the client
|
|
||||||
recovers it can reconnect to the server and ask for missed messages.
|
|
||||||
|
|
||||||
|
|
||||||
Reliability
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
In general the replication stream should be considered an unreliable transport
|
|
||||||
since e.g. commands are not resent if the connection disappears.
|
|
||||||
|
|
||||||
The exception to that are the replication streams, i.e. RDATA commands, since
|
|
||||||
these include tokens which can be used to restart the stream on connection
|
|
||||||
errors.
|
|
||||||
|
|
||||||
The client should keep track of the token in the last RDATA command received
|
|
||||||
for each stream so that on reconneciton it can start streaming from the correct
|
|
||||||
place. Note: not all RDATA have valid tokens due to batching. See
|
|
||||||
``RdataCommand`` for more details.
|
|
||||||
|
|
||||||
|
|
||||||
Example
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
An example iteraction is shown below. Each line is prefixed with '>' or '<' to
|
|
||||||
indicate which side is sending, these are *not* included on the wire::
|
|
||||||
|
|
||||||
* connection established *
|
|
||||||
> SERVER localhost:8823
|
|
||||||
> PING 1490197665618
|
|
||||||
< NAME synapse.app.appservice
|
|
||||||
< PING 1490197665618
|
|
||||||
< REPLICATE events 1
|
|
||||||
< REPLICATE backfill 1
|
|
||||||
< REPLICATE caches 1
|
|
||||||
> POSITION events 1
|
|
||||||
> POSITION backfill 1
|
|
||||||
> POSITION caches 1
|
|
||||||
> RDATA caches 2 ["get_user_by_id",["@01register-user:localhost:8823"],1490197670513]
|
|
||||||
> RDATA events 14 ["$149019767112vOHxz:localhost:8823",
|
|
||||||
"!AFDCvgApUmpdfVjIXm:localhost:8823","m.room.guest_access","",null]
|
|
||||||
< PING 1490197675618
|
|
||||||
> ERROR server stopping
|
|
||||||
* connection closed by server *
|
|
||||||
|
|
||||||
The ``POSITION`` command sent by the server is used to set the clients position
|
|
||||||
without needing to send data with the ``RDATA`` command.
|
|
||||||
|
|
||||||
|
|
||||||
An example of a batched set of ``RDATA`` is::
|
|
||||||
|
|
||||||
> RDATA caches batch ["get_user_by_id",["@test:localhost:8823"],1490197670513]
|
|
||||||
> RDATA caches batch ["get_user_by_id",["@test2:localhost:8823"],1490197670513]
|
|
||||||
> RDATA caches batch ["get_user_by_id",["@test3:localhost:8823"],1490197670513]
|
|
||||||
> RDATA caches 54 ["get_user_by_id",["@test4:localhost:8823"],1490197670513]
|
|
||||||
|
|
||||||
In this case the client shouldn't advance their caches token until it sees the
|
|
||||||
the last ``RDATA``.
|
|
||||||
|
|
||||||
|
|
||||||
List of commands
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The list of valid commands, with which side can send it: server (S) or client (C):
|
|
||||||
|
|
||||||
SERVER (S)
|
|
||||||
Sent at the start to identify which server the client is talking to
|
|
||||||
|
|
||||||
RDATA (S)
|
|
||||||
A single update in a stream
|
|
||||||
|
|
||||||
POSITION (S)
|
|
||||||
The position of the stream has been updated
|
|
||||||
|
|
||||||
ERROR (S, C)
|
|
||||||
There was an error
|
|
||||||
|
|
||||||
PING (S, C)
|
|
||||||
Sent periodically to ensure the connection is still alive
|
|
||||||
|
|
||||||
NAME (C)
|
|
||||||
Sent at the start by client to inform the server who they are
|
|
||||||
|
|
||||||
REPLICATE (C)
|
|
||||||
Asks the server to replicate a given stream
|
|
||||||
|
|
||||||
USER_SYNC (C)
|
|
||||||
A user has started or stopped syncing
|
|
||||||
|
|
||||||
FEDERATION_ACK (C)
|
|
||||||
Acknowledge receipt of some federation data
|
|
||||||
|
|
||||||
REMOVE_PUSHER (C)
|
|
||||||
Inform the server a pusher should be removed
|
|
||||||
|
|
||||||
INVALIDATE_CACHE (C)
|
|
||||||
Inform the server a cache should be invalidated
|
|
||||||
|
|
||||||
SYNC (S, C)
|
|
||||||
Used exclusively in tests
|
|
||||||
|
|
||||||
|
|
||||||
See ``synapse/replication/tcp/commands.py`` for a detailed description and the
|
|
||||||
format of each command.
|
|
||||||
@@ -9,35 +9,31 @@ the Home Server to generate credentials that are valid for use on the TURN
|
|||||||
server through the use of a secret shared between the Home Server and the
|
server through the use of a secret shared between the Home Server and the
|
||||||
TURN server.
|
TURN server.
|
||||||
|
|
||||||
This document describes how to install coturn
|
This document described how to install coturn
|
||||||
(https://github.com/coturn/coturn) which also supports the TURN REST API,
|
(https://code.google.com/p/coturn/) which also supports the TURN REST API,
|
||||||
and integrate it with synapse.
|
and integrate it with synapse.
|
||||||
|
|
||||||
coturn Setup
|
coturn Setup
|
||||||
============
|
============
|
||||||
|
|
||||||
You may be able to setup coturn via your package manager, or set it up manually using the usual ``configure, make, make install`` process.
|
|
||||||
|
|
||||||
1. Check out coturn::
|
1. Check out coturn::
|
||||||
|
svn checkout http://coturn.googlecode.com/svn/trunk/ coturn
|
||||||
git clone https://github.com/coturn/coturn.git coturn
|
|
||||||
cd coturn
|
cd coturn
|
||||||
|
|
||||||
2. Configure it::
|
2. Configure it::
|
||||||
|
|
||||||
./configure
|
./configure
|
||||||
|
|
||||||
You may need to install ``libevent2``: if so, you should do so
|
You may need to install libevent2: if so, you should do so
|
||||||
in the way recommended by your operating system.
|
in the way recommended by your operating system.
|
||||||
You can ignore warnings about lack of database support: a
|
You can ignore warnings about lack of database support: a
|
||||||
database is unnecessary for this purpose.
|
database is unnecessary for this purpose.
|
||||||
|
|
||||||
3. Build and install it::
|
3. Build and install it::
|
||||||
|
|
||||||
make
|
make
|
||||||
make install
|
make install
|
||||||
|
|
||||||
4. Create or edit the config file in ``/etc/turnserver.conf``. The relevant
|
4. Make a config file in /etc/turnserver.conf. You can customise
|
||||||
|
a config file from turnserver.conf.default. The relevant
|
||||||
lines, with example values, are::
|
lines, with example values, are::
|
||||||
|
|
||||||
lt-cred-mech
|
lt-cred-mech
|
||||||
@@ -45,43 +41,19 @@ You may be able to setup coturn via your package manager, or set it up manually
|
|||||||
static-auth-secret=[your secret key here]
|
static-auth-secret=[your secret key here]
|
||||||
realm=turn.myserver.org
|
realm=turn.myserver.org
|
||||||
|
|
||||||
See turnserver.conf for explanations of the options.
|
See turnserver.conf.default for explanations of the options.
|
||||||
One way to generate the static-auth-secret is with pwgen::
|
One way to generate the static-auth-secret is with pwgen::
|
||||||
|
|
||||||
pwgen -s 64 1
|
pwgen -s 64 1
|
||||||
|
|
||||||
5. Consider your security settings. TURN lets users request a relay
|
5. Ensure youe firewall allows traffic into the TURN server on
|
||||||
which will connect to arbitrary IP addresses and ports. At the least
|
|
||||||
we recommend:
|
|
||||||
|
|
||||||
# VoIP traffic is all UDP. There is no reason to let users connect to arbitrary TCP endpoints via the relay.
|
|
||||||
no-tcp-relay
|
|
||||||
|
|
||||||
# don't let the relay ever try to connect to private IP address ranges within your network (if any)
|
|
||||||
# given the turn server is likely behind your firewall, remember to include any privileged public IPs too.
|
|
||||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
|
||||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
|
||||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
|
||||||
|
|
||||||
# special case the turn server itself so that client->TURN->TURN->client flows work
|
|
||||||
allowed-peer-ip=10.0.0.1
|
|
||||||
|
|
||||||
# consider whether you want to limit the quota of relayed streams per user (or total) to avoid risk of DoS.
|
|
||||||
user-quota=12 # 4 streams per video call, so 12 streams = 3 simultaneous relayed calls per user.
|
|
||||||
total-quota=1200
|
|
||||||
|
|
||||||
Ideally coturn should refuse to relay traffic which isn't SRTP;
|
|
||||||
see https://github.com/matrix-org/synapse/issues/2009
|
|
||||||
|
|
||||||
6. Ensure your firewall allows traffic into the TURN server on
|
|
||||||
the ports you've configured it to listen on (remember to allow
|
the ports you've configured it to listen on (remember to allow
|
||||||
both TCP and UDP TURN traffic)
|
both TCP and UDP if you've enabled both).
|
||||||
|
|
||||||
7. If you've configured coturn to support TLS/DTLS, generate or
|
6. If you've configured coturn to support TLS/DTLS, generate or
|
||||||
import your private key and certificate.
|
import your private key and certificate.
|
||||||
|
|
||||||
8. Start the turn server::
|
7. Start the turn server::
|
||||||
|
|
||||||
bin/turnserver -o
|
bin/turnserver -o
|
||||||
|
|
||||||
|
|
||||||
@@ -106,19 +78,12 @@ Your home server configuration file needs the following extra keys:
|
|||||||
to refresh credentials. The TURN REST API specification recommends
|
to refresh credentials. The TURN REST API specification recommends
|
||||||
one day (86400000).
|
one day (86400000).
|
||||||
|
|
||||||
4. "turn_allow_guests": Whether to allow guest users to use the TURN
|
|
||||||
server. This is enabled by default, as otherwise VoIP will not
|
|
||||||
work reliably for guests. However, it does introduce a security risk
|
|
||||||
as it lets guests connect to arbitrary endpoints without having gone
|
|
||||||
through a CAPTCHA or similar to register a real account.
|
|
||||||
|
|
||||||
As an example, here is the relevant section of the config file for
|
As an example, here is the relevant section of the config file for
|
||||||
matrix.org::
|
matrix.org::
|
||||||
|
|
||||||
turn_uris: [ "turn:turn.matrix.org:3478?transport=udp", "turn:turn.matrix.org:3478?transport=tcp" ]
|
turn_uris: [ "turn:turn.matrix.org:3478?transport=udp", "turn:turn.matrix.org:3478?transport=tcp" ]
|
||||||
turn_shared_secret: n0t4ctuAllymatr1Xd0TorgSshar3d5ecret4obvIousreAsons
|
turn_shared_secret: n0t4ctuAllymatr1Xd0TorgSshar3d5ecret4obvIousreAsons
|
||||||
turn_user_lifetime: 86400000
|
turn_user_lifetime: 86400000
|
||||||
turn_allow_guests: True
|
|
||||||
|
|
||||||
Now, restart synapse::
|
Now, restart synapse::
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
URL Previews
|
|
||||||
============
|
|
||||||
|
|
||||||
Design notes on a URL previewing service for Matrix:
|
|
||||||
|
|
||||||
Options are:
|
|
||||||
|
|
||||||
1. Have an AS which listens for URLs, downloads them, and inserts an event that describes their metadata.
|
|
||||||
* Pros:
|
|
||||||
* Decouples the implementation entirely from Synapse.
|
|
||||||
* Uses existing Matrix events & content repo to store the metadata.
|
|
||||||
* Cons:
|
|
||||||
* Which AS should provide this service for a room, and why should you trust it?
|
|
||||||
* Doesn't work well with E2E; you'd have to cut the AS into every room
|
|
||||||
* the AS would end up subscribing to every room anyway.
|
|
||||||
|
|
||||||
2. Have a generic preview API (nothing to do with Matrix) that provides a previewing service:
|
|
||||||
* Pros:
|
|
||||||
* Simple and flexible; can be used by any clients at any point
|
|
||||||
* Cons:
|
|
||||||
* If each HS provides one of these independently, all the HSes in a room may needlessly DoS the target URI
|
|
||||||
* We need somewhere to store the URL metadata rather than just using Matrix itself
|
|
||||||
* We can't piggyback on matrix to distribute the metadata between HSes.
|
|
||||||
|
|
||||||
3. Make the synapse of the sending user responsible for spidering the URL and inserting an event asynchronously which describes the metadata.
|
|
||||||
* Pros:
|
|
||||||
* Works transparently for all clients
|
|
||||||
* Piggy-backs nicely on using Matrix for distributing the metadata.
|
|
||||||
* No confusion as to which AS
|
|
||||||
* Cons:
|
|
||||||
* Doesn't work with E2E
|
|
||||||
* We might want to decouple the implementation of the spider from the HS, given spider behaviour can be quite complicated and evolve much more rapidly than the HS. It's more like a bot than a core part of the server.
|
|
||||||
|
|
||||||
4. Make the sending client use the preview API and insert the event itself when successful.
|
|
||||||
* Pros:
|
|
||||||
* Works well with E2E
|
|
||||||
* No custom server functionality
|
|
||||||
* Lets the client customise the preview that they send (like on FB)
|
|
||||||
* Cons:
|
|
||||||
* Entirely specific to the sending client, whereas it'd be nice if /any/ URL was correctly previewed if clients support it.
|
|
||||||
|
|
||||||
5. Have the option of specifying a shared (centralised) previewing service used by a room, to avoid all the different HSes in the room DoSing the target.
|
|
||||||
|
|
||||||
Best solution is probably a combination of both 2 and 4.
|
|
||||||
* Sending clients do their best to create and send a preview at the point of sending the message, perhaps delaying the message until the preview is computed? (This also lets the user validate the preview before sending)
|
|
||||||
* Receiving clients have the option of going and creating their own preview if one doesn't arrive soon enough (or if the original sender didn't create one)
|
|
||||||
|
|
||||||
This is a bit magical though in that the preview could come from two entirely different sources - the sending HS or your local one. However, this can always be exposed to users: "Generate your own URL previews if none are available?"
|
|
||||||
|
|
||||||
This is tantamount also to senders calculating their own thumbnails for sending in advance of the main content - we are trusting the sender not to lie about the content in the thumbnail. Whereas currently thumbnails are calculated by the receiving homeserver to avoid this attack.
|
|
||||||
|
|
||||||
However, this kind of phishing attack does exist whether we let senders pick their thumbnails or not, in that a malicious sender can send normal text messages around the attachment claiming it to be legitimate. We could rely on (future) reputation/abuse management to punish users who phish (be it with bogus metadata or bogus descriptions). Bogus metadata is particularly bad though, especially if it's avoidable.
|
|
||||||
|
|
||||||
As a first cut, let's do #2 and have the receiver hit the API to calculate its own previews (as it does currently for image thumbnails). We can then extend/optimise this to option 4 as a special extra if needed.
|
|
||||||
|
|
||||||
API
|
|
||||||
---
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /_matrix/media/r0/preview_url?url=http://wherever.com
|
|
||||||
200 OK
|
|
||||||
{
|
|
||||||
"og:type" : "article"
|
|
||||||
"og:url" : "https://twitter.com/matrixdotorg/status/684074366691356672"
|
|
||||||
"og:title" : "Matrix on Twitter"
|
|
||||||
"og:image" : "https://pbs.twimg.com/profile_images/500400952029888512/yI0qtFi7_400x400.png"
|
|
||||||
"og:description" : "“Synapse 0.12 is out! Lots of polishing, performance &amp; bugfixes: /sync API, /r0 prefix, fulltext search, 3PID invites https://t.co/5alhXLLEGP”"
|
|
||||||
"og:site_name" : "Twitter"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* Downloads the URL
|
|
||||||
* If HTML, just stores it in RAM and parses it for OG meta tags
|
|
||||||
* Download any media OG meta tags to the media repo, and refer to them in the OG via mxc:// URIs.
|
|
||||||
* If a media filetype we know we can thumbnail: store it on disk, and hand it to the thumbnailer. Generate OG meta tags from the thumbnailer contents.
|
|
||||||
* Otherwise, don't bother downloading further.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
User Directory API Implementation
|
|
||||||
=================================
|
|
||||||
|
|
||||||
The user directory is currently maintained based on the 'visible' users
|
|
||||||
on this particular server - i.e. ones which your account shares a room with, or
|
|
||||||
who are present in a publicly viewable room present on the server.
|
|
||||||
|
|
||||||
The directory info is stored in various tables, which can (typically after
|
|
||||||
DB corruption) get stale or out of sync. If this happens, for now the
|
|
||||||
quickest solution to fix it is:
|
|
||||||
|
|
||||||
```
|
|
||||||
UPDATE user_directory_stream_pos SET stream_id = NULL;
|
|
||||||
```
|
|
||||||
|
|
||||||
and restart the synapse, which should then start a background task to
|
|
||||||
flush the current tables and regenerate the directory.
|
|
||||||
238
docs/workers.rst
238
docs/workers.rst
@@ -1,238 +0,0 @@
|
|||||||
Scaling synapse via workers
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Synapse has experimental support for splitting out functionality into
|
|
||||||
multiple separate python processes, helping greatly with scalability. These
|
|
||||||
processes are called 'workers', and are (eventually) intended to scale
|
|
||||||
horizontally independently.
|
|
||||||
|
|
||||||
All of the below is highly experimental and subject to change as Synapse evolves,
|
|
||||||
but documenting it here to help folks needing highly scalable Synapses similar
|
|
||||||
to the one running matrix.org!
|
|
||||||
|
|
||||||
All processes continue to share the same database instance, and as such, workers
|
|
||||||
only work with postgres based synapse deployments (sharing a single sqlite
|
|
||||||
across multiple processes is a recipe for disaster, plus you should be using
|
|
||||||
postgres anyway if you care about scalability).
|
|
||||||
|
|
||||||
The workers communicate with the master synapse process via a synapse-specific
|
|
||||||
TCP protocol called 'replication' - analogous to MySQL or Postgres style
|
|
||||||
database replication; feeding a stream of relevant data to the workers so they
|
|
||||||
can be kept in sync with the main synapse process and database state.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
To make effective use of the workers, you will need to configure an HTTP
|
|
||||||
reverse-proxy such as nginx or haproxy, which will direct incoming requests to
|
|
||||||
the correct worker, or to the main synapse instance. Note that this includes
|
|
||||||
requests made to the federation port. The caveats regarding running a
|
|
||||||
reverse-proxy on the federation port still apply (see
|
|
||||||
https://github.com/matrix-org/synapse/blob/master/README.rst#reverse-proxying-the-federation-port).
|
|
||||||
|
|
||||||
To enable workers, you need to add two replication listeners to the master
|
|
||||||
synapse, e.g.::
|
|
||||||
|
|
||||||
listeners:
|
|
||||||
# The TCP replication port
|
|
||||||
- port: 9092
|
|
||||||
bind_address: '127.0.0.1'
|
|
||||||
type: replication
|
|
||||||
# The HTTP replication port
|
|
||||||
- port: 9093
|
|
||||||
bind_address: '127.0.0.1'
|
|
||||||
type: http
|
|
||||||
resources:
|
|
||||||
- names: [replication]
|
|
||||||
|
|
||||||
Under **no circumstances** should these replication API listeners be exposed to
|
|
||||||
the public internet; it currently implements no authentication whatsoever and is
|
|
||||||
unencrypted.
|
|
||||||
|
|
||||||
(Roughly, the TCP port is used for streaming data from the master to the
|
|
||||||
workers, and the HTTP port for the workers to send data to the main
|
|
||||||
synapse process.)
|
|
||||||
|
|
||||||
You then create a set of configs for the various worker processes. These
|
|
||||||
should be worker configuration files, and should be stored in a dedicated
|
|
||||||
subdirectory, to allow synctl to manipulate them.
|
|
||||||
|
|
||||||
Each worker configuration file inherits the configuration of the main homeserver
|
|
||||||
configuration file. You can then override configuration specific to that worker,
|
|
||||||
e.g. the HTTP listener that it provides (if any); logging configuration; etc.
|
|
||||||
You should minimise the number of overrides though to maintain a usable config.
|
|
||||||
|
|
||||||
You must specify the type of worker application (``worker_app``). The currently
|
|
||||||
available worker applications are listed below. You must also specify the
|
|
||||||
replication endpoints that it's talking to on the main synapse process.
|
|
||||||
``worker_replication_host`` should specify the host of the main synapse,
|
|
||||||
``worker_replication_port`` should point to the TCP replication listener port and
|
|
||||||
``worker_replication_http_port`` should point to the HTTP replication port.
|
|
||||||
|
|
||||||
Currently, only the ``event_creator`` worker requires specifying
|
|
||||||
``worker_replication_http_port``.
|
|
||||||
|
|
||||||
For instance::
|
|
||||||
|
|
||||||
worker_app: synapse.app.synchrotron
|
|
||||||
|
|
||||||
# The replication listener on the synapse to talk to.
|
|
||||||
worker_replication_host: 127.0.0.1
|
|
||||||
worker_replication_port: 9092
|
|
||||||
worker_replication_http_port: 9093
|
|
||||||
|
|
||||||
worker_listeners:
|
|
||||||
- type: http
|
|
||||||
port: 8083
|
|
||||||
resources:
|
|
||||||
- names:
|
|
||||||
- client
|
|
||||||
|
|
||||||
worker_daemonize: True
|
|
||||||
worker_pid_file: /home/matrix/synapse/synchrotron.pid
|
|
||||||
worker_log_config: /home/matrix/synapse/config/synchrotron_log_config.yaml
|
|
||||||
|
|
||||||
...is a full configuration for a synchrotron worker instance, which will expose a
|
|
||||||
plain HTTP ``/sync`` endpoint on port 8083 separately from the ``/sync`` endpoint provided
|
|
||||||
by the main synapse.
|
|
||||||
|
|
||||||
Obviously you should configure your reverse-proxy to route the relevant
|
|
||||||
endpoints to the worker (``localhost:8083`` in the above example).
|
|
||||||
|
|
||||||
Finally, to actually run your worker-based synapse, you must pass synctl the -a
|
|
||||||
commandline option to tell it to operate on all the worker configurations found
|
|
||||||
in the given directory, e.g.::
|
|
||||||
|
|
||||||
synctl -a $CONFIG/workers start
|
|
||||||
|
|
||||||
Currently one should always restart all workers when restarting or upgrading
|
|
||||||
synapse, unless you explicitly know it's safe not to. For instance, restarting
|
|
||||||
synapse without restarting all the synchrotrons may result in broken typing
|
|
||||||
notifications.
|
|
||||||
|
|
||||||
To manipulate a specific worker, you pass the -w option to synctl::
|
|
||||||
|
|
||||||
synctl -w $CONFIG/workers/synchrotron.yaml restart
|
|
||||||
|
|
||||||
|
|
||||||
Available worker applications
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
``synapse.app.pusher``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles sending push notifications to sygnal and email. Doesn't handle any
|
|
||||||
REST endpoints itself, but you should set ``start_pushers: False`` in the
|
|
||||||
shared configuration file to stop the main synapse sending these notifications.
|
|
||||||
|
|
||||||
Note this worker cannot be load-balanced: only one instance should be active.
|
|
||||||
|
|
||||||
``synapse.app.synchrotron``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The synchrotron handles ``sync`` requests from clients. In particular, it can
|
|
||||||
handle REST endpoints matching the following regular expressions::
|
|
||||||
|
|
||||||
^/_matrix/client/(v2_alpha|r0)/sync$
|
|
||||||
^/_matrix/client/(api/v1|v2_alpha|r0)/events$
|
|
||||||
^/_matrix/client/(api/v1|r0)/initialSync$
|
|
||||||
^/_matrix/client/(api/v1|r0)/rooms/[^/]+/initialSync$
|
|
||||||
|
|
||||||
The above endpoints should all be routed to the synchrotron worker by the
|
|
||||||
reverse-proxy configuration.
|
|
||||||
|
|
||||||
It is possible to run multiple instances of the synchrotron to scale
|
|
||||||
horizontally. In this case the reverse-proxy should be configured to
|
|
||||||
load-balance across the instances, though it will be more efficient if all
|
|
||||||
requests from a particular user are routed to a single instance. Extracting
|
|
||||||
a userid from the access token is currently left as an exercise for the reader.
|
|
||||||
|
|
||||||
``synapse.app.appservice``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles sending output traffic to Application Services. Doesn't handle any
|
|
||||||
REST endpoints itself, but you should set ``notify_appservices: False`` in the
|
|
||||||
shared configuration file to stop the main synapse sending these notifications.
|
|
||||||
|
|
||||||
Note this worker cannot be load-balanced: only one instance should be active.
|
|
||||||
|
|
||||||
``synapse.app.federation_reader``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles a subset of federation endpoints. In particular, it can handle REST
|
|
||||||
endpoints matching the following regular expressions::
|
|
||||||
|
|
||||||
^/_matrix/federation/v1/event/
|
|
||||||
^/_matrix/federation/v1/state/
|
|
||||||
^/_matrix/federation/v1/state_ids/
|
|
||||||
^/_matrix/federation/v1/backfill/
|
|
||||||
^/_matrix/federation/v1/get_missing_events/
|
|
||||||
^/_matrix/federation/v1/publicRooms
|
|
||||||
|
|
||||||
The above endpoints should all be routed to the federation_reader worker by the
|
|
||||||
reverse-proxy configuration.
|
|
||||||
|
|
||||||
``synapse.app.federation_sender``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles sending federation traffic to other servers. Doesn't handle any
|
|
||||||
REST endpoints itself, but you should set ``send_federation: False`` in the
|
|
||||||
shared configuration file to stop the main synapse sending this traffic.
|
|
||||||
|
|
||||||
Note this worker cannot be load-balanced: only one instance should be active.
|
|
||||||
|
|
||||||
``synapse.app.media_repository``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles the media repository. It can handle all endpoints starting with::
|
|
||||||
|
|
||||||
/_matrix/media/
|
|
||||||
|
|
||||||
You should also set ``enable_media_repo: False`` in the shared configuration
|
|
||||||
file to stop the main synapse running background jobs related to managing the
|
|
||||||
media repository.
|
|
||||||
|
|
||||||
Note this worker cannot be load-balanced: only one instance should be active.
|
|
||||||
|
|
||||||
``synapse.app.client_reader``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles client API endpoints. It can handle REST endpoints matching the
|
|
||||||
following regular expressions::
|
|
||||||
|
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/publicRooms$
|
|
||||||
|
|
||||||
``synapse.app.user_dir``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles searches in the user directory. It can handle REST endpoints matching
|
|
||||||
the following regular expressions::
|
|
||||||
|
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/user_directory/search$
|
|
||||||
|
|
||||||
``synapse.app.frontend_proxy``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Proxies some frequently-requested client endpoints to add caching and remove
|
|
||||||
load from the main synapse. It can handle REST endpoints matching the following
|
|
||||||
regular expressions::
|
|
||||||
|
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/keys/upload
|
|
||||||
|
|
||||||
It will proxy any requests it cannot handle to the main synapse instance. It
|
|
||||||
must therefore be configured with the location of the main instance, via
|
|
||||||
the ``worker_main_http_uri`` setting in the frontend_proxy worker configuration
|
|
||||||
file. For example::
|
|
||||||
|
|
||||||
worker_main_http_uri: http://127.0.0.1:8008
|
|
||||||
|
|
||||||
|
|
||||||
``synapse.app.event_creator``
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Handles non-state event creation. It can handle REST endpoints matching::
|
|
||||||
|
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/rooms/.*/send
|
|
||||||
|
|
||||||
It will create events locally and then send them on to the main synapse
|
|
||||||
instance to be persisted and handled.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
: ${WORKSPACE:="$(pwd)"}
|
|
||||||
|
|
||||||
export WORKSPACE
|
|
||||||
export PYTHONDONTWRITEBYTECODE=yep
|
|
||||||
export SYNAPSE_CACHE_FACTOR=1
|
|
||||||
|
|
||||||
export HAPROXY_BIN=/home/haproxy/haproxy-1.6.11/haproxy
|
|
||||||
|
|
||||||
./jenkins/prepare_synapse.sh
|
|
||||||
./jenkins/clone.sh sytest https://github.com/matrix-org/sytest.git
|
|
||||||
./jenkins/clone.sh dendron https://github.com/matrix-org/dendron.git
|
|
||||||
./dendron/jenkins/build_dendron.sh
|
|
||||||
./sytest/jenkins/prep_sytest_for_postgres.sh
|
|
||||||
|
|
||||||
./sytest/jenkins/install_and_run.sh \
|
|
||||||
--python $WORKSPACE/.tox/py27/bin/python \
|
|
||||||
--synapse-directory $WORKSPACE \
|
|
||||||
--dendron $WORKSPACE/dendron/bin/dendron \
|
|
||||||
--haproxy \
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
: ${WORKSPACE:="$(pwd)"}
|
|
||||||
|
|
||||||
export WORKSPACE
|
|
||||||
export PYTHONDONTWRITEBYTECODE=yep
|
|
||||||
export SYNAPSE_CACHE_FACTOR=1
|
|
||||||
|
|
||||||
./jenkins/prepare_synapse.sh
|
|
||||||
./jenkins/clone.sh sytest https://github.com/matrix-org/sytest.git
|
|
||||||
./jenkins/clone.sh dendron https://github.com/matrix-org/dendron.git
|
|
||||||
./dendron/jenkins/build_dendron.sh
|
|
||||||
./sytest/jenkins/prep_sytest_for_postgres.sh
|
|
||||||
|
|
||||||
./sytest/jenkins/install_and_run.sh \
|
|
||||||
--python $WORKSPACE/.tox/py27/bin/python \
|
|
||||||
--synapse-directory $WORKSPACE \
|
|
||||||
--dendron $WORKSPACE/dendron/bin/dendron \
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
: ${WORKSPACE:="$(pwd)"}
|
|
||||||
|
|
||||||
export PYTHONDONTWRITEBYTECODE=yep
|
|
||||||
export SYNAPSE_CACHE_FACTOR=1
|
|
||||||
|
|
||||||
# Output test results as junit xml
|
|
||||||
export TRIAL_FLAGS="--reporter=subunit"
|
|
||||||
export TOXSUFFIX="| subunit-1to2 | subunit2junitxml --no-passthrough --output-to=results.xml"
|
|
||||||
# Write coverage reports to a separate file for each process
|
|
||||||
export COVERAGE_OPTS="-p"
|
|
||||||
export DUMP_COVERAGE_COMMAND="coverage help"
|
|
||||||
|
|
||||||
# Output flake8 violations to violations.flake8.log
|
|
||||||
export PEP8SUFFIX="--output-file=violations.flake8.log"
|
|
||||||
|
|
||||||
rm .coverage* || echo "No coverage files to remove"
|
|
||||||
|
|
||||||
tox -e packaging -e pep8
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
: ${WORKSPACE:="$(pwd)"}
|
|
||||||
|
|
||||||
export WORKSPACE
|
|
||||||
export PYTHONDONTWRITEBYTECODE=yep
|
|
||||||
export SYNAPSE_CACHE_FACTOR=1
|
|
||||||
|
|
||||||
./jenkins/prepare_synapse.sh
|
|
||||||
./jenkins/clone.sh sytest https://github.com/matrix-org/sytest.git
|
|
||||||
|
|
||||||
./sytest/jenkins/prep_sytest_for_postgres.sh
|
|
||||||
|
|
||||||
./sytest/jenkins/install_and_run.sh \
|
|
||||||
--python $WORKSPACE/.tox/py27/bin/python \
|
|
||||||
--synapse-directory $WORKSPACE \
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
: ${WORKSPACE:="$(pwd)"}
|
|
||||||
|
|
||||||
export WORKSPACE
|
|
||||||
export PYTHONDONTWRITEBYTECODE=yep
|
|
||||||
export SYNAPSE_CACHE_FACTOR=1
|
|
||||||
|
|
||||||
./jenkins/prepare_synapse.sh
|
|
||||||
./jenkins/clone.sh sytest https://github.com/matrix-org/sytest.git
|
|
||||||
|
|
||||||
./sytest/jenkins/install_and_run.sh \
|
|
||||||
--python $WORKSPACE/.tox/py27/bin/python \
|
|
||||||
--synapse-directory $WORKSPACE \
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
: ${WORKSPACE:="$(pwd)"}
|
|
||||||
|
|
||||||
export PYTHONDONTWRITEBYTECODE=yep
|
|
||||||
export SYNAPSE_CACHE_FACTOR=1
|
|
||||||
|
|
||||||
# Output test results as junit xml
|
|
||||||
export TRIAL_FLAGS="--reporter=subunit"
|
|
||||||
export TOXSUFFIX="| subunit-1to2 | subunit2junitxml --no-passthrough --output-to=results.xml"
|
|
||||||
# Write coverage reports to a separate file for each process
|
|
||||||
export COVERAGE_OPTS="-p"
|
|
||||||
export DUMP_COVERAGE_COMMAND="coverage help"
|
|
||||||
|
|
||||||
# Output flake8 violations to violations.flake8.log
|
|
||||||
# Don't exit with non-0 status code on Jenkins,
|
|
||||||
# so that the build steps continue and a later step can decided whether to
|
|
||||||
# UNSTABLE or FAILURE this build.
|
|
||||||
export PEP8SUFFIX="--output-file=violations.flake8.log || echo flake8 finished with status code \$?"
|
|
||||||
|
|
||||||
rm .coverage* || echo "No coverage files to remove"
|
|
||||||
|
|
||||||
tox --notest -e py27
|
|
||||||
TOX_BIN=$WORKSPACE/.tox/py27/bin
|
|
||||||
python synapse/python_dependencies.py | xargs -n1 $TOX_BIN/pip install
|
|
||||||
$TOX_BIN/pip install lxml
|
|
||||||
|
|
||||||
tox -e py27
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
# This clones a project from github into a named subdirectory
|
|
||||||
# If the project has a branch with the same name as this branch
|
|
||||||
# then it will checkout that branch after cloning.
|
|
||||||
# Otherwise it will checkout "origin/develop."
|
|
||||||
# The first argument is the name of the directory to checkout
|
|
||||||
# the branch into.
|
|
||||||
# The second argument is the URL of the remote repository to checkout.
|
|
||||||
# Usually something like https://github.com/matrix-org/sytest.git
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
NAME=$1
|
|
||||||
PROJECT=$2
|
|
||||||
BASE=".$NAME-base"
|
|
||||||
|
|
||||||
# Update our mirror.
|
|
||||||
if [ ! -d ".$NAME-base" ]; then
|
|
||||||
# Create a local mirror of the source repository.
|
|
||||||
# This saves us from having to download the entire repository
|
|
||||||
# when this script is next run.
|
|
||||||
git clone "$PROJECT" "$BASE" --mirror
|
|
||||||
else
|
|
||||||
# Fetch any updates from the source repository.
|
|
||||||
(cd "$BASE"; git fetch -p)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove the existing repository so that we have a clean copy
|
|
||||||
rm -rf "$NAME"
|
|
||||||
# Cloning with --shared means that we will share portions of the
|
|
||||||
# .git directory with our local mirror.
|
|
||||||
git clone "$BASE" "$NAME" --shared
|
|
||||||
|
|
||||||
# Jenkins may have supplied us with the name of the branch in the
|
|
||||||
# environment. Otherwise we will have to guess based on the current
|
|
||||||
# commit.
|
|
||||||
: ${GIT_BRANCH:="origin/$(git rev-parse --abbrev-ref HEAD)"}
|
|
||||||
cd "$NAME"
|
|
||||||
# check out the relevant branch
|
|
||||||
git checkout "${GIT_BRANCH}" || (
|
|
||||||
echo >&2 "No ref ${GIT_BRANCH} found, falling back to develop"
|
|
||||||
git checkout "origin/develop"
|
|
||||||
)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
cd "`dirname $0`/.."
|
|
||||||
|
|
||||||
TOX_DIR=$WORKSPACE/.tox
|
|
||||||
|
|
||||||
mkdir -p $TOX_DIR
|
|
||||||
|
|
||||||
if ! [ $TOX_DIR -ef .tox ]; then
|
|
||||||
ln -s "$TOX_DIR" .tox
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set up the virtualenv
|
|
||||||
tox -e py27 --notest -v
|
|
||||||
|
|
||||||
TOX_BIN=$TOX_DIR/py27/bin
|
|
||||||
$TOX_BIN/pip install setuptools
|
|
||||||
{ python synapse/python_dependencies.py
|
|
||||||
echo lxml psycopg2
|
|
||||||
} | xargs $TOX_BIN/pip install
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015 OpenMarket 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.
|
||||||
@@ -25,26 +25,17 @@ import urllib2
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
def request_registration(user, password, server_location, shared_secret, admin=False):
|
def request_registration(user, password, server_location, shared_secret):
|
||||||
mac = hmac.new(
|
mac = hmac.new(
|
||||||
key=shared_secret,
|
key=shared_secret,
|
||||||
|
msg=user,
|
||||||
digestmod=hashlib.sha1,
|
digestmod=hashlib.sha1,
|
||||||
)
|
).hexdigest()
|
||||||
|
|
||||||
mac.update(user)
|
|
||||||
mac.update("\x00")
|
|
||||||
mac.update(password)
|
|
||||||
mac.update("\x00")
|
|
||||||
mac.update("admin" if admin else "notadmin")
|
|
||||||
|
|
||||||
mac = mac.hexdigest()
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"user": user,
|
"username": user,
|
||||||
"password": password,
|
"password": password,
|
||||||
"mac": mac,
|
"mac": mac,
|
||||||
"type": "org.matrix.login.shared_secret",
|
|
||||||
"admin": admin,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server_location = server_location.rstrip("/")
|
server_location = server_location.rstrip("/")
|
||||||
@@ -52,7 +43,7 @@ def request_registration(user, password, server_location, shared_secret, admin=F
|
|||||||
print "Sending registration request..."
|
print "Sending registration request..."
|
||||||
|
|
||||||
req = urllib2.Request(
|
req = urllib2.Request(
|
||||||
"%s/_matrix/client/api/v1/register" % (server_location,),
|
"%s/_matrix/client/v2_alpha/register" % (server_location,),
|
||||||
data=json.dumps(data),
|
data=json.dumps(data),
|
||||||
headers={'Content-Type': 'application/json'}
|
headers={'Content-Type': 'application/json'}
|
||||||
)
|
)
|
||||||
@@ -76,7 +67,7 @@ def request_registration(user, password, server_location, shared_secret, admin=F
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def register_new_user(user, password, server_location, shared_secret, admin):
|
def register_new_user(user, password, server_location, shared_secret):
|
||||||
if not user:
|
if not user:
|
||||||
try:
|
try:
|
||||||
default_user = getpass.getuser()
|
default_user = getpass.getuser()
|
||||||
@@ -107,14 +98,7 @@ def register_new_user(user, password, server_location, shared_secret, admin):
|
|||||||
print "Passwords do not match"
|
print "Passwords do not match"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not admin:
|
request_registration(user, password, server_location, shared_secret)
|
||||||
admin = raw_input("Make admin [no]: ")
|
|
||||||
if admin in ("y", "yes", "true"):
|
|
||||||
admin = True
|
|
||||||
else:
|
|
||||||
admin = False
|
|
||||||
|
|
||||||
request_registration(user, password, server_location, shared_secret, bool(admin))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -134,11 +118,6 @@ if __name__ == "__main__":
|
|||||||
default=None,
|
default=None,
|
||||||
help="New password for user. Will prompt if omitted.",
|
help="New password for user. Will prompt if omitted.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"-a", "--admin",
|
|
||||||
action="store_true",
|
|
||||||
help="Register new user as an admin. Will prompt if omitted.",
|
|
||||||
)
|
|
||||||
|
|
||||||
group = parser.add_mutually_exclusive_group(required=True)
|
group = parser.add_mutually_exclusive_group(required=True)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
@@ -171,4 +150,4 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
secret = args.shared_secret
|
secret = args.shared_secret
|
||||||
|
|
||||||
register_new_user(args.user, args.password, args.server_url, secret, args.admin)
|
register_new_user(args.user, args.password, args.server_url, secret)
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
.header {
|
|
||||||
border-bottom: 4px solid #e4f7ed ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif_link a, .footer a {
|
|
||||||
color: #76CFA6 ! important;
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, code {
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page {
|
|
||||||
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
|
||||||
font-color: #454545;
|
|
||||||
font-size: 12pt;
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#inner {
|
|
||||||
width: 640px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
width: 100%;
|
|
||||||
height: 87px;
|
|
||||||
color: #454545;
|
|
||||||
border-bottom: 4px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
text-align: right;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.salutation {
|
|
||||||
padding-top: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summarytext {
|
|
||||||
}
|
|
||||||
|
|
||||||
.room {
|
|
||||||
width: 100%;
|
|
||||||
color: #454545;
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room_header td {
|
|
||||||
padding-top: 38px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room_name {
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room_header h2 {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-left: 75px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room_avatar {
|
|
||||||
width: 56px;
|
|
||||||
line-height: 0px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room_avatar img {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif {
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
margin-top: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical_message .sender_avatar {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* spell out opacity and historical_message class names for Outlook aka Word */
|
|
||||||
.historical_message .sender_name {
|
|
||||||
color: #e3e3e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical_message .message_time {
|
|
||||||
color: #e3e3e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical_message .message_body {
|
|
||||||
color: #c7c7c7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.historical_message td,
|
|
||||||
.message td {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sender_avatar {
|
|
||||||
width: 56px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sender_avatar img {
|
|
||||||
margin-top: -2px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sender_name {
|
|
||||||
display: inline;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #a2a2a2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message_time {
|
|
||||||
text-align: right;
|
|
||||||
width: 100px;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #a2a2a2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message_body {
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif_link td {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif_link a, .footer a {
|
|
||||||
color: #454545;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
{% for message in notif.messages %}
|
|
||||||
<tr class="{{ "historical_message" if message.is_historical else "message" }}">
|
|
||||||
<td class="sender_avatar">
|
|
||||||
{% if loop.index0 == 0 or notif.messages[loop.index0 - 1].sender_name != notif.messages[loop.index0].sender_name %}
|
|
||||||
{% if message.sender_avatar_url %}
|
|
||||||
<img alt="" class="sender_avatar" src="{{ message.sender_avatar_url|mxc_to_http(32,32) }}" />
|
|
||||||
{% else %}
|
|
||||||
{% if message.sender_hash % 3 == 0 %}
|
|
||||||
<img class="sender_avatar" src="https://vector.im/beta/img/76cfa6.png" />
|
|
||||||
{% elif message.sender_hash % 3 == 1 %}
|
|
||||||
<img class="sender_avatar" src="https://vector.im/beta/img/50e2c2.png" />
|
|
||||||
{% else %}
|
|
||||||
<img class="sender_avatar" src="https://vector.im/beta/img/f4c371.png" />
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="message_contents">
|
|
||||||
{% if loop.index0 == 0 or notif.messages[loop.index0 - 1].sender_name != notif.messages[loop.index0].sender_name %}
|
|
||||||
<div class="sender_name">{% if message.msgtype == "m.emote" %}*{% endif %} {{ message.sender_name }}</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="message_body">
|
|
||||||
{% if message.msgtype == "m.text" %}
|
|
||||||
{{ message.body_text_html }}
|
|
||||||
{% elif message.msgtype == "m.emote" %}
|
|
||||||
{{ message.body_text_html }}
|
|
||||||
{% elif message.msgtype == "m.notice" %}
|
|
||||||
{{ message.body_text_html }}
|
|
||||||
{% elif message.msgtype == "m.image" %}
|
|
||||||
<img src="{{ message.image_url|mxc_to_http(640, 480, scale) }}" />
|
|
||||||
{% elif message.msgtype == "m.file" %}
|
|
||||||
<span class="filename">{{ message.body_text_plain }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="message_time">{{ message.ts|format_ts("%H:%M") }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr class="notif_link">
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<a href="{{ notif.link }}">View {{ room.title }}</a>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{% for message in notif.messages %}
|
|
||||||
{% if message.msgtype == "m.emote" %}* {% endif %}{{ message.sender_name }} ({{ message.ts|format_ts("%H:%M") }})
|
|
||||||
{% if message.msgtype == "m.text" %}
|
|
||||||
{{ message.body_text_plain }}
|
|
||||||
{% elif message.msgtype == "m.emote" %}
|
|
||||||
{{ message.body_text_plain }}
|
|
||||||
{% elif message.msgtype == "m.notice" %}
|
|
||||||
{{ message.body_text_plain }}
|
|
||||||
{% elif message.msgtype == "m.image" %}
|
|
||||||
{{ message.body_text_plain }}
|
|
||||||
{% elif message.msgtype == "m.file" %}
|
|
||||||
{{ message.body_text_plain }}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
View {{ room.title }} at {{ notif.link }}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<style type="text/css">
|
|
||||||
{% include 'mail.css' without context %}
|
|
||||||
{% include "mail-%s.css" % app_name ignore missing without context %}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<table id="page">
|
|
||||||
<tr>
|
|
||||||
<td> </td>
|
|
||||||
<td id="inner">
|
|
||||||
<table class="header">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="salutation">Hi {{ user_display_name }},</div>
|
|
||||||
<div class="summarytext">{{ summary_text }}</div>
|
|
||||||
</td>
|
|
||||||
<td class="logo">
|
|
||||||
{% if app_name == "Riot" %}
|
|
||||||
<img src="http://matrix.org/img/riot-logo-email.png" width="83" height="83" alt="[Riot]"/>
|
|
||||||
{% elif app_name == "Vector" %}
|
|
||||||
<img src="http://matrix.org/img/vector-logo-email.png" width="64" height="83" alt="[Vector]"/>
|
|
||||||
{% else %}
|
|
||||||
<img src="http://matrix.org/img/matrix-120x51.png" width="120" height="51" alt="[matrix]"/>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
{% for room in rooms %}
|
|
||||||
{% include 'room.html' with context %}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="footer">
|
|
||||||
<a href="{{ unsubscribe_link }}">Unsubscribe</a>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<div class="debug">
|
|
||||||
Sending email at {{ reason.now|format_ts("%c") }} due to activity in room {{ reason.room_name }} because
|
|
||||||
an event was received at {{ reason.received_at|format_ts("%c") }}
|
|
||||||
which is more than {{ "%.1f"|format(reason.delay_before_mail_ms / (60*1000)) }} ({{ reason.delay_before_mail_ms }}) mins ago,
|
|
||||||
{% if reason.last_sent_ts %}
|
|
||||||
and the last time we sent a mail for this room was {{ reason.last_sent_ts|format_ts("%c") }},
|
|
||||||
which is more than {{ "%.1f"|format(reason.throttle_ms / (60*1000)) }} (current throttle_ms) mins ago.
|
|
||||||
{% else %}
|
|
||||||
and we don't have a last time we sent a mail for this room.
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
Hi {{ user_display_name }},
|
|
||||||
|
|
||||||
{{ summary_text }}
|
|
||||||
|
|
||||||
{% for room in rooms %}
|
|
||||||
{% include 'room.txt' with context %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
You can disable these notifications at {{ unsubscribe_link }}
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<table class="room">
|
|
||||||
<tr class="room_header">
|
|
||||||
<td class="room_avatar">
|
|
||||||
{% if room.avatar_url %}
|
|
||||||
<img alt="" src="{{ room.avatar_url|mxc_to_http(48,48) }}" />
|
|
||||||
{% else %}
|
|
||||||
{% if room.hash % 3 == 0 %}
|
|
||||||
<img alt="" src="https://vector.im/beta/img/76cfa6.png" />
|
|
||||||
{% elif room.hash % 3 == 1 %}
|
|
||||||
<img alt="" src="https://vector.im/beta/img/50e2c2.png" />
|
|
||||||
{% else %}
|
|
||||||
<img alt="" src="https://vector.im/beta/img/f4c371.png" />
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="room_name" colspan="2">
|
|
||||||
{{ room.title }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% if room.invite %}
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<a href="{{ room.link }}">Join the conversation.</a>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
{% else %}
|
|
||||||
{% for notif in room.notifs %}
|
|
||||||
{% include 'notif.html' with context %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{{ room.title }}
|
|
||||||
|
|
||||||
{% if room.invite %}
|
|
||||||
You've been invited, join at {{ room.link }}
|
|
||||||
{% else %}
|
|
||||||
{% for notif in room.notifs %}
|
|
||||||
{% include 'notif.txt' with context %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import psycopg2
|
|
||||||
import yaml
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import hashlib
|
|
||||||
from unpaddedbase64 import encode_base64
|
|
||||||
from signedjson.key import read_signing_keys
|
|
||||||
from signedjson.sign import sign_json
|
|
||||||
from canonicaljson import encode_canonical_json
|
|
||||||
|
|
||||||
|
|
||||||
def select_v1_keys(connection):
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT server_name, key_id, verify_key FROM server_signature_keys")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
cursor.close()
|
|
||||||
results = {}
|
|
||||||
for server_name, key_id, verify_key in rows:
|
|
||||||
results.setdefault(server_name, {})[key_id] = encode_base64(verify_key)
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def select_v1_certs(connection):
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT server_name, tls_certificate FROM server_tls_certificates")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
cursor.close()
|
|
||||||
results = {}
|
|
||||||
for server_name, tls_certificate in rows:
|
|
||||||
results[server_name] = tls_certificate
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def select_v2_json(connection):
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT server_name, key_id, key_json FROM server_keys_json")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
cursor.close()
|
|
||||||
results = {}
|
|
||||||
for server_name, key_id, key_json in rows:
|
|
||||||
results.setdefault(server_name, {})[key_id] = json.loads(str(key_json).decode("utf-8"))
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def convert_v1_to_v2(server_name, valid_until, keys, certificate):
|
|
||||||
return {
|
|
||||||
"old_verify_keys": {},
|
|
||||||
"server_name": server_name,
|
|
||||||
"verify_keys": {
|
|
||||||
key_id: {"key": key}
|
|
||||||
for key_id, key in keys.items()
|
|
||||||
},
|
|
||||||
"valid_until_ts": valid_until,
|
|
||||||
"tls_fingerprints": [fingerprint(certificate)],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def fingerprint(certificate):
|
|
||||||
finger = hashlib.sha256(certificate)
|
|
||||||
return {"sha256": encode_base64(finger.digest())}
|
|
||||||
|
|
||||||
|
|
||||||
def rows_v2(server, json):
|
|
||||||
valid_until = json["valid_until_ts"]
|
|
||||||
key_json = encode_canonical_json(json)
|
|
||||||
for key_id in json["verify_keys"]:
|
|
||||||
yield (server, key_id, "-", valid_until, valid_until, buffer(key_json))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
config = yaml.load(open(sys.argv[1]))
|
|
||||||
valid_until = int(time.time() / (3600 * 24)) * 1000 * 3600 * 24
|
|
||||||
|
|
||||||
server_name = config["server_name"]
|
|
||||||
signing_key = read_signing_keys(open(config["signing_key_path"]))[0]
|
|
||||||
|
|
||||||
database = config["database"]
|
|
||||||
assert database["name"] == "psycopg2", "Can only convert for postgresql"
|
|
||||||
args = database["args"]
|
|
||||||
args.pop("cp_max")
|
|
||||||
args.pop("cp_min")
|
|
||||||
connection = psycopg2.connect(**args)
|
|
||||||
keys = select_v1_keys(connection)
|
|
||||||
certificates = select_v1_certs(connection)
|
|
||||||
json = select_v2_json(connection)
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
for server in keys:
|
|
||||||
if not server in json:
|
|
||||||
v2_json = convert_v1_to_v2(
|
|
||||||
server, valid_until, keys[server], certificates[server]
|
|
||||||
)
|
|
||||||
v2_json = sign_json(v2_json, server_name, signing_key)
|
|
||||||
result[server] = v2_json
|
|
||||||
|
|
||||||
yaml.safe_dump(result, sys.stdout, default_flow_style=False)
|
|
||||||
|
|
||||||
rows = list(
|
|
||||||
row for server, json in result.items()
|
|
||||||
for row in rows_v2(server, json)
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.executemany(
|
|
||||||
"INSERT INTO server_keys_json ("
|
|
||||||
" server_name, key_id, from_server,"
|
|
||||||
" ts_added_ms, ts_valid_until_ms, key_json"
|
|
||||||
") VALUES (%s, %s, %s, %s, %s, %s)",
|
|
||||||
rows
|
|
||||||
)
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
|
|
||||||
import ast
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
class DefinitionVisitor(ast.NodeVisitor):
|
|
||||||
def __init__(self):
|
|
||||||
super(DefinitionVisitor, self).__init__()
|
|
||||||
self.functions = {}
|
|
||||||
self.classes = {}
|
|
||||||
self.names = {}
|
|
||||||
self.attrs = set()
|
|
||||||
self.definitions = {
|
|
||||||
'def': self.functions,
|
|
||||||
'class': self.classes,
|
|
||||||
'names': self.names,
|
|
||||||
'attrs': self.attrs,
|
|
||||||
}
|
|
||||||
|
|
||||||
def visit_Name(self, node):
|
|
||||||
self.names.setdefault(type(node.ctx).__name__, set()).add(node.id)
|
|
||||||
|
|
||||||
def visit_Attribute(self, node):
|
|
||||||
self.attrs.add(node.attr)
|
|
||||||
for child in ast.iter_child_nodes(node):
|
|
||||||
self.visit(child)
|
|
||||||
|
|
||||||
def visit_ClassDef(self, node):
|
|
||||||
visitor = DefinitionVisitor()
|
|
||||||
self.classes[node.name] = visitor.definitions
|
|
||||||
for child in ast.iter_child_nodes(node):
|
|
||||||
visitor.visit(child)
|
|
||||||
|
|
||||||
def visit_FunctionDef(self, node):
|
|
||||||
visitor = DefinitionVisitor()
|
|
||||||
self.functions[node.name] = visitor.definitions
|
|
||||||
for child in ast.iter_child_nodes(node):
|
|
||||||
visitor.visit(child)
|
|
||||||
|
|
||||||
|
|
||||||
def non_empty(defs):
|
|
||||||
functions = {name: non_empty(f) for name, f in defs['def'].items()}
|
|
||||||
classes = {name: non_empty(f) for name, f in defs['class'].items()}
|
|
||||||
result = {}
|
|
||||||
if functions: result['def'] = functions
|
|
||||||
if classes: result['class'] = classes
|
|
||||||
names = defs['names']
|
|
||||||
uses = []
|
|
||||||
for name in names.get('Load', ()):
|
|
||||||
if name not in names.get('Param', ()) and name not in names.get('Store', ()):
|
|
||||||
uses.append(name)
|
|
||||||
uses.extend(defs['attrs'])
|
|
||||||
if uses: result['uses'] = uses
|
|
||||||
result['names'] = names
|
|
||||||
result['attrs'] = defs['attrs']
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def definitions_in_code(input_code):
|
|
||||||
input_ast = ast.parse(input_code)
|
|
||||||
visitor = DefinitionVisitor()
|
|
||||||
visitor.visit(input_ast)
|
|
||||||
definitions = non_empty(visitor.definitions)
|
|
||||||
return definitions
|
|
||||||
|
|
||||||
|
|
||||||
def definitions_in_file(filepath):
|
|
||||||
with open(filepath) as f:
|
|
||||||
return definitions_in_code(f.read())
|
|
||||||
|
|
||||||
|
|
||||||
def defined_names(prefix, defs, names):
|
|
||||||
for name, funcs in defs.get('def', {}).items():
|
|
||||||
names.setdefault(name, {'defined': []})['defined'].append(prefix + name)
|
|
||||||
defined_names(prefix + name + ".", funcs, names)
|
|
||||||
|
|
||||||
for name, funcs in defs.get('class', {}).items():
|
|
||||||
names.setdefault(name, {'defined': []})['defined'].append(prefix + name)
|
|
||||||
defined_names(prefix + name + ".", funcs, names)
|
|
||||||
|
|
||||||
|
|
||||||
def used_names(prefix, item, defs, names):
|
|
||||||
for name, funcs in defs.get('def', {}).items():
|
|
||||||
used_names(prefix + name + ".", name, funcs, names)
|
|
||||||
|
|
||||||
for name, funcs in defs.get('class', {}).items():
|
|
||||||
used_names(prefix + name + ".", name, funcs, names)
|
|
||||||
|
|
||||||
path = prefix.rstrip('.')
|
|
||||||
for used in defs.get('uses', ()):
|
|
||||||
if used in names:
|
|
||||||
if item:
|
|
||||||
names[item].setdefault('uses', []).append(used)
|
|
||||||
names[used].setdefault('used', {}).setdefault(item, []).append(path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys, os, argparse, re
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Find definitions.')
|
|
||||||
parser.add_argument(
|
|
||||||
"--unused", action="store_true", help="Only list unused definitions"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--ignore", action="append", metavar="REGEXP", help="Ignore a pattern"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pattern", action="append", metavar="REGEXP",
|
|
||||||
help="Search for a pattern"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"directories", nargs='+', metavar="DIR",
|
|
||||||
help="Directories to search for definitions"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--referrers", default=0, type=int,
|
|
||||||
help="Include referrers up to the given depth"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--referred", default=0, type=int,
|
|
||||||
help="Include referred down to the given depth"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--format", default="yaml",
|
|
||||||
help="Output format, one of 'yaml' or 'dot'"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
definitions = {}
|
|
||||||
for directory in args.directories:
|
|
||||||
for root, dirs, files in os.walk(directory):
|
|
||||||
for filename in files:
|
|
||||||
if filename.endswith(".py"):
|
|
||||||
filepath = os.path.join(root, filename)
|
|
||||||
definitions[filepath] = definitions_in_file(filepath)
|
|
||||||
|
|
||||||
names = {}
|
|
||||||
for filepath, defs in definitions.items():
|
|
||||||
defined_names(filepath + ":", defs, names)
|
|
||||||
|
|
||||||
for filepath, defs in definitions.items():
|
|
||||||
used_names(filepath + ":", None, defs, names)
|
|
||||||
|
|
||||||
patterns = [re.compile(pattern) for pattern in args.pattern or ()]
|
|
||||||
ignore = [re.compile(pattern) for pattern in args.ignore or ()]
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
for name, definition in names.items():
|
|
||||||
if patterns and not any(pattern.match(name) for pattern in patterns):
|
|
||||||
continue
|
|
||||||
if ignore and any(pattern.match(name) for pattern in ignore):
|
|
||||||
continue
|
|
||||||
if args.unused and definition.get('used'):
|
|
||||||
continue
|
|
||||||
result[name] = definition
|
|
||||||
|
|
||||||
referrer_depth = args.referrers
|
|
||||||
referrers = set()
|
|
||||||
while referrer_depth:
|
|
||||||
referrer_depth -= 1
|
|
||||||
for entry in result.values():
|
|
||||||
for used_by in entry.get("used", ()):
|
|
||||||
referrers.add(used_by)
|
|
||||||
for name, definition in names.items():
|
|
||||||
if not name in referrers:
|
|
||||||
continue
|
|
||||||
if ignore and any(pattern.match(name) for pattern in ignore):
|
|
||||||
continue
|
|
||||||
result[name] = definition
|
|
||||||
|
|
||||||
referred_depth = args.referred
|
|
||||||
referred = set()
|
|
||||||
while referred_depth:
|
|
||||||
referred_depth -= 1
|
|
||||||
for entry in result.values():
|
|
||||||
for uses in entry.get("uses", ()):
|
|
||||||
referred.add(uses)
|
|
||||||
for name, definition in names.items():
|
|
||||||
if not name in referred:
|
|
||||||
continue
|
|
||||||
if ignore and any(pattern.match(name) for pattern in ignore):
|
|
||||||
continue
|
|
||||||
result[name] = definition
|
|
||||||
|
|
||||||
if args.format == 'yaml':
|
|
||||||
yaml.dump(result, sys.stdout, default_flow_style=False)
|
|
||||||
elif args.format == 'dot':
|
|
||||||
print "digraph {"
|
|
||||||
for name, entry in result.items():
|
|
||||||
print name
|
|
||||||
for used_by in entry.get("used", ()):
|
|
||||||
if used_by in result:
|
|
||||||
print used_by, "->", name
|
|
||||||
print "}"
|
|
||||||
else:
|
|
||||||
raise ValueError("Unknown format %r" % (args.format))
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env python2
|
|
||||||
|
|
||||||
import pymacaroons
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
sys.stderr.write("usage: %s macaroon [key]\n" % (sys.argv[0],))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
macaroon_string = sys.argv[1]
|
|
||||||
key = sys.argv[2] if len(sys.argv) > 2 else None
|
|
||||||
|
|
||||||
macaroon = pymacaroons.Macaroon.deserialize(macaroon_string)
|
|
||||||
print macaroon.inspect()
|
|
||||||
|
|
||||||
print ""
|
|
||||||
|
|
||||||
verifier = pymacaroons.Verifier()
|
|
||||||
verifier.satisfy_general(lambda c: True)
|
|
||||||
try:
|
|
||||||
verifier.verify(macaroon, key)
|
|
||||||
print "Signature is correct"
|
|
||||||
except Exception as e:
|
|
||||||
print e.message
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
|
|
||||||
import ast
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
PATTERNS_V1 = []
|
|
||||||
PATTERNS_V2 = []
|
|
||||||
|
|
||||||
RESULT = {
|
|
||||||
"v1": PATTERNS_V1,
|
|
||||||
"v2": PATTERNS_V2,
|
|
||||||
}
|
|
||||||
|
|
||||||
class CallVisitor(ast.NodeVisitor):
|
|
||||||
def visit_Call(self, node):
|
|
||||||
if isinstance(node.func, ast.Name):
|
|
||||||
name = node.func.id
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if name == "client_path_patterns":
|
|
||||||
PATTERNS_V1.append(node.args[0].s)
|
|
||||||
elif name == "client_v2_patterns":
|
|
||||||
PATTERNS_V2.append(node.args[0].s)
|
|
||||||
|
|
||||||
|
|
||||||
def find_patterns_in_code(input_code):
|
|
||||||
input_ast = ast.parse(input_code)
|
|
||||||
visitor = CallVisitor()
|
|
||||||
visitor.visit(input_ast)
|
|
||||||
|
|
||||||
|
|
||||||
def find_patterns_in_file(filepath):
|
|
||||||
with open(filepath) as f:
|
|
||||||
find_patterns_in_code(f.read())
|
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Find url patterns.')
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"directories", nargs='+', metavar="DIR",
|
|
||||||
help="Directories to search for definitions"
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
for directory in args.directories:
|
|
||||||
for root, dirs, files in os.walk(directory):
|
|
||||||
for filename in files:
|
|
||||||
if filename.endswith(".py"):
|
|
||||||
filepath = os.path.join(root, filename)
|
|
||||||
find_patterns_in_file(filepath)
|
|
||||||
|
|
||||||
PATTERNS_V1.sort()
|
|
||||||
PATTERNS_V2.sort()
|
|
||||||
|
|
||||||
yaml.dump(RESULT, sys.stdout, default_flow_style=False)
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
## CAUTION:
|
|
||||||
## This script will remove (hopefully) all trace of the given room ID from
|
|
||||||
## your homeserver.db
|
|
||||||
|
|
||||||
## Do not run it lightly.
|
|
||||||
|
|
||||||
ROOMID="$1"
|
|
||||||
|
|
||||||
sqlite3 homeserver.db <<EOF
|
|
||||||
DELETE FROM event_forward_extremities WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_backward_extremities WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_edges WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_depth WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM state_forward_extremities WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM events WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_json WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM state_events WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM current_state_events WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_memberships WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM feedback WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM topics WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_names WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM rooms WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_hosts WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_aliases WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM state_groups WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM state_groups_state WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM receipts_graph WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM receipts_linearized WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_search_content WHERE c1room_id = '$ROOMID';
|
|
||||||
DELETE FROM guest_access WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM history_visibility WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_tags WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_tags_revisions WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM room_account_data WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_push_actions WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM local_invites WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM pusher_throttle WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_reports WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM public_room_list_stream WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM stream_ordering_to_exterm WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM event_auth WHERE room_id = '$ROOMID';
|
|
||||||
DELETE FROM appservice_room_list WHERE room_id = '$ROOMID';
|
|
||||||
VACUUM;
|
|
||||||
EOF
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import requests
|
|
||||||
import collections
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
|
|
||||||
Entry = collections.namedtuple("Entry", "name position rows")
|
|
||||||
|
|
||||||
ROW_TYPES = {}
|
|
||||||
|
|
||||||
|
|
||||||
def row_type_for_columns(name, column_names):
|
|
||||||
column_names = tuple(column_names)
|
|
||||||
row_type = ROW_TYPES.get((name, column_names))
|
|
||||||
if row_type is None:
|
|
||||||
row_type = collections.namedtuple(name, column_names)
|
|
||||||
ROW_TYPES[(name, column_names)] = row_type
|
|
||||||
return row_type
|
|
||||||
|
|
||||||
|
|
||||||
def parse_response(content):
|
|
||||||
streams = json.loads(content)
|
|
||||||
result = {}
|
|
||||||
for name, value in streams.items():
|
|
||||||
row_type = row_type_for_columns(name, value["field_names"])
|
|
||||||
position = value["position"]
|
|
||||||
rows = [row_type(*row) for row in value["rows"]]
|
|
||||||
result[name] = Entry(name, position, rows)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def replicate(server, streams):
|
|
||||||
return parse_response(requests.get(
|
|
||||||
server + "/_synapse/replication",
|
|
||||||
verify=False,
|
|
||||||
params=streams
|
|
||||||
).content)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
server = sys.argv[1]
|
|
||||||
|
|
||||||
streams = None
|
|
||||||
while not streams:
|
|
||||||
try:
|
|
||||||
streams = {
|
|
||||||
row.name: row.position
|
|
||||||
for row in replicate(server, {"streams":"-1"})["streams"].rows
|
|
||||||
}
|
|
||||||
except requests.exceptions.ConnectionError as e:
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
results = replicate(server, streams)
|
|
||||||
except:
|
|
||||||
sys.stdout.write("connection_lost("+ repr(streams) + ")\n")
|
|
||||||
break
|
|
||||||
for update in results.values():
|
|
||||||
for row in update.rows:
|
|
||||||
sys.stdout.write(repr(row) + "\n")
|
|
||||||
streams[update.name] = update.position
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
main()
|
|
||||||
@@ -56,9 +56,10 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
js = json.load(args.json)
|
js = json.load(args.json)
|
||||||
|
|
||||||
|
|
||||||
auth = Auth(Mock())
|
auth = Auth(Mock())
|
||||||
check_auth(
|
check_auth(
|
||||||
auth,
|
auth,
|
||||||
[FrozenEvent(d) for d in js["auth_chain"]],
|
[FrozenEvent(d) for d in js["auth_chain"]],
|
||||||
[FrozenEvent(d) for d in js.get("pdus", [])],
|
[FrozenEvent(d) for d in js["pdus"]],
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from synapse.crypto.event_signing import *
|
from synapse.crypto.event_signing import *
|
||||||
from unpaddedbase64 import encode_base64
|
from syutil.base64util import encode_base64
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
|
||||||
from signedjson.sign import verify_signed_json
|
from syutil.crypto.jsonsign import verify_signed_json
|
||||||
from signedjson.key import decode_verify_key_bytes, write_signing_keys
|
from syutil.crypto.signing_key import (
|
||||||
from unpaddedbase64 import decode_base64
|
decode_verify_key_bytes, write_signing_keys
|
||||||
|
)
|
||||||
|
from syutil.base64util import decode_base64
|
||||||
|
|
||||||
import urllib2
|
import urllib2
|
||||||
import json
|
import json
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/perl -pi
|
#!/usr/bin/perl -pi
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2015 OpenMarket 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,7 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
$copyright = <<EOT;
|
$copyright = <<EOT;
|
||||||
/* Copyright 2016 OpenMarket Ltd
|
/* Copyright 2015 OpenMarket 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.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/perl -pi
|
#!/usr/bin/perl -pi
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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,7 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
$copyright = <<EOT;
|
$copyright = <<EOT;
|
||||||
# Copyright 2016 OpenMarket Ltd
|
# Copyright 2015 OpenMarket 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.
|
||||||
21
scripts/database-prepare-for-0.0.1.sh
Executable file
21
scripts/database-prepare-for-0.0.1.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This is will prepare a synapse database for running with v0.0.1 of synapse.
|
||||||
|
# It will store all the user information, but will *delete* all messages and
|
||||||
|
# room data.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cp "$1" "$1.bak"
|
||||||
|
|
||||||
|
DUMP=$(sqlite3 "$1" << 'EOF'
|
||||||
|
.dump users
|
||||||
|
.dump access_tokens
|
||||||
|
.dump presence
|
||||||
|
.dump profiles
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
rm "$1"
|
||||||
|
|
||||||
|
sqlite3 "$1" <<< "$DUMP"
|
||||||
21
scripts/database-prepare-for-0.5.0.sh
Executable file
21
scripts/database-prepare-for-0.5.0.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This is will prepare a synapse database for running with v0.5.0 of synapse.
|
||||||
|
# It will store all the user information, but will *delete* all messages and
|
||||||
|
# room data.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cp "$1" "$1.bak"
|
||||||
|
|
||||||
|
DUMP=$(sqlite3 "$1" << 'EOF'
|
||||||
|
.dump users
|
||||||
|
.dump access_tokens
|
||||||
|
.dump presence
|
||||||
|
.dump profiles
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
rm "$1"
|
||||||
|
|
||||||
|
sqlite3 "$1" <<< "$DUMP"
|
||||||
128
scripts-dev/federation_client.py → scripts/federation_client.py
Executable file → Normal file
128
scripts-dev/federation_client.py → scripts/federation_client.py
Executable file → Normal file
@@ -1,30 +1,10 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
# Copyright 2017 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 __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import nacl.signing
|
import nacl.signing
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
import requests
|
import requests
|
||||||
import sys
|
import sys
|
||||||
import srvlookup
|
import srvlookup
|
||||||
import yaml
|
|
||||||
|
|
||||||
def encode_base64(input_bytes):
|
def encode_base64(input_bytes):
|
||||||
"""Encode bytes as a base64 string without any padding."""
|
"""Encode bytes as a base64 string without any padding."""
|
||||||
@@ -123,124 +103,44 @@ def lookup(destination, path):
|
|||||||
except:
|
except:
|
||||||
return "https://%s:%d%s" % (destination, 8448, path)
|
return "https://%s:%d%s" % (destination, 8448, path)
|
||||||
|
|
||||||
|
def get_json(origin_name, origin_key, destination, path):
|
||||||
def request_json(method, origin_name, origin_key, destination, path, content):
|
request_json = {
|
||||||
if method is None:
|
"method": "GET",
|
||||||
if content is None:
|
|
||||||
method = "GET"
|
|
||||||
else:
|
|
||||||
method = "POST"
|
|
||||||
|
|
||||||
json_to_sign = {
|
|
||||||
"method": method,
|
|
||||||
"uri": path,
|
"uri": path,
|
||||||
"origin": origin_name,
|
"origin": origin_name,
|
||||||
"destination": destination,
|
"destination": destination,
|
||||||
}
|
}
|
||||||
|
|
||||||
if content is not None:
|
signed_json = sign_json(request_json, origin_key, origin_name)
|
||||||
json_to_sign["content"] = json.loads(content)
|
|
||||||
|
|
||||||
signed_json = sign_json(json_to_sign, origin_key, origin_name)
|
|
||||||
|
|
||||||
authorization_headers = []
|
authorization_headers = []
|
||||||
|
|
||||||
for key, sig in signed_json["signatures"][origin_name].items():
|
for key, sig in signed_json["signatures"][origin_name].items():
|
||||||
header = "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
|
authorization_headers.append(bytes(
|
||||||
|
"X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
|
||||||
origin_name, key, sig,
|
origin_name, key, sig,
|
||||||
)
|
)
|
||||||
authorization_headers.append(bytes(header))
|
))
|
||||||
print ("Authorization: %s" % header, file=sys.stderr)
|
|
||||||
|
|
||||||
dest = lookup(destination, path)
|
result = requests.get(
|
||||||
print ("Requesting %s" % dest, file=sys.stderr)
|
lookup(destination, path),
|
||||||
|
|
||||||
result = requests.request(
|
|
||||||
method=method,
|
|
||||||
url=dest,
|
|
||||||
headers={"Authorization": authorization_headers[0]},
|
headers={"Authorization": authorization_headers[0]},
|
||||||
verify=False,
|
verify=False,
|
||||||
data=content,
|
|
||||||
)
|
)
|
||||||
sys.stderr.write("Status Code: %d\n" % (result.status_code,))
|
|
||||||
return result.json()
|
return result.json()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
origin_name, keyfile, destination, path = sys.argv[1:]
|
||||||
description=
|
|
||||||
"Signs and sends a federation request to a matrix homeserver",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
with open(keyfile) as f:
|
||||||
"-N", "--server-name",
|
|
||||||
help="Name to give as the local homeserver. If unspecified, will be "
|
|
||||||
"read from the config file.",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"-k", "--signing-key-path",
|
|
||||||
help="Path to the file containing the private ed25519 key to sign the "
|
|
||||||
"request with.",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"-c", "--config",
|
|
||||||
default="homeserver.yaml",
|
|
||||||
help="Path to server config file. Ignored if --server-name and "
|
|
||||||
"--signing-key-path are both given.",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"-d", "--destination",
|
|
||||||
default="matrix.org",
|
|
||||||
help="name of the remote homeserver. We will do SRV lookups and "
|
|
||||||
"connect appropriately.",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"-X", "--method",
|
|
||||||
help="HTTP method to use for the request. Defaults to GET if --data is"
|
|
||||||
"unspecified, POST if it is."
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--body",
|
|
||||||
help="Data to send as the body of the HTTP request"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"path",
|
|
||||||
help="request path. We will add '/_matrix/federation/v1/' to this."
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.server_name or not args.signing_key_path:
|
|
||||||
read_args_from_config(args)
|
|
||||||
|
|
||||||
with open(args.signing_key_path) as f:
|
|
||||||
key = read_signing_keys(f)[0]
|
key = read_signing_keys(f)[0]
|
||||||
|
|
||||||
result = request_json(
|
result = get_json(
|
||||||
args.method,
|
origin_name, key, destination, "/_matrix/federation/v1/" + path
|
||||||
args.server_name, key, args.destination,
|
|
||||||
"/_matrix/federation/v1/" + args.path,
|
|
||||||
content=args.body,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
json.dump(result, sys.stdout)
|
json.dump(result, sys.stdout)
|
||||||
print ("")
|
|
||||||
|
|
||||||
|
|
||||||
def read_args_from_config(args):
|
|
||||||
with open(args.config, 'r') as fh:
|
|
||||||
config = yaml.safe_load(fh)
|
|
||||||
if not args.server_name:
|
|
||||||
args.server_name = config['server_name']
|
|
||||||
if not args.signing_key_path:
|
|
||||||
args.signing_key_path = config['signing_key_path']
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -6,8 +6,8 @@ from synapse.crypto.event_signing import (
|
|||||||
add_event_pdu_content_hash, compute_pdu_event_reference_hash
|
add_event_pdu_content_hash, compute_pdu_event_reference_hash
|
||||||
)
|
)
|
||||||
from synapse.api.events.utils import prune_pdu
|
from synapse.api.events.utils import prune_pdu
|
||||||
from unpaddedbase64 import encode_base64, decode_base64
|
from syutil.base64util import encode_base64, decode_base64
|
||||||
from canonicaljson import encode_canonical_json
|
from syutil.jsonutil import encode_canonical_json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import bcrypt
|
|
||||||
import getpass
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
bcrypt_rounds=12
|
|
||||||
password_pepper = ""
|
|
||||||
|
|
||||||
def prompt_for_pass():
|
|
||||||
password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
if not password:
|
|
||||||
raise Exception("Password cannot be blank.")
|
|
||||||
|
|
||||||
confirm_password = getpass.getpass("Confirm password: ")
|
|
||||||
|
|
||||||
if password != confirm_password:
|
|
||||||
raise Exception("Passwords do not match.")
|
|
||||||
|
|
||||||
return password
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Calculate the hash of a new password, so that passwords"
|
|
||||||
" can be reset")
|
|
||||||
parser.add_argument(
|
|
||||||
"-p", "--password",
|
|
||||||
default=None,
|
|
||||||
help="New password for user. Will prompt if omitted.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-c", "--config",
|
|
||||||
type=argparse.FileType('r'),
|
|
||||||
help="Path to server config file. Used to read in bcrypt_rounds and password_pepper.",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
if "config" in args and args.config:
|
|
||||||
config = yaml.safe_load(args.config)
|
|
||||||
bcrypt_rounds = config.get("bcrypt_rounds", bcrypt_rounds)
|
|
||||||
password_config = config.get("password_config", {})
|
|
||||||
password_pepper = password_config.get("pepper", password_pepper)
|
|
||||||
password = args.password
|
|
||||||
|
|
||||||
if not password:
|
|
||||||
password = prompt_for_pass()
|
|
||||||
|
|
||||||
print bcrypt.hashpw(password + password_pepper, bcrypt.gensalt(bcrypt_rounds))
|
|
||||||
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2017 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Moves a list of remote media from one media store to another.
|
|
||||||
|
|
||||||
The input should be a list of media files to be moved, one per line. Each line
|
|
||||||
should be formatted::
|
|
||||||
|
|
||||||
<origin server>|<file id>
|
|
||||||
|
|
||||||
This can be extracted from postgres with::
|
|
||||||
|
|
||||||
psql --tuples-only -A -c "select media_origin, filesystem_id from
|
|
||||||
matrix.remote_media_cache where ..."
|
|
||||||
|
|
||||||
To use, pipe the above into::
|
|
||||||
|
|
||||||
PYTHON_PATH=. ./scripts/move_remote_media_to_new_store.py <source repo> <dest repo>
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from synapse.rest.media.v1.filepath import MediaFilePaths
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
def main(src_repo, dest_repo):
|
|
||||||
src_paths = MediaFilePaths(src_repo)
|
|
||||||
dest_paths = MediaFilePaths(dest_repo)
|
|
||||||
for line in sys.stdin:
|
|
||||||
line = line.strip()
|
|
||||||
parts = line.split('|')
|
|
||||||
if len(parts) != 2:
|
|
||||||
print("Unable to parse input line %s" % line, file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
move_media(parts[0], parts[1], src_paths, dest_paths)
|
|
||||||
|
|
||||||
|
|
||||||
def move_media(origin_server, file_id, src_paths, dest_paths):
|
|
||||||
"""Move the given file, and any thumbnails, to the dest repo
|
|
||||||
|
|
||||||
Args:
|
|
||||||
origin_server (str):
|
|
||||||
file_id (str):
|
|
||||||
src_paths (MediaFilePaths):
|
|
||||||
dest_paths (MediaFilePaths):
|
|
||||||
"""
|
|
||||||
logger.info("%s/%s", origin_server, file_id)
|
|
||||||
|
|
||||||
# check that the original exists
|
|
||||||
original_file = src_paths.remote_media_filepath(origin_server, file_id)
|
|
||||||
if not os.path.exists(original_file):
|
|
||||||
logger.warn(
|
|
||||||
"Original for %s/%s (%s) does not exist",
|
|
||||||
origin_server, file_id, original_file,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
mkdir_and_move(
|
|
||||||
original_file,
|
|
||||||
dest_paths.remote_media_filepath(origin_server, file_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
# now look for thumbnails
|
|
||||||
original_thumb_dir = src_paths.remote_media_thumbnail_dir(
|
|
||||||
origin_server, file_id,
|
|
||||||
)
|
|
||||||
if not os.path.exists(original_thumb_dir):
|
|
||||||
return
|
|
||||||
|
|
||||||
mkdir_and_move(
|
|
||||||
original_thumb_dir,
|
|
||||||
dest_paths.remote_media_thumbnail_dir(origin_server, file_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def mkdir_and_move(original_file, dest_file):
|
|
||||||
dirname = os.path.dirname(dest_file)
|
|
||||||
if not os.path.exists(dirname):
|
|
||||||
logger.debug("mkdir %s", dirname)
|
|
||||||
os.makedirs(dirname)
|
|
||||||
logger.debug("mv %s %s", original_file, dest_file)
|
|
||||||
shutil.move(original_file, dest_file)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=__doc__,
|
|
||||||
formatter_class = argparse.RawDescriptionHelpFormatter,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-v", action='store_true', help='enable debug logging')
|
|
||||||
parser.add_argument(
|
|
||||||
"src_repo",
|
|
||||||
help="Path to source content repo",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"dest_repo",
|
|
||||||
help="Path to source content repo",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging_config = {
|
|
||||||
"level": logging.DEBUG if args.v else logging.INFO,
|
|
||||||
"format": "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s"
|
|
||||||
}
|
|
||||||
logging.basicConfig(**logging_config)
|
|
||||||
|
|
||||||
main(args.src_repo, args.dest_repo)
|
|
||||||
24
scripts/nuke-room-from-db.sh
Executable file
24
scripts/nuke-room-from-db.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
## CAUTION:
|
||||||
|
## This script will remove (hopefully) all trace of the given room ID from
|
||||||
|
## your homeserver.db
|
||||||
|
|
||||||
|
## Do not run it lightly.
|
||||||
|
|
||||||
|
ROOMID="$1"
|
||||||
|
|
||||||
|
sqlite3 homeserver.db <<EOF
|
||||||
|
DELETE FROM context_depth WHERE context = '$ROOMID';
|
||||||
|
DELETE FROM current_state WHERE context = '$ROOMID';
|
||||||
|
DELETE FROM feedback WHERE room_id = '$ROOMID';
|
||||||
|
DELETE FROM messages WHERE room_id = '$ROOMID';
|
||||||
|
DELETE FROM pdu_backward_extremities WHERE context = '$ROOMID';
|
||||||
|
DELETE FROM pdu_edges WHERE context = '$ROOMID';
|
||||||
|
DELETE FROM pdu_forward_extremities WHERE context = '$ROOMID';
|
||||||
|
DELETE FROM pdus WHERE context = '$ROOMID';
|
||||||
|
DELETE FROM room_data WHERE room_id = '$ROOMID';
|
||||||
|
DELETE FROM room_memberships WHERE room_id = '$ROOMID';
|
||||||
|
DELETE FROM rooms WHERE room_id = '$ROOMID';
|
||||||
|
DELETE FROM state_pdus WHERE context = '$ROOMID';
|
||||||
|
EOF
|
||||||
312
scripts/synapse_port_db → scripts/port_from_sqlite_to_postgres.py
Executable file → Normal file
312
scripts/synapse_port_db → scripts/port_from_sqlite_to_postgres.py
Executable file → Normal file
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015 OpenMarket 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.
|
||||||
@@ -19,7 +18,6 @@ from twisted.enterprise import adbapi
|
|||||||
|
|
||||||
from synapse.storage._base import LoggingTransaction, SQLBaseStore
|
from synapse.storage._base import LoggingTransaction, SQLBaseStore
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.storage.prepare_database import prepare_database
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import curses
|
import curses
|
||||||
@@ -30,26 +28,14 @@ import traceback
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("synapse_port_db")
|
logger = logging.getLogger("port_from_sqlite_to_postgres")
|
||||||
|
|
||||||
|
|
||||||
BOOLEAN_COLUMNS = {
|
BOOLEAN_COLUMNS = {
|
||||||
"events": ["processed", "outlier", "contains_url"],
|
"events": ["processed", "outlier"],
|
||||||
"rooms": ["is_public"],
|
"rooms": ["is_public"],
|
||||||
"event_edges": ["is_state"],
|
"event_edges": ["is_state"],
|
||||||
"presence_list": ["accepted"],
|
"presence_list": ["accepted"],
|
||||||
"presence_stream": ["currently_active"],
|
|
||||||
"public_room_list_stream": ["visibility"],
|
|
||||||
"device_lists_outbound_pokes": ["sent"],
|
|
||||||
"users_who_share_rooms": ["share_private"],
|
|
||||||
"groups": ["is_public"],
|
|
||||||
"group_rooms": ["is_public"],
|
|
||||||
"group_users": ["is_public", "is_admin"],
|
|
||||||
"group_summary_rooms": ["is_public"],
|
|
||||||
"group_room_categories": ["is_public"],
|
|
||||||
"group_summary_users": ["is_public"],
|
|
||||||
"group_roles": ["is_public"],
|
|
||||||
"local_group_membership": ["is_publicised", "is_admin"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -81,15 +67,6 @@ APPEND_ONLY_TABLES = [
|
|||||||
"state_groups_state",
|
"state_groups_state",
|
||||||
"event_to_state_groups",
|
"event_to_state_groups",
|
||||||
"rejections",
|
"rejections",
|
||||||
"event_search",
|
|
||||||
"presence_stream",
|
|
||||||
"push_rules_stream",
|
|
||||||
"current_state_resets",
|
|
||||||
"ex_outlier_stream",
|
|
||||||
"cache_invalidation_stream",
|
|
||||||
"public_room_list_stream",
|
|
||||||
"state_group_edges",
|
|
||||||
"stream_ordering_to_exterm",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -111,16 +88,13 @@ class Store(object):
|
|||||||
|
|
||||||
_simple_select_onecol_txn = SQLBaseStore.__dict__["_simple_select_onecol_txn"]
|
_simple_select_onecol_txn = SQLBaseStore.__dict__["_simple_select_onecol_txn"]
|
||||||
_simple_select_onecol = SQLBaseStore.__dict__["_simple_select_onecol"]
|
_simple_select_onecol = SQLBaseStore.__dict__["_simple_select_onecol"]
|
||||||
_simple_select_one = SQLBaseStore.__dict__["_simple_select_one"]
|
|
||||||
_simple_select_one_txn = SQLBaseStore.__dict__["_simple_select_one_txn"]
|
|
||||||
_simple_select_one_onecol = SQLBaseStore.__dict__["_simple_select_one_onecol"]
|
_simple_select_one_onecol = SQLBaseStore.__dict__["_simple_select_one_onecol"]
|
||||||
_simple_select_one_onecol_txn = SQLBaseStore.__dict__[
|
_simple_select_one_onecol_txn = SQLBaseStore.__dict__["_simple_select_one_onecol_txn"]
|
||||||
"_simple_select_one_onecol_txn"
|
|
||||||
]
|
|
||||||
|
|
||||||
_simple_update_one = SQLBaseStore.__dict__["_simple_update_one"]
|
_simple_update_one = SQLBaseStore.__dict__["_simple_update_one"]
|
||||||
_simple_update_one_txn = SQLBaseStore.__dict__["_simple_update_one_txn"]
|
_simple_update_one_txn = SQLBaseStore.__dict__["_simple_update_one_txn"]
|
||||||
_simple_update_txn = SQLBaseStore.__dict__["_simple_update_txn"]
|
|
||||||
|
_execute_and_decode = SQLBaseStore.__dict__["_execute_and_decode"]
|
||||||
|
|
||||||
def runInteraction(self, desc, func, *args, **kwargs):
|
def runInteraction(self, desc, func, *args, **kwargs):
|
||||||
def r(conn):
|
def r(conn):
|
||||||
@@ -131,7 +105,7 @@ class Store(object):
|
|||||||
try:
|
try:
|
||||||
txn = conn.cursor()
|
txn = conn.cursor()
|
||||||
return func(
|
return func(
|
||||||
LoggingTransaction(txn, desc, self.database_engine, [], []),
|
LoggingTransaction(txn, desc, self.database_engine),
|
||||||
*args, **kwargs
|
*args, **kwargs
|
||||||
)
|
)
|
||||||
except self.database_engine.module.DatabaseError as e:
|
except self.database_engine.module.DatabaseError as e:
|
||||||
@@ -182,40 +156,31 @@ class Porter(object):
|
|||||||
def setup_table(self, table):
|
def setup_table(self, table):
|
||||||
if table in APPEND_ONLY_TABLES:
|
if table in APPEND_ONLY_TABLES:
|
||||||
# It's safe to just carry on inserting.
|
# It's safe to just carry on inserting.
|
||||||
row = yield self.postgres_store._simple_select_one(
|
next_chunk = yield self.postgres_store._simple_select_one_onecol(
|
||||||
table="port_from_sqlite3",
|
table="port_from_sqlite3",
|
||||||
keyvalues={"table_name": table},
|
keyvalues={"table_name": table},
|
||||||
retcols=("forward_rowid", "backward_rowid"),
|
retcol="rowid",
|
||||||
allow_none=True,
|
allow_none=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
total_to_port = None
|
total_to_port = None
|
||||||
if row is None:
|
if next_chunk is None:
|
||||||
if table == "sent_transactions":
|
if table == "sent_transactions":
|
||||||
forward_chunk, already_ported, total_to_port = (
|
next_chunk, already_ported, total_to_port = (
|
||||||
yield self._setup_sent_transactions()
|
yield self._setup_sent_transactions()
|
||||||
)
|
)
|
||||||
backward_chunk = 0
|
|
||||||
else:
|
else:
|
||||||
yield self.postgres_store._simple_insert(
|
yield self.postgres_store._simple_insert(
|
||||||
table="port_from_sqlite3",
|
table="port_from_sqlite3",
|
||||||
values={
|
values={"table_name": table, "rowid": 1}
|
||||||
"table_name": table,
|
|
||||||
"forward_rowid": 1,
|
|
||||||
"backward_rowid": 0,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
forward_chunk = 1
|
next_chunk = 1
|
||||||
backward_chunk = 0
|
|
||||||
already_ported = 0
|
already_ported = 0
|
||||||
else:
|
|
||||||
forward_chunk = row["forward_rowid"]
|
|
||||||
backward_chunk = row["backward_rowid"]
|
|
||||||
|
|
||||||
if total_to_port is None:
|
if total_to_port is None:
|
||||||
already_ported, total_to_port = yield self._get_total_count_to_port(
|
already_ported, total_to_port = yield self._get_total_count_to_port(
|
||||||
table, forward_chunk, backward_chunk
|
table, next_chunk
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
def delete_all(txn):
|
def delete_all(txn):
|
||||||
@@ -229,105 +194,43 @@ class Porter(object):
|
|||||||
|
|
||||||
yield self.postgres_store._simple_insert(
|
yield self.postgres_store._simple_insert(
|
||||||
table="port_from_sqlite3",
|
table="port_from_sqlite3",
|
||||||
values={
|
values={"table_name": table, "rowid": 0}
|
||||||
"table_name": table,
|
|
||||||
"forward_rowid": 1,
|
|
||||||
"backward_rowid": 0,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
forward_chunk = 1
|
next_chunk = 1
|
||||||
backward_chunk = 0
|
|
||||||
|
|
||||||
already_ported, total_to_port = yield self._get_total_count_to_port(
|
already_ported, total_to_port = yield self._get_total_count_to_port(
|
||||||
table, forward_chunk, backward_chunk
|
table, next_chunk
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue(
|
defer.returnValue((table, already_ported, total_to_port, next_chunk))
|
||||||
(table, already_ported, total_to_port, forward_chunk, backward_chunk)
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def handle_table(self, table, postgres_size, table_size, forward_chunk,
|
def handle_table(self, table, postgres_size, table_size, next_chunk):
|
||||||
backward_chunk):
|
|
||||||
if not table_size:
|
if not table_size:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.progress.add_table(table, postgres_size, table_size)
|
self.progress.add_table(table, postgres_size, table_size)
|
||||||
|
|
||||||
if table == "event_search":
|
select = (
|
||||||
yield self.handle_search_table(
|
|
||||||
postgres_size, table_size, forward_chunk, backward_chunk
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if table in (
|
|
||||||
"user_directory", "user_directory_search", "users_who_share_rooms",
|
|
||||||
"users_in_pubic_room",
|
|
||||||
):
|
|
||||||
# We don't port these tables, as they're a faff and we can regenreate
|
|
||||||
# them anyway.
|
|
||||||
self.progress.update(table, table_size) # Mark table as done
|
|
||||||
return
|
|
||||||
|
|
||||||
if table == "user_directory_stream_pos":
|
|
||||||
# We need to make sure there is a single row, `(X, null), as that is
|
|
||||||
# what synapse expects to be there.
|
|
||||||
yield self.postgres_store._simple_insert(
|
|
||||||
table=table,
|
|
||||||
values={"stream_id": None},
|
|
||||||
)
|
|
||||||
self.progress.update(table, table_size) # Mark table as done
|
|
||||||
return
|
|
||||||
|
|
||||||
forward_select = (
|
|
||||||
"SELECT rowid, * FROM %s WHERE rowid >= ? ORDER BY rowid LIMIT ?"
|
"SELECT rowid, * FROM %s WHERE rowid >= ? ORDER BY rowid LIMIT ?"
|
||||||
% (table,)
|
% (table,)
|
||||||
)
|
)
|
||||||
|
|
||||||
backward_select = (
|
|
||||||
"SELECT rowid, * FROM %s WHERE rowid <= ? ORDER BY rowid LIMIT ?"
|
|
||||||
% (table,)
|
|
||||||
)
|
|
||||||
|
|
||||||
do_forward = [True]
|
|
||||||
do_backward = [True]
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
def r(txn):
|
def r(txn):
|
||||||
forward_rows = []
|
txn.execute(select, (next_chunk, self.batch_size,))
|
||||||
backward_rows = []
|
rows = txn.fetchall()
|
||||||
if do_forward[0]:
|
|
||||||
txn.execute(forward_select, (forward_chunk, self.batch_size,))
|
|
||||||
forward_rows = txn.fetchall()
|
|
||||||
if not forward_rows:
|
|
||||||
do_forward[0] = False
|
|
||||||
|
|
||||||
if do_backward[0]:
|
|
||||||
txn.execute(backward_select, (backward_chunk, self.batch_size,))
|
|
||||||
backward_rows = txn.fetchall()
|
|
||||||
if not backward_rows:
|
|
||||||
do_backward[0] = False
|
|
||||||
|
|
||||||
if forward_rows or backward_rows:
|
|
||||||
headers = [column[0] for column in txn.description]
|
headers = [column[0] for column in txn.description]
|
||||||
else:
|
|
||||||
headers = None
|
|
||||||
|
|
||||||
return headers, forward_rows, backward_rows
|
return headers, rows
|
||||||
|
|
||||||
headers, frows, brows = yield self.sqlite_store.runInteraction(
|
headers, rows = yield self.sqlite_store.runInteraction("select", r)
|
||||||
"select", r
|
|
||||||
)
|
|
||||||
|
|
||||||
if frows or brows:
|
if rows:
|
||||||
if frows:
|
next_chunk = rows[-1][0] + 1
|
||||||
forward_chunk = max(row[0] for row in frows) + 1
|
|
||||||
if brows:
|
|
||||||
backward_chunk = min(row[0] for row in brows) - 1
|
|
||||||
|
|
||||||
rows = frows + brows
|
self._convert_rows(table, headers, rows)
|
||||||
rows = self._convert_rows(table, headers, rows)
|
|
||||||
|
|
||||||
def insert(txn):
|
def insert(txn):
|
||||||
self.postgres_store.insert_many_txn(
|
self.postgres_store.insert_many_txn(
|
||||||
@@ -338,10 +241,7 @@ class Porter(object):
|
|||||||
txn,
|
txn,
|
||||||
table="port_from_sqlite3",
|
table="port_from_sqlite3",
|
||||||
keyvalues={"table_name": table},
|
keyvalues={"table_name": table},
|
||||||
updatevalues={
|
updatevalues={"rowid": next_chunk},
|
||||||
"forward_rowid": forward_chunk,
|
|
||||||
"backward_rowid": backward_chunk,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
yield self.postgres_store.execute(insert)
|
yield self.postgres_store.execute(insert)
|
||||||
@@ -352,79 +252,6 @@ class Porter(object):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def handle_search_table(self, postgres_size, table_size, forward_chunk,
|
|
||||||
backward_chunk):
|
|
||||||
select = (
|
|
||||||
"SELECT es.rowid, es.*, e.origin_server_ts, e.stream_ordering"
|
|
||||||
" FROM event_search as es"
|
|
||||||
" INNER JOIN events AS e USING (event_id, room_id)"
|
|
||||||
" WHERE es.rowid >= ?"
|
|
||||||
" ORDER BY es.rowid LIMIT ?"
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
def r(txn):
|
|
||||||
txn.execute(select, (forward_chunk, self.batch_size,))
|
|
||||||
rows = txn.fetchall()
|
|
||||||
headers = [column[0] for column in txn.description]
|
|
||||||
|
|
||||||
return headers, rows
|
|
||||||
|
|
||||||
headers, rows = yield self.sqlite_store.runInteraction("select", r)
|
|
||||||
|
|
||||||
if rows:
|
|
||||||
forward_chunk = rows[-1][0] + 1
|
|
||||||
|
|
||||||
# We have to treat event_search differently since it has a
|
|
||||||
# different structure in the two different databases.
|
|
||||||
def insert(txn):
|
|
||||||
sql = (
|
|
||||||
"INSERT INTO event_search (event_id, room_id, key,"
|
|
||||||
" sender, vector, origin_server_ts, stream_ordering)"
|
|
||||||
" VALUES (?,?,?,?,to_tsvector('english', ?),?,?)"
|
|
||||||
)
|
|
||||||
|
|
||||||
rows_dict = []
|
|
||||||
for row in rows:
|
|
||||||
d = dict(zip(headers, row))
|
|
||||||
if "\0" in d['value']:
|
|
||||||
logger.warn('dropping search row %s', d)
|
|
||||||
else:
|
|
||||||
rows_dict.append(d)
|
|
||||||
|
|
||||||
txn.executemany(sql, [
|
|
||||||
(
|
|
||||||
row["event_id"],
|
|
||||||
row["room_id"],
|
|
||||||
row["key"],
|
|
||||||
row["sender"],
|
|
||||||
row["value"],
|
|
||||||
row["origin_server_ts"],
|
|
||||||
row["stream_ordering"],
|
|
||||||
)
|
|
||||||
for row in rows_dict
|
|
||||||
])
|
|
||||||
|
|
||||||
self.postgres_store._simple_update_one_txn(
|
|
||||||
txn,
|
|
||||||
table="port_from_sqlite3",
|
|
||||||
keyvalues={"table_name": "event_search"},
|
|
||||||
updatevalues={
|
|
||||||
"forward_rowid": forward_chunk,
|
|
||||||
"backward_rowid": backward_chunk,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
yield self.postgres_store.execute(insert)
|
|
||||||
|
|
||||||
postgres_size += len(rows)
|
|
||||||
|
|
||||||
self.progress.update("event_search", postgres_size)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def setup_db(self, db_config, database_engine):
|
def setup_db(self, db_config, database_engine):
|
||||||
db_conn = database_engine.module.connect(
|
db_conn = database_engine.module.connect(
|
||||||
**{
|
**{
|
||||||
@@ -433,7 +260,7 @@ class Porter(object):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
prepare_database(db_conn, database_engine, config=None)
|
database_engine.prepare_database(db_conn)
|
||||||
|
|
||||||
db_conn.commit()
|
db_conn.commit()
|
||||||
|
|
||||||
@@ -450,8 +277,8 @@ class Porter(object):
|
|||||||
**self.postgres_config["args"]
|
**self.postgres_config["args"]
|
||||||
)
|
)
|
||||||
|
|
||||||
sqlite_engine = create_engine(sqlite_config)
|
sqlite_engine = create_engine("sqlite3")
|
||||||
postgres_engine = create_engine(postgres_config)
|
postgres_engine = create_engine("psycopg2")
|
||||||
|
|
||||||
self.sqlite_store = Store(sqlite_db_pool, sqlite_engine)
|
self.sqlite_store = Store(sqlite_db_pool, sqlite_engine)
|
||||||
self.postgres_store = Store(postgres_db_pool, postgres_engine)
|
self.postgres_store = Store(postgres_db_pool, postgres_engine)
|
||||||
@@ -479,7 +306,9 @@ class Porter(object):
|
|||||||
|
|
||||||
postgres_tables = yield self.postgres_store._simple_select_onecol(
|
postgres_tables = yield self.postgres_store._simple_select_onecol(
|
||||||
table="information_schema.tables",
|
table="information_schema.tables",
|
||||||
keyvalues={},
|
keyvalues={
|
||||||
|
"table_schema": "public",
|
||||||
|
},
|
||||||
retcol="distinct table_name",
|
retcol="distinct table_name",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -493,32 +322,10 @@ class Porter(object):
|
|||||||
txn.execute(
|
txn.execute(
|
||||||
"CREATE TABLE port_from_sqlite3 ("
|
"CREATE TABLE port_from_sqlite3 ("
|
||||||
" table_name varchar(100) NOT NULL UNIQUE,"
|
" table_name varchar(100) NOT NULL UNIQUE,"
|
||||||
" forward_rowid bigint NOT NULL,"
|
" rowid bigint NOT NULL"
|
||||||
" backward_rowid bigint NOT NULL"
|
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
|
|
||||||
# The old port script created a table with just a "rowid" column.
|
|
||||||
# We want people to be able to rerun this script from an old port
|
|
||||||
# so that they can pick up any missing events that were not
|
|
||||||
# ported across.
|
|
||||||
def alter_table(txn):
|
|
||||||
txn.execute(
|
|
||||||
"ALTER TABLE IF EXISTS port_from_sqlite3"
|
|
||||||
" RENAME rowid TO forward_rowid"
|
|
||||||
)
|
|
||||||
txn.execute(
|
|
||||||
"ALTER TABLE IF EXISTS port_from_sqlite3"
|
|
||||||
" ADD backward_rowid bigint NOT NULL DEFAULT 0"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield self.postgres_store.runInteraction(
|
|
||||||
"alter_table", alter_table
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.info("Failed to create port table: %s", e)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield self.postgres_store.runInteraction(
|
yield self.postgres_store.runInteraction(
|
||||||
"create_port_table", create_port_table
|
"create_port_table", create_port_table
|
||||||
@@ -563,29 +370,19 @@ class Porter(object):
|
|||||||
i for i, h in enumerate(headers) if h in bool_col_names
|
i for i, h in enumerate(headers) if h in bool_col_names
|
||||||
]
|
]
|
||||||
|
|
||||||
class BadValueException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def conv(j, col):
|
def conv(j, col):
|
||||||
if j in bool_cols:
|
if j in bool_cols:
|
||||||
return bool(col)
|
return bool(col)
|
||||||
elif isinstance(col, basestring) and "\0" in col:
|
|
||||||
logger.warn("DROPPING ROW: NUL value in table %s col %s: %r", table, headers[j], col)
|
|
||||||
raise BadValueException();
|
|
||||||
return col
|
return col
|
||||||
|
|
||||||
outrows = []
|
|
||||||
for i, row in enumerate(rows):
|
for i, row in enumerate(rows):
|
||||||
try:
|
rows[i] = tuple(
|
||||||
outrows.append(tuple(
|
self.postgres_store.database_engine.encode_parameter(
|
||||||
conv(j, col)
|
conv(j, col)
|
||||||
|
)
|
||||||
for j, col in enumerate(row)
|
for j, col in enumerate(row)
|
||||||
if j > 0
|
if j > 0
|
||||||
))
|
)
|
||||||
except BadValueException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return outrows
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _setup_sent_transactions(self):
|
def _setup_sent_transactions(self):
|
||||||
@@ -613,10 +410,9 @@ class Porter(object):
|
|||||||
"select", r,
|
"select", r,
|
||||||
)
|
)
|
||||||
|
|
||||||
rows = self._convert_rows("sent_transactions", headers, rows)
|
self._convert_rows("sent_transactions", headers, rows)
|
||||||
|
|
||||||
inserted_rows = len(rows)
|
inserted_rows = len(rows)
|
||||||
if inserted_rows:
|
|
||||||
max_inserted_rowid = max(r[0] for r in rows)
|
max_inserted_rowid = max(r[0] for r in rows)
|
||||||
|
|
||||||
def insert(txn):
|
def insert(txn):
|
||||||
@@ -625,8 +421,6 @@ class Porter(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
yield self.postgres_store.execute(insert)
|
yield self.postgres_store.execute(insert)
|
||||||
else:
|
|
||||||
max_inserted_rowid = 0
|
|
||||||
|
|
||||||
def get_start_id(txn):
|
def get_start_id(txn):
|
||||||
txn.execute(
|
txn.execute(
|
||||||
@@ -646,11 +440,7 @@ class Porter(object):
|
|||||||
|
|
||||||
yield self.postgres_store._simple_insert(
|
yield self.postgres_store._simple_insert(
|
||||||
table="port_from_sqlite3",
|
table="port_from_sqlite3",
|
||||||
values={
|
values={"table_name": "sent_transactions", "rowid": next_chunk}
|
||||||
"table_name": "sent_transactions",
|
|
||||||
"forward_rowid": next_chunk,
|
|
||||||
"backward_rowid": 0,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_sent_table_size(txn):
|
def get_sent_table_size(txn):
|
||||||
@@ -671,18 +461,13 @@ class Porter(object):
|
|||||||
defer.returnValue((next_chunk, inserted_rows, total_count))
|
defer.returnValue((next_chunk, inserted_rows, total_count))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _get_remaining_count_to_port(self, table, forward_chunk, backward_chunk):
|
def _get_remaining_count_to_port(self, table, next_chunk):
|
||||||
frows = yield self.sqlite_store.execute_sql(
|
rows = yield self.sqlite_store.execute_sql(
|
||||||
"SELECT count(*) FROM %s WHERE rowid >= ?" % (table,),
|
"SELECT count(*) FROM %s WHERE rowid >= ?" % (table,),
|
||||||
forward_chunk,
|
next_chunk,
|
||||||
)
|
)
|
||||||
|
|
||||||
brows = yield self.sqlite_store.execute_sql(
|
defer.returnValue(rows[0][0])
|
||||||
"SELECT count(*) FROM %s WHERE rowid <= ?" % (table,),
|
|
||||||
backward_chunk,
|
|
||||||
)
|
|
||||||
|
|
||||||
defer.returnValue(frows[0][0] + brows[0][0])
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _get_already_ported_count(self, table):
|
def _get_already_ported_count(self, table):
|
||||||
@@ -693,10 +478,10 @@ class Porter(object):
|
|||||||
defer.returnValue(rows[0][0])
|
defer.returnValue(rows[0][0])
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _get_total_count_to_port(self, table, forward_chunk, backward_chunk):
|
def _get_total_count_to_port(self, table, next_chunk):
|
||||||
remaining, done = yield defer.gatherResults(
|
remaining, done = yield defer.gatherResults(
|
||||||
[
|
[
|
||||||
self._get_remaining_count_to_port(table, forward_chunk, backward_chunk),
|
self._get_remaining_count_to_port(table, next_chunk),
|
||||||
self._get_already_ported_count(table),
|
self._get_already_ported_count(table),
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
@@ -939,9 +724,6 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
postgres_config = yaml.safe_load(args.postgres_config)
|
postgres_config = yaml.safe_load(args.postgres_config)
|
||||||
|
|
||||||
if "database" in postgres_config:
|
|
||||||
postgres_config = postgres_config["database"]
|
|
||||||
|
|
||||||
if "name" not in postgres_config:
|
if "name" not in postgres_config:
|
||||||
sys.stderr.write("Malformed database config: no 'name'")
|
sys.stderr.write("Malformed database config: no 'name'")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
use JSON::XS;
|
|
||||||
use LWP::UserAgent;
|
|
||||||
use URI::Escape;
|
|
||||||
|
|
||||||
if (@ARGV < 4) {
|
|
||||||
die "usage: $0 <homeserver url> <access_token> <room_id|room_alias> <group_id>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
my ($hs, $access_token, $room_id, $group_id) = @ARGV;
|
|
||||||
my $ua = LWP::UserAgent->new();
|
|
||||||
$ua->timeout(10);
|
|
||||||
|
|
||||||
if ($room_id =~ /^#/) {
|
|
||||||
$room_id = uri_escape($room_id);
|
|
||||||
$room_id = decode_json($ua->get("${hs}/_matrix/client/r0/directory/room/${room_id}?access_token=${access_token}")->decoded_content)->{room_id};
|
|
||||||
}
|
|
||||||
|
|
||||||
my $room_users = [ keys %{decode_json($ua->get("${hs}/_matrix/client/r0/rooms/${room_id}/joined_members?access_token=${access_token}")->decoded_content)->{joined}} ];
|
|
||||||
my $group_users = [
|
|
||||||
(map { $_->{user_id} } @{decode_json($ua->get("${hs}/_matrix/client/unstable/groups/${group_id}/users?access_token=${access_token}" )->decoded_content)->{chunk}}),
|
|
||||||
(map { $_->{user_id} } @{decode_json($ua->get("${hs}/_matrix/client/unstable/groups/${group_id}/invited_users?access_token=${access_token}" )->decoded_content)->{chunk}}),
|
|
||||||
];
|
|
||||||
|
|
||||||
die "refusing to sync from empty room" unless (@$room_users);
|
|
||||||
die "refusing to sync to empty group" unless (@$group_users);
|
|
||||||
|
|
||||||
my $diff = {};
|
|
||||||
foreach my $user (@$room_users) { $diff->{$user}++ }
|
|
||||||
foreach my $user (@$group_users) { $diff->{$user}-- }
|
|
||||||
|
|
||||||
foreach my $user (keys %$diff) {
|
|
||||||
if ($diff->{$user} == 1) {
|
|
||||||
warn "inviting $user";
|
|
||||||
print STDERR $ua->put("${hs}/_matrix/client/unstable/groups/${group_id}/admin/users/invite/${user}?access_token=${access_token}", Content=>'{}')->status_line."\n";
|
|
||||||
}
|
|
||||||
elsif ($diff->{$user} == -1) {
|
|
||||||
warn "removing $user";
|
|
||||||
print STDERR $ua->put("${hs}/_matrix/client/unstable/groups/${group_id}/admin/users/remove/${user}?access_token=${access_token}", Content=>'{}')->status_line."\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
331
scripts/upgrade_db_to_v0.6.0.py
Normal file
331
scripts/upgrade_db_to_v0.6.0.py
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
|
||||||
|
from synapse.storage import SCHEMA_VERSION, read_schema
|
||||||
|
from synapse.storage._base import SQLBaseStore
|
||||||
|
from synapse.storage.signatures import SignatureStore
|
||||||
|
from synapse.storage.event_federation import EventFederationStore
|
||||||
|
|
||||||
|
from syutil.base64util import encode_base64, decode_base64
|
||||||
|
|
||||||
|
from synapse.crypto.event_signing import compute_event_signature
|
||||||
|
|
||||||
|
from synapse.events.builder import EventBuilder
|
||||||
|
from synapse.events.utils import prune_event
|
||||||
|
|
||||||
|
from synapse.crypto.event_signing import check_event_content_hash
|
||||||
|
|
||||||
|
from syutil.crypto.jsonsign import (
|
||||||
|
verify_signed_json, SignatureVerifyException,
|
||||||
|
)
|
||||||
|
from syutil.crypto.signing_key import decode_verify_key_bytes
|
||||||
|
|
||||||
|
from syutil.jsonutil import encode_canonical_json
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
# import dns.resolver
|
||||||
|
import hashlib
|
||||||
|
import httplib
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import syutil
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
|
||||||
|
delta_sql = """
|
||||||
|
CREATE TABLE IF NOT EXISTS event_json(
|
||||||
|
event_id TEXT NOT NULL,
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
internal_metadata NOT NULL,
|
||||||
|
json BLOB NOT NULL,
|
||||||
|
CONSTRAINT ev_j_uniq UNIQUE (event_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS event_json_id ON event_json(event_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS event_json_room_id ON event_json(room_id);
|
||||||
|
|
||||||
|
PRAGMA user_version = 10;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Store(object):
|
||||||
|
_get_event_signatures_txn = SignatureStore.__dict__["_get_event_signatures_txn"]
|
||||||
|
_get_event_content_hashes_txn = SignatureStore.__dict__["_get_event_content_hashes_txn"]
|
||||||
|
_get_event_reference_hashes_txn = SignatureStore.__dict__["_get_event_reference_hashes_txn"]
|
||||||
|
_get_prev_event_hashes_txn = SignatureStore.__dict__["_get_prev_event_hashes_txn"]
|
||||||
|
_get_prev_events_and_state = EventFederationStore.__dict__["_get_prev_events_and_state"]
|
||||||
|
_get_auth_events = EventFederationStore.__dict__["_get_auth_events"]
|
||||||
|
cursor_to_dict = SQLBaseStore.__dict__["cursor_to_dict"]
|
||||||
|
_simple_select_onecol_txn = SQLBaseStore.__dict__["_simple_select_onecol_txn"]
|
||||||
|
_simple_select_list_txn = SQLBaseStore.__dict__["_simple_select_list_txn"]
|
||||||
|
_simple_insert_txn = SQLBaseStore.__dict__["_simple_insert_txn"]
|
||||||
|
|
||||||
|
def _generate_event_json(self, txn, rows):
|
||||||
|
events = []
|
||||||
|
for row in rows:
|
||||||
|
d = dict(row)
|
||||||
|
|
||||||
|
d.pop("stream_ordering", None)
|
||||||
|
d.pop("topological_ordering", None)
|
||||||
|
d.pop("processed", None)
|
||||||
|
|
||||||
|
if "origin_server_ts" not in d:
|
||||||
|
d["origin_server_ts"] = d.pop("ts", 0)
|
||||||
|
else:
|
||||||
|
d.pop("ts", 0)
|
||||||
|
|
||||||
|
d.pop("prev_state", None)
|
||||||
|
d.update(json.loads(d.pop("unrecognized_keys")))
|
||||||
|
|
||||||
|
d["sender"] = d.pop("user_id")
|
||||||
|
|
||||||
|
d["content"] = json.loads(d["content"])
|
||||||
|
|
||||||
|
if "age_ts" not in d:
|
||||||
|
# For compatibility
|
||||||
|
d["age_ts"] = d.get("origin_server_ts", 0)
|
||||||
|
|
||||||
|
d.setdefault("unsigned", {})["age_ts"] = d.pop("age_ts")
|
||||||
|
|
||||||
|
outlier = d.pop("outlier", False)
|
||||||
|
|
||||||
|
# d.pop("membership", None)
|
||||||
|
|
||||||
|
d.pop("state_hash", None)
|
||||||
|
|
||||||
|
d.pop("replaces_state", None)
|
||||||
|
|
||||||
|
b = EventBuilder(d)
|
||||||
|
b.internal_metadata.outlier = outlier
|
||||||
|
|
||||||
|
events.append(b)
|
||||||
|
|
||||||
|
for i, ev in enumerate(events):
|
||||||
|
signatures = self._get_event_signatures_txn(
|
||||||
|
txn, ev.event_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
ev.signatures = {
|
||||||
|
n: {
|
||||||
|
k: encode_base64(v) for k, v in s.items()
|
||||||
|
}
|
||||||
|
for n, s in signatures.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes = self._get_event_content_hashes_txn(
|
||||||
|
txn, ev.event_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
ev.hashes = {
|
||||||
|
k: encode_base64(v) for k, v in hashes.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
prevs = self._get_prev_events_and_state(txn, ev.event_id)
|
||||||
|
|
||||||
|
ev.prev_events = [
|
||||||
|
(e_id, h)
|
||||||
|
for e_id, h, is_state in prevs
|
||||||
|
if is_state == 0
|
||||||
|
]
|
||||||
|
|
||||||
|
# ev.auth_events = self._get_auth_events(txn, ev.event_id)
|
||||||
|
|
||||||
|
hashes = dict(ev.auth_events)
|
||||||
|
|
||||||
|
for e_id, hash in ev.prev_events:
|
||||||
|
if e_id in hashes and not hash:
|
||||||
|
hash.update(hashes[e_id])
|
||||||
|
#
|
||||||
|
# if hasattr(ev, "state_key"):
|
||||||
|
# ev.prev_state = [
|
||||||
|
# (e_id, h)
|
||||||
|
# for e_id, h, is_state in prevs
|
||||||
|
# if is_state == 1
|
||||||
|
# ]
|
||||||
|
|
||||||
|
return [e.build() for e in events]
|
||||||
|
|
||||||
|
|
||||||
|
store = Store()
|
||||||
|
|
||||||
|
|
||||||
|
# def get_key(server_name):
|
||||||
|
# print "Getting keys for: %s" % (server_name,)
|
||||||
|
# targets = []
|
||||||
|
# if ":" in server_name:
|
||||||
|
# target, port = server_name.split(":")
|
||||||
|
# targets.append((target, int(port)))
|
||||||
|
# try:
|
||||||
|
# answers = dns.resolver.query("_matrix._tcp." + server_name, "SRV")
|
||||||
|
# for srv in answers:
|
||||||
|
# targets.append((srv.target, srv.port))
|
||||||
|
# except dns.resolver.NXDOMAIN:
|
||||||
|
# targets.append((server_name, 8448))
|
||||||
|
# except:
|
||||||
|
# print "Failed to lookup keys for %s" % (server_name,)
|
||||||
|
# return {}
|
||||||
|
#
|
||||||
|
# for target, port in targets:
|
||||||
|
# url = "https://%s:%i/_matrix/key/v1" % (target, port)
|
||||||
|
# try:
|
||||||
|
# keys = json.load(urllib2.urlopen(url, timeout=2))
|
||||||
|
# verify_keys = {}
|
||||||
|
# for key_id, key_base64 in keys["verify_keys"].items():
|
||||||
|
# verify_key = decode_verify_key_bytes(
|
||||||
|
# key_id, decode_base64(key_base64)
|
||||||
|
# )
|
||||||
|
# verify_signed_json(keys, server_name, verify_key)
|
||||||
|
# verify_keys[key_id] = verify_key
|
||||||
|
# print "Got keys for: %s" % (server_name,)
|
||||||
|
# return verify_keys
|
||||||
|
# except urllib2.URLError:
|
||||||
|
# pass
|
||||||
|
# except urllib2.HTTPError:
|
||||||
|
# pass
|
||||||
|
# except httplib.HTTPException:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# print "Failed to get keys for %s" % (server_name,)
|
||||||
|
# return {}
|
||||||
|
|
||||||
|
|
||||||
|
def reinsert_events(cursor, server_name, signing_key):
|
||||||
|
print "Running delta: v10"
|
||||||
|
|
||||||
|
cursor.executescript(delta_sql)
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT * FROM events ORDER BY rowid ASC"
|
||||||
|
)
|
||||||
|
|
||||||
|
print "Getting events..."
|
||||||
|
|
||||||
|
rows = store.cursor_to_dict(cursor)
|
||||||
|
|
||||||
|
events = store._generate_event_json(cursor, rows)
|
||||||
|
|
||||||
|
print "Got events from DB."
|
||||||
|
|
||||||
|
algorithms = {
|
||||||
|
"sha256": hashlib.sha256,
|
||||||
|
}
|
||||||
|
|
||||||
|
key_id = "%s:%s" % (signing_key.alg, signing_key.version)
|
||||||
|
verify_key = signing_key.verify_key
|
||||||
|
verify_key.alg = signing_key.alg
|
||||||
|
verify_key.version = signing_key.version
|
||||||
|
|
||||||
|
server_keys = {
|
||||||
|
server_name: {
|
||||||
|
key_id: verify_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
N = len(events)
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
if i % 100 == 0:
|
||||||
|
print "Processed: %d/%d events" % (i,N,)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# for alg_name in event.hashes:
|
||||||
|
# if check_event_content_hash(event, algorithms[alg_name]):
|
||||||
|
# pass
|
||||||
|
# else:
|
||||||
|
# pass
|
||||||
|
# print "FAIL content hash %s %s" % (alg_name, event.event_id, )
|
||||||
|
|
||||||
|
have_own_correctly_signed = False
|
||||||
|
for host, sigs in event.signatures.items():
|
||||||
|
pruned = prune_event(event)
|
||||||
|
|
||||||
|
for key_id in sigs:
|
||||||
|
if host not in server_keys:
|
||||||
|
server_keys[host] = {} # get_key(host)
|
||||||
|
if key_id in server_keys[host]:
|
||||||
|
try:
|
||||||
|
verify_signed_json(
|
||||||
|
pruned.get_pdu_json(),
|
||||||
|
host,
|
||||||
|
server_keys[host][key_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
if host == server_name:
|
||||||
|
have_own_correctly_signed = True
|
||||||
|
except SignatureVerifyException:
|
||||||
|
print "FAIL signature check %s %s" % (
|
||||||
|
key_id, event.event_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: Re sign with our own server key
|
||||||
|
if not have_own_correctly_signed:
|
||||||
|
sigs = compute_event_signature(event, server_name, signing_key)
|
||||||
|
event.signatures.update(sigs)
|
||||||
|
|
||||||
|
pruned = prune_event(event)
|
||||||
|
|
||||||
|
for key_id in event.signatures[server_name]:
|
||||||
|
verify_signed_json(
|
||||||
|
pruned.get_pdu_json(),
|
||||||
|
server_name,
|
||||||
|
server_keys[server_name][key_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
event_json = encode_canonical_json(
|
||||||
|
event.get_dict()
|
||||||
|
).decode("UTF-8")
|
||||||
|
|
||||||
|
metadata_json = encode_canonical_json(
|
||||||
|
event.internal_metadata.get_dict()
|
||||||
|
).decode("UTF-8")
|
||||||
|
|
||||||
|
store._simple_insert_txn(
|
||||||
|
cursor,
|
||||||
|
table="event_json",
|
||||||
|
values={
|
||||||
|
"event_id": event.event_id,
|
||||||
|
"room_id": event.room_id,
|
||||||
|
"internal_metadata": metadata_json,
|
||||||
|
"json": event_json,
|
||||||
|
},
|
||||||
|
or_replace=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main(database, server_name, signing_key):
|
||||||
|
conn = sqlite3.connect(database)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Do other deltas:
|
||||||
|
cursor.execute("PRAGMA user_version")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
if row and row[0]:
|
||||||
|
user_version = row[0]
|
||||||
|
# Run every version since after the current version.
|
||||||
|
for v in range(user_version + 1, 10):
|
||||||
|
print "Running delta: %d" % (v,)
|
||||||
|
sql_script = read_schema("delta/v%d" % (v,))
|
||||||
|
cursor.executescript(sql_script)
|
||||||
|
|
||||||
|
reinsert_events(cursor, server_name, signing_key)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print "Success!"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument("database")
|
||||||
|
parser.add_argument("server_name")
|
||||||
|
parser.add_argument(
|
||||||
|
"signing_key", type=argparse.FileType('r'),
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
signing_key = syutil.crypto.signing_key.read_signing_keys(
|
||||||
|
args.signing_key
|
||||||
|
)
|
||||||
|
|
||||||
|
main(args.database, args.server_name, signing_key[0])
|
||||||
@@ -3,6 +3,9 @@ source-dir = docs/sphinx
|
|||||||
build-dir = docs/build
|
build-dir = docs/build
|
||||||
all_files = 1
|
all_files = 1
|
||||||
|
|
||||||
|
[aliases]
|
||||||
|
test = trial
|
||||||
|
|
||||||
[trial]
|
[trial]
|
||||||
test_suite = tests
|
test_suite = tests
|
||||||
|
|
||||||
@@ -13,8 +16,3 @@ ignore =
|
|||||||
docs/*
|
docs/*
|
||||||
pylint.cfg
|
pylint.cfg
|
||||||
tox.ini
|
tox.ini
|
||||||
|
|
||||||
[flake8]
|
|
||||||
max-line-length = 90
|
|
||||||
# W503 requires that binary operators be at the end, not start, of lines. Erik doesn't like it.
|
|
||||||
ignore = W503
|
|
||||||
|
|||||||
56
setup.py
56
setup.py
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014 OpenMarket 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,54 +14,13 @@
|
|||||||
# 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 glob
|
|
||||||
import os
|
import os
|
||||||
from setuptools import setup, find_packages, Command
|
from setuptools import setup, find_packages
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
# Some notes on `setup.py test`:
|
|
||||||
#
|
|
||||||
# Once upon a time we used to try to make `setup.py test` run `tox` to run the
|
|
||||||
# tests. That's a bad idea for three reasons:
|
|
||||||
#
|
|
||||||
# 1: `setup.py test` is supposed to find out whether the tests work in the
|
|
||||||
# *current* environmentt, not whatever tox sets up.
|
|
||||||
# 2: Empirically, trying to install tox during the test run wasn't working ("No
|
|
||||||
# module named virtualenv").
|
|
||||||
# 3: The tox documentation advises against it[1].
|
|
||||||
#
|
|
||||||
# Even further back in time, we used to use setuptools_trial [2]. That has its
|
|
||||||
# own set of issues: for instance, it requires installation of Twisted to build
|
|
||||||
# an sdist (because the recommended mode of usage is to add it to
|
|
||||||
# `setup_requires`). That in turn means that in order to successfully run tox
|
|
||||||
# you have to have the python header files installed for whichever version of
|
|
||||||
# python tox uses (which is python3 on recent ubuntus, for example).
|
|
||||||
#
|
|
||||||
# So, for now at least, we stick with what appears to be the convention among
|
|
||||||
# Twisted projects, and don't attempt to do anything when someone runs
|
|
||||||
# `setup.py test`; instead we direct people to run `trial` directly if they
|
|
||||||
# care.
|
|
||||||
#
|
|
||||||
# [1]: http://tox.readthedocs.io/en/2.5.0/example/basic.html#integration-with-setup-py-test-command
|
|
||||||
# [2]: https://pypi.python.org/pypi/setuptools_trial
|
|
||||||
class TestCommand(Command):
|
|
||||||
user_options = []
|
|
||||||
|
|
||||||
def initialize_options(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def finalize_options(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
print ("""Synapse's tests cannot be run via setup.py. To run them, try:
|
|
||||||
PYTHONPATH="." trial tests
|
|
||||||
""")
|
|
||||||
|
|
||||||
def read_file(path_segments):
|
def read_file(path_segments):
|
||||||
"""Read a file from the package. Takes a list of strings to join to
|
"""Read a file from the package. Takes a list of strings to join to
|
||||||
make the path"""
|
make the path"""
|
||||||
@@ -77,7 +36,6 @@ def exec_file(path_segments):
|
|||||||
exec(code, result)
|
exec(code, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
version = exec_file(("synapse", "__init__.py"))["__version__"]
|
version = exec_file(("synapse", "__init__.py"))["__version__"]
|
||||||
dependencies = exec_file(("synapse", "python_dependencies.py"))
|
dependencies = exec_file(("synapse", "python_dependencies.py"))
|
||||||
long_description = read_file(("README.rst",))
|
long_description = read_file(("README.rst",))
|
||||||
@@ -88,10 +46,14 @@ setup(
|
|||||||
packages=find_packages(exclude=["tests", "tests.*"]),
|
packages=find_packages(exclude=["tests", "tests.*"]),
|
||||||
description="Reference Synapse Home Server",
|
description="Reference Synapse Home Server",
|
||||||
install_requires=dependencies['requirements'](include_conditional=True).keys(),
|
install_requires=dependencies['requirements'](include_conditional=True).keys(),
|
||||||
dependency_links=dependencies["DEPENDENCY_LINKS"].values(),
|
setup_requires=[
|
||||||
|
"Twisted==14.0.2", # Here to override setuptools_trial's dependency on Twisted>=2.4.0
|
||||||
|
"setuptools_trial",
|
||||||
|
"mock"
|
||||||
|
],
|
||||||
|
dependency_links=dependencies["DEPENDENCY_LINKS"],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
scripts=["synctl"] + glob.glob("scripts/*"),
|
scripts=["synctl", "register_new_matrix_user"],
|
||||||
cmdclass={'test': TestCommand},
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014, 2015 OpenMarket 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 +16,4 @@
|
|||||||
""" This is a reference implementation of a Matrix home server.
|
""" This is a reference implementation of a Matrix home server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.27.0-rc2"
|
__version__ = "0.8.1-r4"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014, 2015 OpenMarket 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.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014, 2015 OpenMarket Ltd
|
||||||
# Copyright 2017 Vector Creations 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.
|
||||||
@@ -28,11 +27,22 @@ class Membership(object):
|
|||||||
LIST = (INVITE, JOIN, KNOCK, LEAVE, BAN)
|
LIST = (INVITE, JOIN, KNOCK, LEAVE, BAN)
|
||||||
|
|
||||||
|
|
||||||
|
class Feedback(object):
|
||||||
|
|
||||||
|
"""Represents the types of feedback a user can send in response to a
|
||||||
|
message."""
|
||||||
|
|
||||||
|
DELIVERED = u"delivered"
|
||||||
|
READ = u"read"
|
||||||
|
LIST = (DELIVERED, READ)
|
||||||
|
|
||||||
|
|
||||||
class PresenceState(object):
|
class PresenceState(object):
|
||||||
"""Represents the presence state of a user."""
|
"""Represents the presence state of a user."""
|
||||||
OFFLINE = u"offline"
|
OFFLINE = u"offline"
|
||||||
UNAVAILABLE = u"unavailable"
|
UNAVAILABLE = u"unavailable"
|
||||||
ONLINE = u"online"
|
ONLINE = u"online"
|
||||||
|
FREE_FOR_CHAT = u"free_for_chat"
|
||||||
|
|
||||||
|
|
||||||
class JoinRules(object):
|
class JoinRules(object):
|
||||||
@@ -44,8 +54,10 @@ class JoinRules(object):
|
|||||||
|
|
||||||
class LoginType(object):
|
class LoginType(object):
|
||||||
PASSWORD = u"m.login.password"
|
PASSWORD = u"m.login.password"
|
||||||
|
OAUTH = u"m.login.oauth2"
|
||||||
|
EMAIL_CODE = u"m.login.email.code"
|
||||||
|
EMAIL_URL = u"m.login.email.url"
|
||||||
EMAIL_IDENTITY = u"m.login.email.identity"
|
EMAIL_IDENTITY = u"m.login.email.identity"
|
||||||
MSISDN = u"m.login.msisdn"
|
|
||||||
RECAPTCHA = u"m.login.recaptcha"
|
RECAPTCHA = u"m.login.recaptcha"
|
||||||
DUMMY = u"m.login.dummy"
|
DUMMY = u"m.login.dummy"
|
||||||
|
|
||||||
@@ -61,12 +73,7 @@ class EventTypes(object):
|
|||||||
PowerLevels = "m.room.power_levels"
|
PowerLevels = "m.room.power_levels"
|
||||||
Aliases = "m.room.aliases"
|
Aliases = "m.room.aliases"
|
||||||
Redaction = "m.room.redaction"
|
Redaction = "m.room.redaction"
|
||||||
ThirdPartyInvite = "m.room.third_party_invite"
|
Feedback = "m.room.message.feedback"
|
||||||
|
|
||||||
RoomHistoryVisibility = "m.room.history_visibility"
|
|
||||||
CanonicalAlias = "m.room.canonical_alias"
|
|
||||||
RoomAvatar = "m.room.avatar"
|
|
||||||
GuestAccess = "m.room.guest_access"
|
|
||||||
|
|
||||||
# These are used for validation
|
# These are used for validation
|
||||||
Message = "m.room.message"
|
Message = "m.room.message"
|
||||||
@@ -78,14 +85,3 @@ class RejectedReason(object):
|
|||||||
AUTH_ERROR = "auth_error"
|
AUTH_ERROR = "auth_error"
|
||||||
REPLACED = "replaced"
|
REPLACED = "replaced"
|
||||||
NOT_ANCESTOR = "not_ancestor"
|
NOT_ANCESTOR = "not_ancestor"
|
||||||
|
|
||||||
|
|
||||||
class RoomCreationPreset(object):
|
|
||||||
PRIVATE_CHAT = "private_chat"
|
|
||||||
PUBLIC_CHAT = "public_chat"
|
|
||||||
TRUSTED_PRIVATE_CHAT = "trusted_private_chat"
|
|
||||||
|
|
||||||
|
|
||||||
class ThirdPartyEntityKind(object):
|
|
||||||
USER = "user"
|
|
||||||
LOCATION = "location"
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
# Copyright 2014, 2015 OpenMarket 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.
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
"""Contains exceptions and error codes."""
|
"""Contains exceptions and error codes."""
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -30,68 +29,42 @@ class Codes(object):
|
|||||||
USER_IN_USE = "M_USER_IN_USE"
|
USER_IN_USE = "M_USER_IN_USE"
|
||||||
ROOM_IN_USE = "M_ROOM_IN_USE"
|
ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||||
BAD_PAGINATION = "M_BAD_PAGINATION"
|
BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||||
BAD_STATE = "M_BAD_STATE"
|
|
||||||
UNKNOWN = "M_UNKNOWN"
|
UNKNOWN = "M_UNKNOWN"
|
||||||
NOT_FOUND = "M_NOT_FOUND"
|
NOT_FOUND = "M_NOT_FOUND"
|
||||||
MISSING_TOKEN = "M_MISSING_TOKEN"
|
MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||||
UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||||
GUEST_ACCESS_FORBIDDEN = "M_GUEST_ACCESS_FORBIDDEN"
|
|
||||||
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||||
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||||
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||||
MISSING_PARAM = "M_MISSING_PARAM"
|
MISSING_PARAM = "M_MISSING_PARAM"
|
||||||
INVALID_PARAM = "M_INVALID_PARAM"
|
|
||||||
TOO_LARGE = "M_TOO_LARGE"
|
TOO_LARGE = "M_TOO_LARGE"
|
||||||
EXCLUSIVE = "M_EXCLUSIVE"
|
EXCLUSIVE = "M_EXCLUSIVE"
|
||||||
THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
||||||
THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
|
||||||
THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
|
||||||
THREEPID_DENIED = "M_THREEPID_DENIED"
|
|
||||||
INVALID_USERNAME = "M_INVALID_USERNAME"
|
|
||||||
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
|
||||||
|
|
||||||
|
|
||||||
class CodeMessageException(RuntimeError):
|
class CodeMessageException(RuntimeError):
|
||||||
"""An exception with integer code and message string attributes.
|
"""An exception with integer code and message string attributes."""
|
||||||
|
|
||||||
Attributes:
|
|
||||||
code (int): HTTP error code
|
|
||||||
msg (str): string describing the error
|
|
||||||
"""
|
|
||||||
def __init__(self, code, msg):
|
def __init__(self, code, msg):
|
||||||
|
logger.info("%s: %s, %s", type(self).__name__, code, msg)
|
||||||
super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
|
super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
|
||||||
self.code = code
|
self.code = code
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
self.response_code_message = None
|
||||||
|
|
||||||
def error_dict(self):
|
def error_dict(self):
|
||||||
return cs_error(self.msg)
|
return cs_error(self.msg)
|
||||||
|
|
||||||
|
|
||||||
class MatrixCodeMessageException(CodeMessageException):
|
|
||||||
"""An error from a general matrix endpoint, eg. from a proxied Matrix API call.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
errcode (str): Matrix error code e.g 'M_FORBIDDEN'
|
|
||||||
"""
|
|
||||||
def __init__(self, code, msg, errcode=Codes.UNKNOWN):
|
|
||||||
super(MatrixCodeMessageException, self).__init__(code, msg)
|
|
||||||
self.errcode = errcode
|
|
||||||
|
|
||||||
|
|
||||||
class SynapseError(CodeMessageException):
|
class SynapseError(CodeMessageException):
|
||||||
"""A base exception type for matrix errors which have an errcode and error
|
"""A base error which can be caught for all synapse events."""
|
||||||
message (as well as an HTTP status code).
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
errcode (str): Matrix error code e.g 'M_FORBIDDEN'
|
|
||||||
"""
|
|
||||||
def __init__(self, code, msg, errcode=Codes.UNKNOWN):
|
def __init__(self, code, msg, errcode=Codes.UNKNOWN):
|
||||||
"""Constructs a synapse error.
|
"""Constructs a synapse error.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code (int): The integer error code (an HTTP response code)
|
code (int): The integer error code (an HTTP response code)
|
||||||
msg (str): The human-readable error message.
|
msg (str): The human-readable error message.
|
||||||
errcode (str): The matrix error code e.g 'M_FORBIDDEN'
|
err (str): The error code e.g 'M_FORBIDDEN'
|
||||||
"""
|
"""
|
||||||
super(SynapseError, self).__init__(code, msg)
|
super(SynapseError, self).__init__(code, msg)
|
||||||
self.errcode = errcode
|
self.errcode = errcode
|
||||||
@@ -102,38 +75,10 @@ class SynapseError(CodeMessageException):
|
|||||||
self.errcode,
|
self.errcode,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_http_response_exception(cls, err):
|
|
||||||
"""Make a SynapseError based on an HTTPResponseException
|
|
||||||
|
|
||||||
This is useful when a proxied request has failed, and we need to
|
class RoomError(SynapseError):
|
||||||
decide how to map the failure onto a matrix error to send back to the
|
"""An error raised when a room event fails."""
|
||||||
client.
|
pass
|
||||||
|
|
||||||
An attempt is made to parse the body of the http response as a matrix
|
|
||||||
error. If that succeeds, the errcode and error message from the body
|
|
||||||
are used as the errcode and error message in the new synapse error.
|
|
||||||
|
|
||||||
Otherwise, the errcode is set to M_UNKNOWN, and the error message is
|
|
||||||
set to the reason code from the HTTP response.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
err (HttpResponseException):
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SynapseError:
|
|
||||||
"""
|
|
||||||
# try to parse the body as json, to get better errcode/msg, but
|
|
||||||
# default to M_UNKNOWN with the HTTP status as the error text
|
|
||||||
try:
|
|
||||||
j = json.loads(err.response)
|
|
||||||
except ValueError:
|
|
||||||
j = {}
|
|
||||||
errcode = j.get('errcode', Codes.UNKNOWN)
|
|
||||||
errmsg = j.get('error', err.msg)
|
|
||||||
|
|
||||||
res = SynapseError(err.code, errmsg, errcode)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationError(SynapseError):
|
class RegistrationError(SynapseError):
|
||||||
@@ -141,48 +86,6 @@ class RegistrationError(SynapseError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FederationDeniedError(SynapseError):
|
|
||||||
"""An error raised when the server tries to federate with a server which
|
|
||||||
is not on its federation whitelist.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
destination (str): The destination which has been denied
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, destination):
|
|
||||||
"""Raised by federation client or server to indicate that we are
|
|
||||||
are deliberately not attempting to contact a given server because it is
|
|
||||||
not on our federation whitelist.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): the domain in question
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.destination = destination
|
|
||||||
|
|
||||||
super(FederationDeniedError, self).__init__(
|
|
||||||
code=403,
|
|
||||||
msg="Federation denied with %s." % (self.destination,),
|
|
||||||
errcode=Codes.FORBIDDEN,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InteractiveAuthIncompleteError(Exception):
|
|
||||||
"""An error raised when UI auth is not yet complete
|
|
||||||
|
|
||||||
(This indicates we should return a 401 with 'result' as the body)
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
result (dict): the server response to the request, which should be
|
|
||||||
passed back to the client
|
|
||||||
"""
|
|
||||||
def __init__(self, result):
|
|
||||||
super(InteractiveAuthIncompleteError, self).__init__(
|
|
||||||
"Interactive auth not yet complete",
|
|
||||||
)
|
|
||||||
self.result = result
|
|
||||||
|
|
||||||
|
|
||||||
class UnrecognizedRequestError(SynapseError):
|
class UnrecognizedRequestError(SynapseError):
|
||||||
"""An error indicating we don't understand the request you're trying to make"""
|
"""An error indicating we don't understand the request you're trying to make"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -202,11 +105,13 @@ class UnrecognizedRequestError(SynapseError):
|
|||||||
|
|
||||||
class NotFoundError(SynapseError):
|
class NotFoundError(SynapseError):
|
||||||
"""An error indicating we can't find the thing you asked for"""
|
"""An error indicating we can't find the thing you asked for"""
|
||||||
def __init__(self, msg="Not found", errcode=Codes.NOT_FOUND):
|
def __init__(self, *args, **kwargs):
|
||||||
|
if "errcode" not in kwargs:
|
||||||
|
kwargs["errcode"] = Codes.NOT_FOUND
|
||||||
super(NotFoundError, self).__init__(
|
super(NotFoundError, self).__init__(
|
||||||
404,
|
404,
|
||||||
msg,
|
"Not found",
|
||||||
errcode=errcode
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -219,15 +124,6 @@ class AuthError(SynapseError):
|
|||||||
super(AuthError, self).__init__(*args, **kwargs)
|
super(AuthError, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class EventSizeError(SynapseError):
|
|
||||||
"""An error raised when an event is too big."""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if "errcode" not in kwargs:
|
|
||||||
kwargs["errcode"] = Codes.TOO_LARGE
|
|
||||||
super(EventSizeError, self).__init__(413, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class EventStreamError(SynapseError):
|
class EventStreamError(SynapseError):
|
||||||
"""An error raised when there a problem with the event stream."""
|
"""An error raised when there a problem with the event stream."""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -267,6 +163,7 @@ class LimitExceededError(SynapseError):
|
|||||||
errcode=Codes.LIMIT_EXCEEDED):
|
errcode=Codes.LIMIT_EXCEEDED):
|
||||||
super(LimitExceededError, self).__init__(code, msg, errcode)
|
super(LimitExceededError, self).__init__(code, msg, errcode)
|
||||||
self.retry_after_ms = retry_after_ms
|
self.retry_after_ms = retry_after_ms
|
||||||
|
self.response_code_message = "Too Many Requests"
|
||||||
|
|
||||||
def error_dict(self):
|
def error_dict(self):
|
||||||
return cs_error(
|
return cs_error(
|
||||||
@@ -336,19 +233,6 @@ class FederationError(RuntimeError):
|
|||||||
|
|
||||||
|
|
||||||
class HttpResponseException(CodeMessageException):
|
class HttpResponseException(CodeMessageException):
|
||||||
"""
|
|
||||||
Represents an HTTP-level failure of an outbound request
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
response (str): body of response
|
|
||||||
"""
|
|
||||||
def __init__(self, code, msg, response):
|
def __init__(self, code, msg, response):
|
||||||
"""
|
|
||||||
|
|
||||||
Args:
|
|
||||||
code (int): HTTP status code
|
|
||||||
msg (str): reason phrase from HTTP response status line
|
|
||||||
response (str): body of response
|
|
||||||
"""
|
|
||||||
super(HttpResponseException, self).__init__(code, msg)
|
|
||||||
self.response = response
|
self.response = response
|
||||||
|
super(HttpResponseException, self).__init__(code, msg)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user