1
0

Compare commits

...

18 Commits

Author SHA1 Message Date
Richard van der Hoff d0dc0014c1 update sample config 2019-03-05 17:32:34 +00:00
Richard van der Hoff 054ed1ab5b tweak changelog 2019-03-05 17:28:52 +00:00
Matthew Hodgson b556fcda72 changelog 2019-03-05 00:01:17 +00:00
Matthew Hodgson 11d64ec4a9 remove trailing space 2019-03-04 23:50:14 +00:00
Matthew Hodgson d70c2748af reword the sample config header to be less scary 2019-03-04 23:49:54 +00:00
Richard van der Hoff 48583cef7e Merge pull request #4798 from matrix-org/rav/rr_debug
Add some debug about processing read receipts.
2019-03-04 19:04:05 +00:00
Richard van der Hoff cd7110c869 Merge pull request #4797 from matrix-org/rav/inline_rr_send
Clean up read-receipt handling.
2019-03-04 19:03:40 +00:00
Richard van der Hoff 2db49ea476 Add some debug about processing read receipts.
I'm hoping to establish which rooms are having lots of RRs sent for them, and
how old the events are when they are sent.
2019-03-04 18:19:40 +00:00
Richard van der Hoff b29693a30b Clean up read-receipt handling.
Remove a call to run_as_background_process: there is no need to run this as a
background process, because build_and_send_edu does not block.

We may as well inline the whole of _push_remotes.
2019-03-04 18:16:43 +00:00
Richard van der Hoff 8e28bc5eee Include a default configuration file in the 'docs' directory. (#4791) 2019-03-04 17:14:58 +00:00
Seebi aba5eeabd5 Fix v4v6 option in HAProxy example config (#4790)
The v4v6 option only has a usage one ipv6 socket: https://serverfault.com/q/747895

Signed-off-by: Flakebi <flakebi@t-online.de>
2019-03-04 13:19:41 +00:00
Richard van der Hoff 856c83f5f8 Avoid rebuilding Edu objects in worker mode (#4770)
In worker mode, on the federation sender, when we receive an edu for sending
over the replication socket, it is parsed into an Edu object. There is no point
extracting the contents of it so that we can then immediately build another Edu.
2019-03-04 12:57:44 +00:00
Richard van der Hoff 2c3548d9d8 Update test_typing to use HomeserverTestCase. (#4771) 2019-03-04 10:05:39 +00:00
Richard van der Hoff 3064952939 Fix incorrect log about not persisting duplicate state event. (#4776)
We were logging this when it was not true.
2019-03-01 16:47:12 +00:00
Richard van der Hoff 1beebe916f Merge branch 'master' into develop 2019-03-01 10:58:39 +00:00
Richard van der Hoff ac6a0d72b2 0.99.2 2019-03-01 10:56:22 +00:00
Richard van der Hoff 9ac72d9543 0.99.2 2019-03-01 10:55:44 +00:00
Andrew Morgan ac61b45a75 Minor docstring fixes for MatrixFederationAgent (#4765) 2019-02-28 16:24:01 +00:00
35 changed files with 1424 additions and 290 deletions
+8
View File
@@ -38,6 +38,14 @@ steps:
- wait
- command:
- "python -m pip install tox"
- "tox -e check-sampleconfig"
label: "\U0001F9F9 check-sample-config"
plugins:
- docker#v3.0.1:
image: "python:3.6"
- command:
- "python -m pip install tox"
- "tox -e py27,codecov"
+2 -2
View File
@@ -1,5 +1,5 @@
Synapse 0.99.2rc1 (2019-02-27)
==============================
Synapse 0.99.2 (2019-03-01)
===========================
Features
--------
+1
View File
@@ -0,0 +1 @@
Minor docstring fixes for MatrixFederationAgent.
+1
View File
@@ -0,0 +1 @@
Optimise EDU transmission for the federation_sender worker.
+1
View File
@@ -0,0 +1 @@
Update test_typing to use HomeserverTestCase.
+1
View File
@@ -0,0 +1 @@
Fix incorrect log about not persisting duplicate state event.
+1
View File
@@ -0,0 +1 @@
Fix v4v6 option in HAProxy example config. Contributed by Flakebi.
+1
View File
@@ -0,0 +1 @@
Include a default configuration file in the 'docs' directory.
+1
View File
@@ -0,0 +1 @@
Clean up read-receipt handling.
+1
View File
@@ -0,0 +1 @@
Add some debug about processing read receipts.
+1
View File
@@ -0,0 +1 @@
Include a default configuration file in the 'docs' directory.
+3 -3
View File
@@ -1,9 +1,9 @@
matrix-synapse-py3 (0.99.2rc1) stable; urgency=medium
matrix-synapse-py3 (0.99.2) stable; urgency=medium
* Fix overwriting of config settings on upgrade.
* New synapse release 0.99.2rc1.
* New synapse release 0.99.2.
-- Synapse Packaging team <packages@matrix.org> Wed, 27 Feb 2019 10:45:58 +0000
-- Synapse Packaging team <packages@matrix.org> Fri, 01 Mar 2019 10:55:08 +0000
matrix-synapse-py3 (0.99.1.1) stable; urgency=medium
+12
View File
@@ -0,0 +1,12 @@
# The config is maintained as an up-to-date snapshot of the default
# homeserver.yaml configuration generated by Synapse.
#
# It is intended to act as a reference for the default configuration,
# helping admins keep track of new options and other changes, and compare
# their configs with the current default. As such, many of the actual
# config values shown are placeholders.
#
# It is *not* intended to be copied and used as the basis for a real
# homeserver.yaml. Instead, if you are starting from scratch, please generate
# a fresh config using Synapse by following the instructions in INSTALL.md.
+5 -7
View File
@@ -88,18 +88,16 @@ Let's assume that we expect clients to connect to our server at
* HAProxy::
frontend https
bind 0.0.0.0:443 v4v6 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
bind :::443 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
bind :::443 v4v6 ssl crt /etc/ssl/haproxy/ strict-sni alpn h2,http/1.1
# Matrix client traffic
acl matrix hdr(host) -i matrix.example.com
use_backend matrix if matrix
frontend matrix-federation
bind 0.0.0.0:8448 v4v6 ssl crt /etc/ssl/haproxy/synapse.pem alpn h2,http/1.1
bind :::8448 ssl crt /etc/ssl/haproxy/synapse.pem alpn h2,http/1.1
bind :::8448 v4v6 ssl crt /etc/ssl/haproxy/synapse.pem alpn h2,http/1.1
default_backend matrix
backend matrix
server matrix 127.0.0.1:8008
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
#
# Update/check the docs/sample_config.yaml
set -e
cd `dirname $0`/..
SAMPLE_CONFIG="docs/sample_config.yaml"
if [ "$1" == "--check" ]; then
diff -u "$SAMPLE_CONFIG" <(./scripts/generate_config --header-file docs/.sample_config_header.yaml) >/dev/null || {
echo -e "\e[1m\e[31m$SAMPLE_CONFIG is not up-to-date. Regenerate it with \`scripts-dev/generate_sample_config\`.\e[0m" >&2
exit 1
}
else
./scripts/generate_config --header-file docs/.sample_config_header.yaml -o "$SAMPLE_CONFIG"
fi
+11
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python
import argparse
import shutil
import sys
from synapse.config.homeserver import HomeServerConfig
@@ -50,6 +51,13 @@ if __name__ == "__main__":
help="File to write the configuration to. Default: stdout",
)
parser.add_argument(
"--header-file",
type=argparse.FileType('r'),
help="File from which to read a header, which will be printed before the "
"generated config.",
)
args = parser.parse_args()
report_stats = args.report_stats
@@ -64,4 +72,7 @@ if __name__ == "__main__":
report_stats=report_stats,
)
if args.header_file:
shutil.copyfileobj(args.header_file, args.output_file)
args.output_file.write(conf)
+1 -1
View File
@@ -27,4 +27,4 @@ try:
except ImportError:
pass
__version__ = "0.99.2rc1"
__version__ = "0.99.2"
+16 -11
View File
@@ -180,9 +180,7 @@ class Config(object):
Returns:
str: the yaml config file
"""
default_config = "# vim:ft=yaml\n"
default_config += "\n\n".join(
default_config = "\n\n".join(
dedent(conf)
for conf in self.invoke_all(
"default_config",
@@ -297,19 +295,26 @@ class Config(object):
"Must specify a server_name to a generate config for."
" Pass -H server.name."
)
config_str = obj.generate_config(
config_dir_path=config_dir_path,
data_dir_path=os.getcwd(),
server_name=server_name,
report_stats=(config_args.report_stats == "yes"),
generate_secrets=True,
)
if not cls.path_exists(config_dir_path):
os.makedirs(config_dir_path)
with open(config_path, "w") as config_file:
config_str = obj.generate_config(
config_dir_path=config_dir_path,
data_dir_path=os.getcwd(),
server_name=server_name,
report_stats=(config_args.report_stats == "yes"),
generate_secrets=True,
config_file.write(
"# vim:ft=yaml\n\n"
)
config = yaml.load(config_str)
obj.invoke_all("generate_files", config)
config_file.write(config_str)
config = yaml.load(config_str)
obj.invoke_all("generate_files", config)
print(
(
"A config file has been generated in %r for server name"
+2 -1
View File
@@ -49,7 +49,8 @@ class DatabaseConfig(Config):
def default_config(self, data_dir_path, **kwargs):
database_path = os.path.join(data_dir_path, "homeserver.db")
return """\
# Database configuration
## Database ##
database:
# The database engine name
name: "sqlite3"
+3 -1
View File
@@ -81,7 +81,9 @@ class LoggingConfig(Config):
def default_config(self, config_dir_path, server_name, **kwargs):
log_config = os.path.join(config_dir_path, server_name + ".log.config")
return """
return """\
## Logging ##
# A yaml python logging config file
#
log_config: "%(log_config)s"
+4
View File
@@ -260,9 +260,11 @@ class ServerConfig(Config):
# This is used by remote servers to connect to this server,
# e.g. matrix.org, localhost:8080, etc.
# This is also the last part of your UserID.
#
server_name: "%(server_name)s"
# When running as a daemon, the file to store the pid in
#
pid_file: %(pid_file)s
# CPU affinity mask. Setting this restricts the CPUs on which the
@@ -304,9 +306,11 @@ class ServerConfig(Config):
# Set the soft limit on the number of file descriptors synapse can use
# Zero is used to indicate synapse should set the soft limit to the
# hard limit.
#
soft_file_limit: 0
# Set to false to disable presence tracking on this homeserver.
#
use_presence: true
# The GC threshold parameters to pass to `gc.set_threshold`, if defined
+7 -7
View File
@@ -159,8 +159,12 @@ class FederationRemoteSendQueue(object):
# stream.
pass
def send_edu(self, destination, edu_type, content, key=None):
def build_and_send_edu(self, destination, edu_type, content, key=None):
"""As per TransactionQueue"""
if destination == self.server_name:
logger.info("Not sending EDU to ourselves")
return
pos = self._next_pos()
edu = Edu(
@@ -465,15 +469,11 @@ def process_rows_for_federation(transaction_queue, rows):
for destination, edu_map in iteritems(buff.keyed_edus):
for key, edu in edu_map.items():
transaction_queue.send_edu(
edu.destination, edu.edu_type, edu.content, key=key,
)
transaction_queue.send_edu(edu, key)
for destination, edu_list in iteritems(buff.edus):
for edu in edu_list:
transaction_queue.send_edu(
edu.destination, edu.edu_type, edu.content, key=None,
)
transaction_queue.send_edu(edu, None)
for destination in buff.device_destinations:
transaction_queue.send_device_messages(destination)
+24 -7
View File
@@ -361,7 +361,19 @@ class TransactionQueue(object):
self._attempt_new_transaction(destination)
def send_edu(self, destination, edu_type, content, key=None):
def build_and_send_edu(self, destination, edu_type, content, key=None):
"""Construct an Edu object, and queue it for sending
Args:
destination (str): name of server to send to
edu_type (str): type of EDU to send
content (dict): content of EDU
key (Any|None): clobbering key for this edu
"""
if destination == self.server_name:
logger.info("Not sending EDU to ourselves")
return
edu = Edu(
origin=self.server_name,
destination=destination,
@@ -369,18 +381,23 @@ class TransactionQueue(object):
content=content,
)
if destination == self.server_name:
logger.info("Not sending EDU to ourselves")
return
self.send_edu(edu, key)
def send_edu(self, edu, key):
"""Queue an EDU for sending
Args:
edu (Edu): edu to send
key (Any|None): clobbering key for this edu
"""
if key:
self.pending_edus_keyed_by_dest.setdefault(
destination, {}
edu.destination, {}
)[(edu.edu_type, key)] = edu
else:
self.pending_edus_by_dest.setdefault(destination, []).append(edu)
self.pending_edus_by_dest.setdefault(edu.destination, []).append(edu)
self._attempt_new_transaction(destination)
self._attempt_new_transaction(edu.destination)
def send_device_messages(self, destination):
if destination == self.server_name:
+4 -3
View File
@@ -436,10 +436,11 @@ class EventCreationHandler(object):
if event.is_state():
prev_state = yield self.deduplicate_state_event(event, context)
logger.info(
"Not bothering to persist duplicate state event %s", event.event_id,
)
if prev_state is not None:
logger.info(
"Not bothering to persist state event %s duplicated by %s",
event.event_id, prev_state.event_id,
)
defer.returnValue(prev_state)
yield self.handle_new_client_event(
+3 -3
View File
@@ -816,7 +816,7 @@ class PresenceHandler(object):
if self.is_mine(observed_user):
yield self.invite_presence(observed_user, observer_user)
else:
yield self.federation.send_edu(
yield self.federation.build_and_send_edu(
destination=observed_user.domain,
edu_type="m.presence_invite",
content={
@@ -836,7 +836,7 @@ class PresenceHandler(object):
if self.is_mine(observer_user):
yield self.accept_presence(observed_user, observer_user)
else:
self.federation.send_edu(
self.federation.build_and_send_edu(
destination=observer_user.domain,
edu_type="m.presence_accept",
content={
@@ -848,7 +848,7 @@ class PresenceHandler(object):
state_dict = yield self.get_state(observed_user, as_event=False)
state_dict = format_user_presence_state(state_dict, self.clock.time_msec())
self.federation.send_edu(
self.federation.build_and_send_edu(
destination=observer_user.domain,
edu_type="m.presence",
content={
+42 -57
View File
@@ -16,7 +16,6 @@ import logging
from twisted.internet import defer
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.types import get_domain_from_id
from ._base import BaseHandler
@@ -38,31 +37,6 @@ class ReceiptsHandler(BaseHandler):
self.clock = self.hs.get_clock()
self.state = hs.get_state_handler()
@defer.inlineCallbacks
def received_client_receipt(self, room_id, receipt_type, user_id,
event_id):
"""Called when a client tells us a local user has read up to the given
event_id in the room.
"""
receipt = {
"room_id": room_id,
"receipt_type": receipt_type,
"user_id": user_id,
"event_ids": [event_id],
"data": {
"ts": int(self.clock.time_msec()),
}
}
is_new = yield self._handle_new_receipts([receipt])
if is_new:
# fire off a process in the background to send the receipt to
# remote servers
run_as_background_process(
'push_receipts_to_remotes', self._push_remotes, receipt
)
@defer.inlineCallbacks
def _received_remote_receipt(self, origin, content):
"""Called when we receive an EDU of type m.receipt from a remote HS.
@@ -128,43 +102,54 @@ class ReceiptsHandler(BaseHandler):
defer.returnValue(True)
@defer.inlineCallbacks
def _push_remotes(self, receipt):
"""Given a receipt, works out which remote servers should be
poked and pokes them.
def received_client_receipt(self, room_id, receipt_type, user_id,
event_id):
"""Called when a client tells us a local user has read up to the given
event_id in the room.
"""
try:
# TODO: optimise this to move some of the work to the workers.
room_id = receipt["room_id"]
receipt_type = receipt["receipt_type"]
user_id = receipt["user_id"]
event_ids = receipt["event_ids"]
data = receipt["data"]
receipt = {
"room_id": room_id,
"receipt_type": receipt_type,
"user_id": user_id,
"event_ids": [event_id],
"data": {
"ts": int(self.clock.time_msec()),
}
}
users = yield self.state.get_current_user_in_room(room_id)
remotedomains = set(get_domain_from_id(u) for u in users)
remotedomains = remotedomains.copy()
remotedomains.discard(self.server_name)
is_new = yield self._handle_new_receipts([receipt])
if not is_new:
return
logger.debug("Sending receipt to: %r", remotedomains)
# Work out which remote servers should be poked and poke them.
for domain in remotedomains:
self.federation.send_edu(
destination=domain,
edu_type="m.receipt",
content={
room_id: {
receipt_type: {
user_id: {
"event_ids": event_ids,
"data": data,
}
# TODO: optimise this to move some of the work to the workers.
data = receipt["data"]
# XXX why does this not use state.get_current_hosts_in_room() ?
users = yield self.state.get_current_user_in_room(room_id)
remotedomains = set(get_domain_from_id(u) for u in users)
remotedomains = remotedomains.copy()
remotedomains.discard(self.server_name)
logger.debug("Sending receipt to: %r", remotedomains)
for domain in remotedomains:
self.federation.build_and_send_edu(
destination=domain,
edu_type="m.receipt",
content={
room_id: {
receipt_type: {
user_id: {
"event_ids": [event_id],
"data": data,
}
},
}
},
key=(room_id, receipt_type, user_id),
)
except Exception:
logger.exception("Error pushing receipts to remote servers")
},
key=(room_id, receipt_type, user_id),
)
@defer.inlineCallbacks
def get_receipts_for_room(self, room_id, to_key):
+1 -1
View File
@@ -231,7 +231,7 @@ class TypingHandler(object):
for domain in set(get_domain_from_id(u) for u in users):
if domain != self.server_name:
logger.debug("sending typing update to %s", domain)
self.federation.send_edu(
self.federation.build_and_send_edu(
destination=domain,
edu_type="m.typing",
content={
@@ -68,9 +68,13 @@ class MatrixFederationAgent(object):
TLS policy to use for fetching .well-known files. None to use a default
(browser-like) implementation.
srv_resolver (SrvResolver|None):
_srv_resolver (SrvResolver|None):
SRVResolver impl to use for looking up SRV records. None to use a default
implementation.
_well_known_cache (TTLCache|None):
TTLCache impl for storing cached well-known lookups. None to use a default
implementation.
"""
def __init__(
+20 -6
View File
@@ -346,15 +346,23 @@ class ReceiptsStore(ReceiptsWorkerStore):
def insert_linearized_receipt_txn(self, txn, room_id, receipt_type,
user_id, event_id, data, stream_id):
"""Inserts a read-receipt into the database if it's newer than the current RR
Returns: int|None
None if the RR is older than the current RR
otherwise, the rx timestamp of the event that the RR corresponds to
(or 0 if the event is unknown)
"""
res = self._simple_select_one_txn(
txn,
table="events",
retcols=["topological_ordering", "stream_ordering"],
retcols=["stream_ordering", "received_ts"],
keyvalues={"event_id": event_id},
allow_none=True
)
stream_ordering = int(res["stream_ordering"]) if res else None
rx_ts = res["received_ts"] if res else 0
# We don't want to clobber receipts for more recent events, so we
# have to compare orderings of existing receipts
@@ -373,7 +381,7 @@ class ReceiptsStore(ReceiptsWorkerStore):
"one for later event %s",
event_id, eid,
)
return False
return None
txn.call_after(
self.get_receipts_for_room.invalidate, (room_id, receipt_type)
@@ -429,7 +437,7 @@ class ReceiptsStore(ReceiptsWorkerStore):
stream_ordering=stream_ordering,
)
return True
return rx_ts
@defer.inlineCallbacks
def insert_receipt(self, room_id, receipt_type, user_id, event_ids, data):
@@ -466,7 +474,7 @@ class ReceiptsStore(ReceiptsWorkerStore):
stream_id_manager = self._receipts_id_gen.get_next()
with stream_id_manager as stream_id:
have_persisted = yield self.runInteraction(
event_ts = yield self.runInteraction(
"insert_linearized_receipt",
self.insert_linearized_receipt_txn,
room_id, receipt_type, user_id, linearized_event_id,
@@ -474,8 +482,14 @@ class ReceiptsStore(ReceiptsWorkerStore):
stream_id=stream_id,
)
if not have_persisted:
defer.returnValue(None)
if event_ts is None:
defer.returnValue(None)
now = self._clock.time_msec()
logger.debug(
"RR for event %s in %s (%i ms old)",
linearized_event_id, room_id, now - event_ts,
)
yield self.insert_graph_receipt(
room_id, receipt_type, user_id, event_ids, data
+133 -157
View File
@@ -24,13 +24,17 @@ from synapse.api.errors import AuthError
from synapse.types import UserID
from tests import unittest
from tests.utils import register_federation_servlets
from ..utils import (
DeferredMockCallable,
MockClock,
MockHttpResource,
setup_test_homeserver,
)
# Some local users to test with
U_APPLE = UserID.from_string("@apple:test")
U_BANANA = UserID.from_string("@banana:test")
# Remote user
U_ONION = UserID.from_string("@onion:farm")
# Test room id
ROOM_ID = "a-room"
def _expect_edu_transaction(edu_type, content, origin="test"):
@@ -46,30 +50,21 @@ def _make_edu_transaction_json(edu_type, content):
return json.dumps(_expect_edu_transaction(edu_type, content)).encode('utf8')
class TypingNotificationsTestCase(unittest.TestCase):
"""Tests typing notifications to rooms."""
class TypingNotificationsTestCase(unittest.HomeserverTestCase):
servlets = [register_federation_servlets]
@defer.inlineCallbacks
def setUp(self):
self.clock = MockClock()
def make_homeserver(self, reactor, clock):
# we mock out the keyring so as to skip the authentication check on the
# federation API call.
mock_keyring = Mock(spec=["verify_json_for_server"])
mock_keyring.verify_json_for_server.return_value = defer.succeed(True)
self.mock_http_client = Mock(spec=[])
self.mock_http_client.put_json = DeferredMockCallable()
# we mock out the federation client too
mock_federation_client = Mock(spec=["put_json"])
mock_federation_client.put_json.return_value = defer.succeed((200, "OK"))
self.mock_federation_resource = MockHttpResource()
mock_notifier = Mock()
self.on_new_event = mock_notifier.on_new_event
self.auth = Mock(spec=[])
self.state_handler = Mock()
hs = yield setup_test_homeserver(
self.addCleanup,
"test",
auth=self.auth,
clock=self.clock,
datastore=Mock(
hs = self.setup_test_homeserver(
datastore=(Mock(
spec=[
# Bits that Federation needs
"prep_send_transaction",
@@ -82,16 +77,21 @@ class TypingNotificationsTestCase(unittest.TestCase):
"get_user_directory_stream_pos",
"get_current_state_deltas",
]
),
state_handler=self.state_handler,
handlers=Mock(),
notifier=mock_notifier,
resource_for_client=Mock(),
resource_for_federation=self.mock_federation_resource,
http_client=self.mock_http_client,
keyring=Mock(),
)),
notifier=Mock(),
http_client=mock_federation_client,
keyring=mock_keyring,
)
return hs
def prepare(self, reactor, clock, hs):
# the tests assume that we are starting at unix time 1000
reactor.pump((1000, ))
mock_notifier = hs.get_notifier()
self.on_new_event = mock_notifier.on_new_event
self.handler = hs.get_typing_handler()
self.event_source = hs.get_event_sources().sources["typing"]
@@ -109,13 +109,12 @@ class TypingNotificationsTestCase(unittest.TestCase):
self.datastore.get_received_txn_response = get_received_txn_response
self.room_id = "a-room"
self.room_members = []
def check_joined_room(room_id, user_id):
if user_id not in [u.to_string() for u in self.room_members]:
raise AuthError(401, "User is not in the room")
hs.get_auth().check_joined_room = check_joined_room
def get_joined_hosts_for_room(room_id):
return set(member.domain for member in self.room_members)
@@ -124,8 +123,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
def get_current_user_in_room(room_id):
return set(str(u) for u in self.room_members)
self.state_handler.get_current_user_in_room = get_current_user_in_room
hs.get_state_handler().get_current_user_in_room = get_current_user_in_room
self.datastore.get_user_directory_stream_pos.return_value = (
# we deliberately return a non-None stream pos to avoid doing an initial_spam
@@ -134,230 +132,208 @@ class TypingNotificationsTestCase(unittest.TestCase):
self.datastore.get_current_state_deltas.return_value = None
self.auth.check_joined_room = check_joined_room
self.datastore.get_to_device_stream_token = lambda: 0
self.datastore.get_new_device_msgs_for_remote = lambda *args, **kargs: ([], 0)
self.datastore.delete_device_msgs_for_remote = lambda *args, **kargs: None
# Some local users to test with
self.u_apple = UserID.from_string("@apple:test")
self.u_banana = UserID.from_string("@banana:test")
# Remote user
self.u_onion = UserID.from_string("@onion:farm")
@defer.inlineCallbacks
def test_started_typing_local(self):
self.room_members = [self.u_apple, self.u_banana]
self.room_members = [U_APPLE, U_BANANA]
self.assertEquals(self.event_source.get_current_key(), 0)
yield self.handler.started_typing(
target_user=self.u_apple,
auth_user=self.u_apple,
room_id=self.room_id,
self.successResultOf(self.handler.started_typing(
target_user=U_APPLE,
auth_user=U_APPLE,
room_id=ROOM_ID,
timeout=20000,
)
))
self.on_new_event.assert_has_calls(
[call('typing_key', 1, rooms=[self.room_id])]
[call('typing_key', 1, rooms=[ROOM_ID])]
)
self.assertEquals(self.event_source.get_current_key(), 1)
events = yield self.event_source.get_new_events(
room_ids=[self.room_id], from_key=0
events = self.event_source.get_new_events(
room_ids=[ROOM_ID], from_key=0
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": self.room_id,
"content": {"user_ids": [self.u_apple.to_string()]},
"room_id": ROOM_ID,
"content": {"user_ids": [U_APPLE.to_string()]},
}
],
)
@defer.inlineCallbacks
def test_started_typing_remote_send(self):
self.room_members = [self.u_apple, self.u_onion]
self.room_members = [U_APPLE, U_ONION]
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call(
"farm",
path="/_matrix/federation/v1/send/1000000/",
data=_expect_edu_transaction(
"m.typing",
content={
"room_id": self.room_id,
"user_id": self.u_apple.to_string(),
"typing": True,
},
),
json_data_callback=ANY,
long_retries=True,
backoff_on_404=True,
),
defer.succeed((200, "OK")),
)
yield self.handler.started_typing(
target_user=self.u_apple,
auth_user=self.u_apple,
room_id=self.room_id,
self.successResultOf(self.handler.started_typing(
target_user=U_APPLE,
auth_user=U_APPLE,
room_id=ROOM_ID,
timeout=20000,
))
put_json = self.hs.get_http_client().put_json
put_json.assert_called_once_with(
"farm",
path="/_matrix/federation/v1/send/1000000/",
data=_expect_edu_transaction(
"m.typing",
content={
"room_id": ROOM_ID,
"user_id": U_APPLE.to_string(),
"typing": True,
},
),
json_data_callback=ANY,
long_retries=True,
backoff_on_404=True,
)
yield put_json.await_calls()
@defer.inlineCallbacks
def test_started_typing_remote_recv(self):
self.room_members = [self.u_apple, self.u_onion]
self.room_members = [U_APPLE, U_ONION]
self.assertEquals(self.event_source.get_current_key(), 0)
(code, response) = yield self.mock_federation_resource.trigger(
(request, channel) = self.make_request(
"PUT",
"/_matrix/federation/v1/send/1000000/",
_make_edu_transaction_json(
"m.typing",
content={
"room_id": self.room_id,
"user_id": self.u_onion.to_string(),
"room_id": ROOM_ID,
"user_id": U_ONION.to_string(),
"typing": True,
},
),
federation_auth_origin=b'farm',
)
self.render(request)
self.assertEqual(channel.code, 200)
self.on_new_event.assert_has_calls(
[call('typing_key', 1, rooms=[self.room_id])]
[call('typing_key', 1, rooms=[ROOM_ID])]
)
self.assertEquals(self.event_source.get_current_key(), 1)
events = yield self.event_source.get_new_events(
room_ids=[self.room_id], from_key=0
events = self.event_source.get_new_events(
room_ids=[ROOM_ID], from_key=0
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": self.room_id,
"content": {"user_ids": [self.u_onion.to_string()]},
"room_id": ROOM_ID,
"content": {"user_ids": [U_ONION.to_string()]},
}
],
)
@defer.inlineCallbacks
def test_stopped_typing(self):
self.room_members = [self.u_apple, self.u_banana, self.u_onion]
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call(
"farm",
path="/_matrix/federation/v1/send/1000000/",
data=_expect_edu_transaction(
"m.typing",
content={
"room_id": self.room_id,
"user_id": self.u_apple.to_string(),
"typing": False,
},
),
json_data_callback=ANY,
long_retries=True,
backoff_on_404=True,
),
defer.succeed((200, "OK")),
)
self.room_members = [U_APPLE, U_BANANA, U_ONION]
# Gut-wrenching
from synapse.handlers.typing import RoomMember
member = RoomMember(self.room_id, self.u_apple.to_string())
member = RoomMember(ROOM_ID, U_APPLE.to_string())
self.handler._member_typing_until[member] = 1002000
self.handler._room_typing[self.room_id] = set([self.u_apple.to_string()])
self.handler._room_typing[ROOM_ID] = set([U_APPLE.to_string()])
self.assertEquals(self.event_source.get_current_key(), 0)
yield self.handler.stopped_typing(
target_user=self.u_apple, auth_user=self.u_apple, room_id=self.room_id
)
self.successResultOf(self.handler.stopped_typing(
target_user=U_APPLE, auth_user=U_APPLE, room_id=ROOM_ID
))
self.on_new_event.assert_has_calls(
[call('typing_key', 1, rooms=[self.room_id])]
[call('typing_key', 1, rooms=[ROOM_ID])]
)
yield put_json.await_calls()
put_json = self.hs.get_http_client().put_json
put_json.assert_called_once_with(
"farm",
path="/_matrix/federation/v1/send/1000000/",
data=_expect_edu_transaction(
"m.typing",
content={
"room_id": ROOM_ID,
"user_id": U_APPLE.to_string(),
"typing": False,
},
),
json_data_callback=ANY,
long_retries=True,
backoff_on_404=True,
)
self.assertEquals(self.event_source.get_current_key(), 1)
events = yield self.event_source.get_new_events(
room_ids=[self.room_id], from_key=0
events = self.event_source.get_new_events(
room_ids=[ROOM_ID], from_key=0
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": self.room_id,
"room_id": ROOM_ID,
"content": {"user_ids": []},
}
],
)
@defer.inlineCallbacks
def test_typing_timeout(self):
self.room_members = [self.u_apple, self.u_banana]
self.room_members = [U_APPLE, U_BANANA]
self.assertEquals(self.event_source.get_current_key(), 0)
yield self.handler.started_typing(
target_user=self.u_apple,
auth_user=self.u_apple,
room_id=self.room_id,
self.successResultOf(self.handler.started_typing(
target_user=U_APPLE,
auth_user=U_APPLE,
room_id=ROOM_ID,
timeout=10000,
)
))
self.on_new_event.assert_has_calls(
[call('typing_key', 1, rooms=[self.room_id])]
[call('typing_key', 1, rooms=[ROOM_ID])]
)
self.on_new_event.reset_mock()
self.assertEquals(self.event_source.get_current_key(), 1)
events = yield self.event_source.get_new_events(
room_ids=[self.room_id], from_key=0
events = self.event_source.get_new_events(
room_ids=[ROOM_ID], from_key=0
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": self.room_id,
"content": {"user_ids": [self.u_apple.to_string()]},
"room_id": ROOM_ID,
"content": {"user_ids": [U_APPLE.to_string()]},
}
],
)
self.clock.advance_time(16)
self.reactor.pump([16, ])
self.on_new_event.assert_has_calls(
[call('typing_key', 2, rooms=[self.room_id])]
[call('typing_key', 2, rooms=[ROOM_ID])]
)
self.assertEquals(self.event_source.get_current_key(), 2)
events = yield self.event_source.get_new_events(
room_ids=[self.room_id], from_key=1
events = self.event_source.get_new_events(
room_ids=[ROOM_ID], from_key=1
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": self.room_id,
"room_id": ROOM_ID,
"content": {"user_ids": []},
}
],
@@ -365,29 +341,29 @@ class TypingNotificationsTestCase(unittest.TestCase):
# SYN-230 - see if we can still set after timeout
yield self.handler.started_typing(
target_user=self.u_apple,
auth_user=self.u_apple,
room_id=self.room_id,
self.successResultOf(self.handler.started_typing(
target_user=U_APPLE,
auth_user=U_APPLE,
room_id=ROOM_ID,
timeout=10000,
)
))
self.on_new_event.assert_has_calls(
[call('typing_key', 3, rooms=[self.room_id])]
[call('typing_key', 3, rooms=[ROOM_ID])]
)
self.on_new_event.reset_mock()
self.assertEquals(self.event_source.get_current_key(), 3)
events = yield self.event_source.get_new_events(
room_ids=[self.room_id], from_key=0
events = self.event_source.get_new_events(
room_ids=[ROOM_ID], from_key=0
)
self.assertEquals(
events[0],
[
{
"type": "m.typing",
"room_id": self.room_id,
"content": {"user_ids": [self.u_apple.to_string()]},
"room_id": ROOM_ID,
"content": {"user_ids": [U_APPLE.to_string()]},
}
],
)
+15 -6
View File
@@ -137,6 +137,7 @@ def make_request(
access_token=None,
request=SynapseRequest,
shorthand=True,
federation_auth_origin=None,
):
"""
Make a web request using the given method and path, feed it the
@@ -150,9 +151,11 @@ def make_request(
a dict.
shorthand: Whether to try and be helpful and prefix the given URL
with the usual REST API path, if it doesn't contain it.
federation_auth_origin (bytes|None): if set to not-None, we will add a fake
Authorization header pretenting to be the given server name.
Returns:
A synapse.http.site.SynapseRequest.
Tuple[synapse.http.site.SynapseRequest, channel]
"""
if not isinstance(method, bytes):
method = method.encode('ascii')
@@ -184,6 +187,11 @@ def make_request(
b"Authorization", b"Bearer " + access_token.encode('ascii')
)
if federation_auth_origin is not None:
req.requestHeaders.addRawHeader(
b"Authorization", b"X-Matrix origin=%s,key=,sig=" % (federation_auth_origin,)
)
if content:
req.requestHeaders.addRawHeader(b"Content-Type", b"application/json")
@@ -288,9 +296,6 @@ def setup_test_homeserver(cleanup_func, *args, **kwargs):
**kwargs
)
pool.runWithConnection = runWithConnection
pool.runInteraction = runInteraction
class ThreadPool:
"""
Threadless thread pool.
@@ -316,8 +321,12 @@ def setup_test_homeserver(cleanup_func, *args, **kwargs):
return d
clock.threadpool = ThreadPool()
pool.threadpool = ThreadPool()
pool.running = True
if pool:
pool.runWithConnection = runWithConnection
pool.runInteraction = runInteraction
pool.threadpool = ThreadPool()
pool.running = True
return d
+6 -2
View File
@@ -262,6 +262,7 @@ class HomeserverTestCase(TestCase):
access_token=None,
request=SynapseRequest,
shorthand=True,
federation_auth_origin=None,
):
"""
Create a SynapseRequest at the path using the method and containing the
@@ -275,15 +276,18 @@ class HomeserverTestCase(TestCase):
a dict.
shorthand: Whether to try and be helpful and prefix the given URL
with the usual REST API path, if it doesn't contain it.
federation_auth_origin (bytes|None): if set to not-None, we will add a fake
Authorization header pretenting to be the given server name.
Returns:
A synapse.http.site.SynapseRequest.
Tuple[synapse.http.site.SynapseRequest, channel]
"""
if isinstance(content, dict):
content = json.dumps(content).encode('utf8')
return make_request(
self.reactor, method, path, content, access_token, request, shorthand
self.reactor, method, path, content, access_token, request, shorthand,
federation_auth_origin,
)
def render(self, request):
+21 -14
View File
@@ -29,7 +29,7 @@ from twisted.internet import defer, reactor
from synapse.api.constants import EventTypes, RoomVersions
from synapse.api.errors import CodeMessageException, cs_error
from synapse.config.server import ServerConfig
from synapse.federation.transport import server
from synapse.federation.transport import server as federation_server
from synapse.http.server import HttpServer
from synapse.server import HomeServer
from synapse.storage import DataStore
@@ -200,6 +200,9 @@ def setup_test_homeserver(
Args:
cleanup_func : The function used to register a cleanup routine for
after the test.
Calling this method directly is deprecated: you should instead derive from
HomeserverTestCase.
"""
if reactor is None:
from twisted.internet import reactor
@@ -351,23 +354,27 @@ def setup_test_homeserver(
fed = kargs.get("resource_for_federation", None)
if fed:
server.register_servlets(
hs,
resource=fed,
authenticator=server.Authenticator(hs),
ratelimiter=FederationRateLimiter(
hs.get_clock(),
window_size=hs.config.federation_rc_window_size,
sleep_limit=hs.config.federation_rc_sleep_limit,
sleep_msec=hs.config.federation_rc_sleep_delay,
reject_limit=hs.config.federation_rc_reject_limit,
concurrent_requests=hs.config.federation_rc_concurrent,
),
)
register_federation_servlets(hs, fed)
defer.returnValue(hs)
def register_federation_servlets(hs, resource):
federation_server.register_servlets(
hs,
resource=resource,
authenticator=federation_server.Authenticator(hs),
ratelimiter=FederationRateLimiter(
hs.get_clock(),
window_size=hs.config.federation_rc_window_size,
sleep_limit=hs.config.federation_rc_sleep_limit,
sleep_msec=hs.config.federation_rc_sleep_delay,
reject_limit=hs.config.federation_rc_reject_limit,
concurrent_requests=hs.config.federation_rc_concurrent,
),
)
def get_mock_call_args(pattern_func, mock_func):
""" Return the arguments the mock function was called with interpreted
by the pattern functions argument list.
+3
View File
@@ -118,6 +118,9 @@ commands =
python -m towncrier.check --compare-with=origin/develop
basepython = python3.6
[testenv:check-sampleconfig]
commands = {toxinidir}/scripts-dev/generate_sample_config --check
[testenv:codecov]
skip_install = True
deps =