From b8b52ca09d76e14854535df9a93b25096fcbd36a Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 14:58:51 +0100 Subject: Add logging to try and figure out what is going on with the presence stuff --- synapse/handlers/events.py | 3 +++ synapse/handlers/presence.py | 11 +++++++-- synapse/streams/config.py | 2 +- synapse/util/logutils.py | 53 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py index e08231406d..23bc0b6e28 100644 --- a/synapse/handlers/events.py +++ b/synapse/handlers/events.py @@ -16,6 +16,7 @@ from twisted.internet import defer from synapse.api.events import SynapseEvent +from synapse.util.logutils import log_function from ._base import BaseHandler @@ -44,6 +45,7 @@ class EventStreamHandler(BaseHandler): self.notifier = hs.get_notifier() @defer.inlineCallbacks + @log_function def get_stream(self, auth_user_id, pagin_config, timeout=0): auth_user = self.hs.parse_userid(auth_user_id) @@ -90,6 +92,7 @@ class EventStreamHandler(BaseHandler): # 10 seconds of grace to allow the client to reconnect again # before we think they're gone def _later(): + logger.debug("_later stopped_user_eventstream %s", auth_user) self.distributor.fire( "stopped_user_eventstream", auth_user ) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index c479908f61..1be3845691 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -18,6 +18,8 @@ from twisted.internet import defer from synapse.api.errors import SynapseError, AuthError from synapse.api.constants import PresenceState +from synapse.util.logutils import trace_function, log_function + from ._base import BaseHandler import logging @@ -142,7 +144,7 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def is_presence_visible(self, observer_user, observed_user): defer.returnValue(True) - return + # return # FIXME (erikj): This code path absolutely kills the database. assert(observed_user.is_mine) @@ -188,8 +190,9 @@ class PresenceHandler(BaseHandler): defer.returnValue(state) @defer.inlineCallbacks + @trace_function def set_state(self, target_user, auth_user, state): - return + # return # TODO (erikj): Turn this back on. Why did we end up sending EDUs # everywhere? @@ -245,10 +248,12 @@ class PresenceHandler(BaseHandler): self.push_presence(user, statuscache=statuscache) + @trace_function def started_user_eventstream(self, user): # TODO(paul): Use "last online" state self.set_state(user, user, {"state": PresenceState.ONLINE}) + @trace_function def stopped_user_eventstream(self, user): # TODO(paul): Save current state as "last online" state self.set_state(user, user, {"state": PresenceState.OFFLINE}) @@ -382,6 +387,7 @@ class PresenceHandler(BaseHandler): defer.returnValue(presence) @defer.inlineCallbacks + @trace_function def start_polling_presence(self, user, target_user=None, state=None): logger.debug("Start polling for presence from %s", user) @@ -457,6 +463,7 @@ class PresenceHandler(BaseHandler): content={"poll": [u.to_string() for u in remoteusers]} ) + @trace_function def stop_polling_presence(self, user, target_user=None): logger.debug("Stop polling for presence from %s", user) diff --git a/synapse/streams/config.py b/synapse/streams/config.py index 2434844d80..01bab568ff 100644 --- a/synapse/streams/config.py +++ b/synapse/streams/config.py @@ -81,4 +81,4 @@ class PaginationConfig(object): return ( "" - ) % (self.from_tok, self.to_tok, self.direction, self.limit) + ) % (self.from_token, self.to_token, self.direction, self.limit) diff --git a/synapse/util/logutils.py b/synapse/util/logutils.py index 9270a1790b..86a41ddc8e 100644 --- a/synapse/util/logutils.py +++ b/synapse/util/logutils.py @@ -17,6 +17,8 @@ from inspect import getcallargs import logging +import inspect +import traceback def log_function(f): @@ -63,4 +65,55 @@ def log_function(f): return f(*args, **kwargs) + wrapped.__name__ = func_name + return wrapped + + +def trace_function(f): + func_name = f.__name__ + linenum = f.func_code.co_firstlineno + pathname = f.func_code.co_filename + + def wrapped(*args, **kwargs): + name = f.__module__ + logger = logging.getLogger(name) + level = logging.DEBUG + + s = inspect.currentframe().f_back + + to_print = [ + "\t%s:%s %s. Args: args=%s, kwargs=%s" % ( + pathname, linenum, func_name, args, kwargs + ) + ] + while s: + if True or s.f_globals["__name__"].startswith("synapse"): + filename, lineno, function, _, _ = inspect.getframeinfo(s) + args_string = inspect.formatargvalues(*inspect.getargvalues(s)) + + to_print.append( + "\t%s:%d %s. Args: %s" % ( + filename, lineno, function, args_string + ) + ) + + s = s.f_back + + msg = "\nTraceback for %s:\n" % (func_name,) + "\n".join(to_print) + + record = logging.LogRecord( + name=name, + level=level, + pathname=pathname, + lineno=lineno, + msg=msg, + args=None, + exc_info=None + ) + + logger.handle(record) + + return f(*args, **kwargs) + + wrapped.__name__ = func_name return wrapped -- cgit 1.5.1 From e0ba81344c9f063ed4d9546ce316818b703866a9 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 15:30:42 +0100 Subject: Add more logging. Up the event stream timer to 10s --- synapse/handlers/events.py | 3 ++- synapse/handlers/presence.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py index 23bc0b6e28..f2df2175b6 100644 --- a/synapse/handlers/events.py +++ b/synapse/handlers/events.py @@ -98,8 +98,9 @@ class EventStreamHandler(BaseHandler): ) del self._stop_timer_per_user[auth_user] + logger.debug("Scheduling _later: for %s", auth_user) self._stop_timer_per_user[auth_user] = ( - self.clock.call_later(5, _later) + self.clock.call_later(10, _later) ) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 1be3845691..7741ed3171 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -517,6 +517,7 @@ class PresenceHandler(BaseHandler): ) @defer.inlineCallbacks + @trace_function def push_presence(self, user, statuscache): assert(user.is_mine) @@ -559,6 +560,7 @@ class PresenceHandler(BaseHandler): ) @defer.inlineCallbacks + @trace_function def _send_presence_to_distribution(self, srcuser, localusers=set(), remotedomains=set(), statuscache=None): -- cgit 1.5.1 From eb3094ed315fbf3ee1886eef3d6339f2ea259ba7 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 15:58:38 +0100 Subject: And more logging. --- synapse/handlers/presence.py | 1 + synapse/notifier.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 7741ed3171..a125ea6c10 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -503,6 +503,7 @@ class PresenceHandler(BaseHandler): if not self._local_pushmap[localpart]: del self._local_pushmap[localpart] + @trace_function def _stop_polling_remote(self, user, domain, remoteusers): for u in remoteusers: self._remote_recvmap[u].remove(user) diff --git a/synapse/notifier.py b/synapse/notifier.py index 3d3fcdabdb..d222644cb6 100644 --- a/synapse/notifier.py +++ b/synapse/notifier.py @@ -15,7 +15,7 @@ from twisted.internet import defer, reactor -from synapse.util.logutils import log_function +from synapse.util.logutils import log_function, trace_function import logging @@ -119,6 +119,7 @@ class Notifier(object): ) @defer.inlineCallbacks + @trace_function def on_new_user_event(self, users=[], rooms=[]): """ Used to inform listeners that something has happend presence/user event wise. -- cgit 1.5.1 From c46c8061261c365934d378e52bf721cf400a3303 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Thu, 28 Aug 2014 10:50:39 +0100 Subject: Re-enable presence, un-skip presence tests --- synapse/handlers/presence.py | 8 -------- tests/handlers/test_presence.py | 6 ------ tests/handlers/test_presencelike.py | 3 --- tests/rest/test_presence.py | 2 -- 4 files changed, 19 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index c479908f61..1b3cdcc38c 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -141,10 +141,6 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def is_presence_visible(self, observer_user, observed_user): - defer.returnValue(True) - return - # FIXME (erikj): This code path absolutely kills the database. - assert(observed_user.is_mine) if observer_user == observed_user: @@ -189,10 +185,6 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def set_state(self, target_user, auth_user, state): - return - # TODO (erikj): Turn this back on. Why did we end up sending EDUs - # everywhere? - if not target_user.is_mine: raise SynapseError(400, "User is not hosted on this Home Server") diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 824ed07169..13217d456b 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -192,7 +192,6 @@ class PresenceStateTestCase(unittest.TestCase): ), SynapseError ) - test_get_disallowed_state.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_set_my_state(self): @@ -217,7 +216,6 @@ class PresenceStateTestCase(unittest.TestCase): state={"state": OFFLINE}) self.mock_stop.assert_called_with(self.u_apple) - test_set_my_state.skip = "Presence polling is disabled" class PresenceInvitesTestCase(unittest.TestCase): @@ -657,7 +655,6 @@ class PresencePushTestCase(unittest.TestCase): observed_user=self.u_banana, statuscache=ANY), # self-reflection ]) # and no others... - test_push_local.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_push_remote(self): @@ -709,7 +706,6 @@ class PresencePushTestCase(unittest.TestCase): ) yield put_json.await_calls() - test_push_remote.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_recv_remote(self): @@ -1002,7 +998,6 @@ class PresencePollingTestCase(unittest.TestCase): self.assertFalse("banana" in self.handler._local_pushmap) self.assertFalse("clementine" in self.handler._local_pushmap) - test_push_local.skip = "Presence polling is disabled" @defer.inlineCallbacks @@ -1052,7 +1047,6 @@ class PresencePollingTestCase(unittest.TestCase): put_json.await_calls() self.assertFalse(self.u_potato in self.handler._remote_recvmap) - test_remote_poll_send.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_remote_poll_receive(self): diff --git a/tests/handlers/test_presencelike.py b/tests/handlers/test_presencelike.py index 1b106fc2b3..da06a06647 100644 --- a/tests/handlers/test_presencelike.py +++ b/tests/handlers/test_presencelike.py @@ -139,7 +139,6 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): mocked_set.assert_called_with("apple", {"state": UNAVAILABLE, "status_msg": "Away"}) - test_set_my_state.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_push_local(self): @@ -214,7 +213,6 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): "displayname": "I am an Apple", "avatar_url": "http://foo", }, statuscache.state) - test_push_local.skip = "Presence polling is disabled" @defer.inlineCallbacks @@ -246,7 +244,6 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): ], }, ) - test_push_remote.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_recv_remote(self): diff --git a/tests/rest/test_presence.py b/tests/rest/test_presence.py index e15ee38741..7f7347dcf9 100644 --- a/tests/rest/test_presence.py +++ b/tests/rest/test_presence.py @@ -114,7 +114,6 @@ class PresenceStateTestCase(unittest.TestCase): self.assertEquals(200, code) mocked_set.assert_called_with("apple", {"state": UNAVAILABLE, "status_msg": "Away"}) - test_set_my_status.skip = "Presence polling is disabled" class PresenceListTestCase(unittest.TestCase): @@ -318,4 +317,3 @@ class PresenceEventStreamTestCase(unittest.TestCase): "mtime_age": 0, }}, ]}, response) - test_shortpoll.skip = "Presence polling is disabled" -- cgit 1.5.1 From b1da3fa0a78ab7c6af8c274b6c2beafb7a5a751a Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Thu, 28 Aug 2014 16:19:16 +0100 Subject: Avoid AlreadyCalledError from EDU sending failures --- synapse/federation/replication.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py index 38ae360bcd..7868575a2e 100644 --- a/synapse/federation/replication.py +++ b/synapse/federation/replication.py @@ -541,7 +541,8 @@ class _TransactionQueue(object): ) def eb(failure): - deferred.errback(failure) + if not deferred.called: + deferred.errback(failure) self._attempt_new_transaction(destination).addErrback(eb) return deferred -- cgit 1.5.1 From efc5f3440dc033d9d1713eaa7159b75689704d6c Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Thu, 28 Aug 2014 16:43:55 +0100 Subject: Only send presence "poll"/"unpoll" EDUs when changing from/to zero remotes --- synapse/handlers/presence.py | 16 ++++++++++++++-- tests/handlers/test_presence.py | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 8 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 1b3cdcc38c..bef1508892 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -437,16 +437,22 @@ class PresenceHandler(BaseHandler): ) def _start_polling_remote(self, user, domain, remoteusers): + to_poll = set() + for u in remoteusers: if u not in self._remote_recvmap: self._remote_recvmap[u] = set() + to_poll.add(u) self._remote_recvmap[u].add(user) + if not to_poll: + return defer.succeed(None) + return self.federation.send_edu( destination=domain, edu_type="m.presence", - content={"poll": [u.to_string() for u in remoteusers]} + content={"poll": [u.to_string() for u in to_poll]} ) def stop_polling_presence(self, user, target_user=None): @@ -489,16 +495,22 @@ class PresenceHandler(BaseHandler): del self._local_pushmap[localpart] def _stop_polling_remote(self, user, domain, remoteusers): + to_unpoll = set() + for u in remoteusers: self._remote_recvmap[u].remove(user) if not self._remote_recvmap[u]: del self._remote_recvmap[u] + to_unpoll.add(u) + + if not to_unpoll: + return defer.succeed(None) return self.federation.send_edu( destination=domain, edu_type="m.presence", - content={"unpoll": [u.to_string() for u in remoteusers]} + content={"unpoll": [u.to_string() for u in to_unpoll]} ) @defer.inlineCallbacks diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 13217d456b..8d094fd1f9 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -15,7 +15,7 @@ from twisted.trial import unittest -from twisted.internet import defer +from twisted.internet import defer, reactor from mock import Mock, call, ANY import logging @@ -853,6 +853,7 @@ class PresencePollingTestCase(unittest.TestCase): 'apple': [ "@banana:test", "@clementine:test" ], 'banana': [ "@apple:test" ], 'clementine': [ "@apple:test", "@potato:remote" ], + 'fig': [ "@potato:remote" ], } @@ -902,9 +903,10 @@ class PresencePollingTestCase(unittest.TestCase): # Mocked database state # Local users always start offline self.current_user_state = { - "apple": OFFLINE, - "banana": OFFLINE, - "clementine": OFFLINE, + "apple": OFFLINE, + "banana": OFFLINE, + "clementine": OFFLINE, + "fig": OFFLINE, } def get_presence_state(user_localpart): @@ -934,6 +936,7 @@ class PresencePollingTestCase(unittest.TestCase): self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") + self.u_fig = hs.parse_userid("@fig:test") # Remote users self.u_potato = hs.parse_userid("@potato:remote") @@ -1023,10 +1026,32 @@ class PresencePollingTestCase(unittest.TestCase): yield put_json.await_calls() # Gut-wrenching tests - self.assertTrue(self.u_potato in self.handler._remote_recvmap) + self.assertTrue(self.u_potato in self.handler._remote_recvmap, + msg="expected potato to be in _remote_recvmap" + ) self.assertTrue(self.u_clementine in self.handler._remote_recvmap[self.u_potato]) + # fig goes online; shouldn't send a second poll + yield self.handler.set_state( + target_user=self.u_fig, auth_user=self.u_fig, + state={"state": ONLINE} + ) + + reactor.iterate(delay=0) + + put_json.assert_had_no_calls() + + # fig goes offline + yield self.handler.set_state( + target_user=self.u_fig, auth_user=self.u_fig, + state={"state": OFFLINE} + ) + + reactor.iterate(delay=0) + + put_json.assert_had_no_calls() + put_json.expect_call_and_return( call("remote", path="/matrix/federation/v1/send/1000001/", @@ -1046,7 +1071,9 @@ class PresencePollingTestCase(unittest.TestCase): put_json.await_calls() - self.assertFalse(self.u_potato in self.handler._remote_recvmap) + self.assertFalse(self.u_potato in self.handler._remote_recvmap, + msg="expected potato not to be in _remote_recvmap" + ) @defer.inlineCallbacks def test_remote_poll_receive(self): -- cgit 1.5.1 From a8d318cf8276811222e1df187c5efeadb848f9ee Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 16:44:09 +0100 Subject: Up timeout to 10 minutes --- synapse/handlers/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py index f2df2175b6..f814e7165b 100644 --- a/synapse/handlers/events.py +++ b/synapse/handlers/events.py @@ -100,7 +100,7 @@ class EventStreamHandler(BaseHandler): logger.debug("Scheduling _later: for %s", auth_user) self._stop_timer_per_user[auth_user] = ( - self.clock.call_later(10, _later) + self.clock.call_later(600, _later) ) -- cgit 1.5.1 From 2c7c12bc6e13ea36ea56ede8292f5baa1eb12643 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Thu, 28 Aug 2014 17:39:34 +0100 Subject: Initial room event stream token must be s0, not s1, or everyone will miss the very first room event --- synapse/storage/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py index 4f42afc015..4945b0796e 100644 --- a/synapse/storage/stream.py +++ b/synapse/storage/stream.py @@ -294,7 +294,7 @@ class StreamStore(SQLBaseStore): logger.debug("get_room_events_max_id: %s", res) if not res or not res[0] or not res[0]["m"]: - return "s1" + return "s0" key = res[0]["m"] return "s%d" % (key,) -- cgit 1.5.1 From 1b7686329e96f1bde2a14cc35c98c2761e224c6e Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 17:43:15 +0100 Subject: Don't query the rooms members table so much by using the new notifier api that allows you to specify room_ids to notify. --- synapse/handlers/events.py | 2 +- synapse/handlers/presence.py | 106 ++++++++++++++++++------------------------- 2 files changed, 46 insertions(+), 62 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py index f814e7165b..980a169b25 100644 --- a/synapse/handlers/events.py +++ b/synapse/handlers/events.py @@ -100,7 +100,7 @@ class EventStreamHandler(BaseHandler): logger.debug("Scheduling _later: for %s", auth_user) self._stop_timer_per_user[auth_user] = ( - self.clock.call_later(600, _later) + self.clock.call_later(30, _later) ) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 414a5b7bd5..677c1b2d81 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -260,24 +260,29 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def user_joined_room(self, user, room_id): - localusers = set() - remotedomains = set() - - rm_handler = self.homeserver.get_handlers().room_member_handler - yield rm_handler.fetch_room_distributions_into(room_id, - localusers=localusers, remotedomains=remotedomains, - ignore_user=user) + statuscache = self._get_or_make_usercache(user) if user.is_mine: - yield self._send_presence_to_distribution(srcuser=user, - localusers=localusers, remotedomains=remotedomains, - statuscache=self._get_or_offline_usercache(user), + remote_domains = set( + (yield self.store.get_joined_hosts_for_room(room_id)) ) - for srcuser in localusers: - yield self._send_presence(srcuser=srcuser, destuser=user, - statuscache=self._get_or_offline_usercache(srcuser), - ) + if not remote_domains: + defer.returnValue(None) + + deferreds = [] + for domain in remote_domains: + logger.debug(" | push to remote domain %s", domain) + deferreds.append(self._push_presence_remote(user, domain, + state=statuscache.get_state()) + ) + + + self.push_update_to_clients_2( + observed_user=user, + room_ids=[room_id], + statuscache=self._get_or_offline_usercache(user), + ) @defer.inlineCallbacks def send_invite(self, observer_user, observed_user): @@ -546,53 +551,28 @@ class PresenceHandler(BaseHandler): rm_handler = self.homeserver.get_handlers().room_member_handler room_ids = yield rm_handler.get_rooms_for_user(user) + remote_domains = set() for room_id in room_ids: - yield rm_handler.fetch_room_distributions_into( - room_id, localusers=localusers, remotedomains=remotedomains, - ignore_user=user, + remote_domains.update( + (yield self.store.get_joined_hosts_for_room(room_id)) ) if not localusers and not remotedomains: defer.returnValue(None) - yield self._send_presence_to_distribution(user, - localusers=localusers, remotedomains=remotedomains, - statuscache=statuscache - ) - - def _send_presence(self, srcuser, destuser, statuscache): - if destuser.is_mine: - self.push_update_to_clients( - observer_user=destuser, - observed_user=srcuser, - statuscache=statuscache) - return defer.succeed(None) - else: - return self._push_presence_remote(srcuser, destuser.domain, - state=statuscache.get_state() - ) - - @defer.inlineCallbacks - @trace_function - def _send_presence_to_distribution(self, srcuser, localusers=set(), - remotedomains=set(), statuscache=None): - - for u in localusers: - logger.debug(" | push to local user %s", u) - self.push_update_to_clients( - observer_user=u, - observed_user=srcuser, - statuscache=statuscache, - ) - deferreds = [] for domain in remotedomains: logger.debug(" | push to remote domain %s", domain) - deferreds.append(self._push_presence_remote(srcuser, domain, + deferreds.append(self._push_presence_remote(user, domain, state=statuscache.get_state()) ) - yield defer.DeferredList(deferreds) + self.push_update_to_clients_2( + observed_user=user, + users_to_push=localusers, + room_ids=room_ids, + statuscache=statuscache, + ) @defer.inlineCallbacks def _push_presence_remote(self, user, destination, state=None): @@ -633,12 +613,7 @@ class PresenceHandler(BaseHandler): rm_handler = self.homeserver.get_handlers().room_member_handler room_ids = yield rm_handler.get_rooms_for_user(user) - for room_id in room_ids: - yield rm_handler.fetch_room_distributions_into( - room_id, localusers=observers, ignore_user=user - ) - - if not observers: + if not observers and not room_ids: break state = dict(push) @@ -654,12 +629,12 @@ class PresenceHandler(BaseHandler): self._user_cachemap_latest_serial += 1 statuscache.update(state, serial=self._user_cachemap_latest_serial) - for observer_user in observers: - self.push_update_to_clients( - observer_user=observer_user, - observed_user=user, - statuscache=statuscache, - ) + self.push_update_to_clients_2( + observed_user=user, + users_to_push=observers, + room_ids=room_ids, + statuscache=statuscache, + ) if state["state"] == PresenceState.OFFLINE: del self._user_cachemap[user] @@ -701,6 +676,15 @@ class PresenceHandler(BaseHandler): [observer_user], ) + def push_update_to_clients_2(self, observed_user, users_to_push=[], + room_ids=[], statuscache=None): + statuscache.make_event(user=observed_user, clock=self.clock) + + self.notifier.on_new_user_event( + users_to_push, + room_ids, + ) + class UserPresenceCache(object): """Store an observed user's state and status message. -- cgit 1.5.1 From 722c19d0338cf5476c8a4f437a39422a72ebb263 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Thu, 28 Aug 2014 18:32:39 +0100 Subject: Fix FederationHandler to event.origin --- synapse/handlers/federation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'synapse') diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py index 1cc820fb5b..eac110419c 100644 --- a/synapse/handlers/federation.py +++ b/synapse/handlers/federation.py @@ -146,7 +146,7 @@ class FederationHandler(BaseHandler): # Huh, let's try and get the current state try: yield self.replication_layer.get_state_for_context( - origin, event.room_id + event.origin, event.room_id ) hosts = yield self.store.get_joined_hosts_for_room( -- cgit 1.5.1 From aaf4fd98eea39728a4e3311ac0001fc7a1390b18 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 18:43:03 +0100 Subject: Only poll remote users if they are in our presence list, rather than in a common room --- synapse/handlers/presence.py | 119 ++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 53 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 677c1b2d81..223ddfc650 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -263,26 +263,17 @@ class PresenceHandler(BaseHandler): statuscache = self._get_or_make_usercache(user) if user.is_mine: - remote_domains = set( - (yield self.store.get_joined_hosts_for_room(room_id)) + self.push_update_to_local_and_remote( + observed_user=user, + room_ids=[room_id], + statuscache=self._get_or_offline_usercache(user), + ) + else: + self.push_update_to_clients_2( + observed_user=user, + room_ids=[room_id], + statuscache=self._get_or_offline_usercache(user), ) - - if not remote_domains: - defer.returnValue(None) - - deferreds = [] - for domain in remote_domains: - logger.debug(" | push to remote domain %s", domain) - deferreds.append(self._push_presence_remote(user, domain, - state=statuscache.get_state()) - ) - - - self.push_update_to_clients_2( - observed_user=user, - room_ids=[room_id], - statuscache=self._get_or_offline_usercache(user), - ) @defer.inlineCallbacks def send_invite(self, observer_user, observed_user): @@ -398,6 +389,7 @@ class PresenceHandler(BaseHandler): if target_user: target_users = set([target_user]) + room_ids = [] else: presence = yield self.store.get_presence_list( user.localpart, accepted=True @@ -411,23 +403,24 @@ class PresenceHandler(BaseHandler): rm_handler = self.homeserver.get_handlers().room_member_handler room_ids = yield rm_handler.get_rooms_for_user(user) - for room_id in room_ids: - for member in (yield rm_handler.get_room_members(room_id)): - target_users.add(member) - if state is None: state = yield self.store.get_presence_state(user.localpart) - localusers, remoteusers = partitionbool( - target_users, - lambda u: u.is_mine + _, remote_domains = yield self.push_update_to_local_and_remote( + observed_user=user, + users_to_push=target_users, + room_ids=room_ids, + statuscache=self._get_or_make_usercache(user), ) - for target_user in localusers: - self._start_polling_local(user, target_user) + for target_user in target_users: + if target_user.is_mine: + self._start_polling_local(user, target_user) deferreds = [] - remoteusers_by_domain = partition(remoteusers, lambda u: u.domain) + remote_users = [u for u in target_users if not u.is_mine] + remoteusers_by_domain = partition(remote_users, lambda u: u.domain) + # Only poll for people in our get_presence_list for domain in remoteusers_by_domain: remoteusers = remoteusers_by_domain[domain] @@ -449,12 +442,6 @@ class PresenceHandler(BaseHandler): self._local_pushmap[target_localpart].add(user) - self.push_update_to_clients( - observer_user=user, - observed_user=target_user, - statuscache=self._get_or_offline_usercache(target_user), - ) - def _start_polling_remote(self, user, domain, remoteusers): to_poll = set() @@ -551,21 +538,15 @@ class PresenceHandler(BaseHandler): rm_handler = self.homeserver.get_handlers().room_member_handler room_ids = yield rm_handler.get_rooms_for_user(user) - remote_domains = set() - for room_id in room_ids: - remote_domains.update( - (yield self.store.get_joined_hosts_for_room(room_id)) - ) - - if not localusers and not remotedomains: + if not localusers and not room_ids: defer.returnValue(None) - deferreds = [] - for domain in remotedomains: - logger.debug(" | push to remote domain %s", domain) - deferreds.append(self._push_presence_remote(user, domain, - state=statuscache.get_state()) - ) + yield self.push_update_to_local_and_remote( + observed_user=user, + users_to_push=localusers, + room_ids=room_ids, + statuscache=statuscache, + ) self.push_update_to_clients_2( observed_user=user, @@ -668,14 +649,46 @@ class PresenceHandler(BaseHandler): yield defer.DeferredList(deferreds) - def push_update_to_clients(self, observer_user, observed_user, - statuscache): - statuscache.make_event(user=observed_user, clock=self.clock) + @defer.inlineCallbacks + def push_update_to_local_and_remote(self, observed_user, + users_to_push=[], room_ids=[], + statuscache=None): - self.notifier.on_new_user_event( - [observer_user], + localusers, remoteusers = partitionbool( + users_to_push, + lambda u: u.is_mine + ) + + localusers = set(localusers) + + self.push_update_to_clients_2( + observed_user, + users_to_push=localusers, + room_ids=room_ids, + statuscache=statuscache, ) + remote_domains = set([r.domain for r in remoteusers]) + for room_id in room_ids: + remote_domains.update( + (yield self.store.get_joined_hosts_for_room(room_id)) + ) + + remote_domains.discard(self.hs.hostname) + + deferreds = [] + for domain in remote_domains: + logger.debug(" | push to remote domain %s", domain) + deferreds.append( + self._push_presence_remote( + observed_user, domain, state=statuscache.get_state() + ) + ) + + yield defer.DeferredList(deferreds) + + defer.returnValue((localusers, remote_domains)) + def push_update_to_clients_2(self, observed_user, users_to_push=[], room_ids=[], statuscache=None): statuscache.make_event(user=observed_user, clock=self.clock) -- cgit 1.5.1 From 8113eb7c791890869fb8818da3be8e7ad8e37ea8 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 28 Aug 2014 18:45:00 +0100 Subject: Turn of trace_function logging --- synapse/handlers/presence.py | 16 ++++++++-------- synapse/notifier.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 223ddfc650..174a92d81b 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -18,7 +18,7 @@ from twisted.internet import defer from synapse.api.errors import SynapseError, AuthError from synapse.api.constants import PresenceState -from synapse.util.logutils import trace_function, log_function +from synapse.util.logutils import log_function from ._base import BaseHandler @@ -190,7 +190,7 @@ class PresenceHandler(BaseHandler): defer.returnValue(state) @defer.inlineCallbacks - @trace_function + @log_function def set_state(self, target_user, auth_user, state): # return # TODO (erikj): Turn this back on. Why did we end up sending EDUs @@ -248,12 +248,12 @@ class PresenceHandler(BaseHandler): self.push_presence(user, statuscache=statuscache) - @trace_function + @log_function def started_user_eventstream(self, user): # TODO(paul): Use "last online" state self.set_state(user, user, {"state": PresenceState.ONLINE}) - @trace_function + @log_function def stopped_user_eventstream(self, user): # TODO(paul): Save current state as "last online" state self.set_state(user, user, {"state": PresenceState.OFFLINE}) @@ -383,7 +383,7 @@ class PresenceHandler(BaseHandler): defer.returnValue(presence) @defer.inlineCallbacks - @trace_function + @log_function def start_polling_presence(self, user, target_user=None, state=None): logger.debug("Start polling for presence from %s", user) @@ -461,7 +461,7 @@ class PresenceHandler(BaseHandler): content={"poll": [u.to_string() for u in to_poll]} ) - @trace_function + @log_function def stop_polling_presence(self, user, target_user=None): logger.debug("Stop polling for presence from %s", user) @@ -501,7 +501,7 @@ class PresenceHandler(BaseHandler): if not self._local_pushmap[localpart]: del self._local_pushmap[localpart] - @trace_function + @log_function def _stop_polling_remote(self, user, domain, remoteusers): to_unpoll = set() @@ -522,7 +522,7 @@ class PresenceHandler(BaseHandler): ) @defer.inlineCallbacks - @trace_function + @log_function def push_presence(self, user, statuscache): assert(user.is_mine) diff --git a/synapse/notifier.py b/synapse/notifier.py index d222644cb6..b6d5ec4820 100644 --- a/synapse/notifier.py +++ b/synapse/notifier.py @@ -15,7 +15,7 @@ from twisted.internet import defer, reactor -from synapse.util.logutils import log_function, trace_function +from synapse.util.logutils import log_function import logging @@ -119,7 +119,7 @@ class Notifier(object): ) @defer.inlineCallbacks - @trace_function + @log_function def on_new_user_event(self, users=[], rooms=[]): """ Used to inform listeners that something has happend presence/user event wise. -- cgit 1.5.1 From 5dd38d579b45be26c27b1a109a1d413c3b0425a2 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 29 Aug 2014 12:08:33 +0100 Subject: Fix a couple of bugs in presence handler related to pushing updatesto the correct user. Fix presence tests. --- synapse/federation/replication.py | 2 + synapse/handlers/presence.py | 59 ++++++++++----- tests/handlers/test_presence.py | 138 +++++++++++++++++++++++------------- tests/handlers/test_presencelike.py | 54 ++++++++++---- tests/utils.py | 37 ++++++++-- 5 files changed, 200 insertions(+), 90 deletions(-) (limited to 'synapse') diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py index 7868575a2e..cadf574b3b 100644 --- a/synapse/federation/replication.py +++ b/synapse/federation/replication.py @@ -543,6 +543,8 @@ class _TransactionQueue(object): def eb(failure): if not deferred.called: deferred.errback(failure) + else: + logger.exception("Failed to send edu", failure) self._attempt_new_transaction(destination).addErrback(eb) return deferred diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 174a92d81b..7731de85c0 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -260,7 +260,6 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def user_joined_room(self, user, room_id): - statuscache = self._get_or_make_usercache(user) if user.is_mine: self.push_update_to_local_and_remote( @@ -268,13 +267,25 @@ class PresenceHandler(BaseHandler): room_ids=[room_id], statuscache=self._get_or_offline_usercache(user), ) + else: - self.push_update_to_clients_2( + self.push_update_to_clients( observed_user=user, room_ids=[room_id], statuscache=self._get_or_offline_usercache(user), ) + # We also want to tell them about current presence of people. + rm_handler = self.homeserver.get_handlers().room_member_handler + curr_users = yield rm_handler.get_room_members(room_id) + + for local_user in [c for c in curr_users if c.is_mine]: + self.push_update_to_local_and_remote( + observed_user=local_user, + users_to_push=[user], + statuscache=self._get_or_offline_usercache(local_user), + ) + @defer.inlineCallbacks def send_invite(self, observer_user, observed_user): if not observer_user.is_mine: @@ -405,8 +416,13 @@ class PresenceHandler(BaseHandler): if state is None: state = yield self.store.get_presence_state(user.localpart) + else: +# statuscache = self._get_or_make_usercache(user) +# self._user_cachemap_latest_serial += 1 +# statuscache.update(state, self._user_cachemap_latest_serial) + pass - _, remote_domains = yield self.push_update_to_local_and_remote( + yield self.push_update_to_local_and_remote( observed_user=user, users_to_push=target_users, room_ids=room_ids, @@ -417,6 +433,14 @@ class PresenceHandler(BaseHandler): if target_user.is_mine: self._start_polling_local(user, target_user) + # We want to tell the person that just came online + # presence state of people they are interested in? + self.push_update_to_clients( + observed_user=target_user, + users_to_push=[user], + statuscache=self._get_or_offline_usercache(target_user), + ) + deferreds = [] remote_users = [u for u in target_users if not u.is_mine] remoteusers_by_domain = partition(remote_users, lambda u: u.domain) @@ -544,13 +568,7 @@ class PresenceHandler(BaseHandler): yield self.push_update_to_local_and_remote( observed_user=user, users_to_push=localusers, - room_ids=room_ids, - statuscache=statuscache, - ) - - self.push_update_to_clients_2( - observed_user=user, - users_to_push=localusers, + remote_domains=remotedomains, room_ids=room_ids, statuscache=statuscache, ) @@ -570,12 +588,17 @@ class PresenceHandler(BaseHandler): self.clock.time_msec() - state.pop("mtime") ) + user_state = { + "user_id": user.to_string(), + } + user_state.update(**state) + yield self.federation.send_edu( destination=destination, edu_type="m.presence", content={ "push": [ - dict(user_id=user.to_string(), **state), + user_state, ], } ) @@ -610,7 +633,7 @@ class PresenceHandler(BaseHandler): self._user_cachemap_latest_serial += 1 statuscache.update(state, serial=self._user_cachemap_latest_serial) - self.push_update_to_clients_2( + self.push_update_to_clients( observed_user=user, users_to_push=observers, room_ids=room_ids, @@ -652,6 +675,7 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def push_update_to_local_and_remote(self, observed_user, users_to_push=[], room_ids=[], + remote_domains=[], statuscache=None): localusers, remoteusers = partitionbool( @@ -661,14 +685,15 @@ class PresenceHandler(BaseHandler): localusers = set(localusers) - self.push_update_to_clients_2( - observed_user, + self.push_update_to_clients( + observed_user=observed_user, users_to_push=localusers, room_ids=room_ids, statuscache=statuscache, ) - remote_domains = set([r.domain for r in remoteusers]) + remote_domains = set(remote_domains) + remote_domains |= set([r.domain for r in remoteusers]) for room_id in room_ids: remote_domains.update( (yield self.store.get_joined_hosts_for_room(room_id)) @@ -689,10 +714,8 @@ class PresenceHandler(BaseHandler): defer.returnValue((localusers, remote_domains)) - def push_update_to_clients_2(self, observed_user, users_to_push=[], + def push_update_to_clients(self, observed_user, users_to_push=[], room_ids=[], statuscache=None): - statuscache.make_event(user=observed_user, clock=self.clock) - self.notifier.on_new_user_event( users_to_push, room_ids, diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 8d094fd1f9..fcd7a784cd 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -193,6 +193,8 @@ class PresenceStateTestCase(unittest.TestCase): SynapseError ) + test_get_disallowed_state.skip = "Presence permissions are disabled" + @defer.inlineCallbacks def test_set_my_state(self): mocked_set = self.datastore.set_presence_state @@ -497,6 +499,7 @@ class PresencePushTestCase(unittest.TestCase): db_pool=None, datastore=Mock(spec=[ "set_presence_state", + "get_joined_hosts_for_room", # Bits that Federation needs "prep_send_transaction", @@ -511,8 +514,12 @@ class PresencePushTestCase(unittest.TestCase): ) hs.handlers = JustPresenceHandlers(hs) + def update(*args,**kwargs): + # print "mock_update_client: Args=%s, kwargs=%s" %(args, kwargs,) + return defer.succeed(None) + self.mock_update_client = Mock() - self.mock_update_client.return_value = defer.succeed(None) + self.mock_update_client.side_effect = update self.datastore = hs.get_datastore() @@ -546,6 +553,14 @@ class PresencePushTestCase(unittest.TestCase): return defer.succeed([]) self.room_member_handler.get_room_members = get_room_members + def get_room_hosts(room_id): + if room_id == "a-room": + hosts = set([u.domain for u in self.room_members]) + return defer.succeed(hosts) + else: + return defer.succeed([]) + self.datastore.get_joined_hosts_for_room = get_room_hosts + @defer.inlineCallbacks def fetch_room_distributions_into(room_id, localusers=None, remotedomains=None, ignore_user=None): @@ -611,18 +626,10 @@ class PresencePushTestCase(unittest.TestCase): {"state": ONLINE}) self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_apple, + call(users_to_push=set([self.u_apple, self.u_banana, self.u_clementine]), + room_ids=["a-room"], observed_user=self.u_apple, statuscache=ANY), # self-reflection - call(observer_user=self.u_banana, - observed_user=self.u_apple, - statuscache=ANY), - call(observer_user=self.u_clementine, - observed_user=self.u_apple, - statuscache=ANY), - call(observer_user=self.u_elderberry, - observed_user=self.u_apple, - statuscache=ANY), ], any_order=True) self.mock_update_client.reset_mock() @@ -651,7 +658,8 @@ class PresencePushTestCase(unittest.TestCase): ], presence) self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_banana, + call(users_to_push=set([self.u_banana]), + room_ids=[], observed_user=self.u_banana, statuscache=ANY), # self-reflection ]) # and no others... @@ -659,21 +667,21 @@ class PresencePushTestCase(unittest.TestCase): @defer.inlineCallbacks def test_push_remote(self): put_json = self.mock_http_client.put_json - put_json.expect_call_and_return( - call("remote", - path=ANY, # Can't guarantee which txn ID will be which - data=_expect_edu("remote", "m.presence", - content={ - "push": [ - {"user_id": "@apple:test", - "state": "online", - "mtime_age": 0}, - ], - } - ) - ), - defer.succeed((200, "OK")) - ) +# put_json.expect_call_and_return( +# call("remote", +# path=ANY, # Can't guarantee which txn ID will be which +# data=_expect_edu("remote", "m.presence", +# content={ +# "push": [ +# {"user_id": "@apple:test", +# "state": "online", +# "mtime_age": 0}, +# ], +# } +# ) +# ), +# defer.succeed((200, "OK")) +# ) put_json.expect_call_and_return( call("farm", path=ANY, # Can't guarantee which txn ID will be which @@ -681,7 +689,7 @@ class PresencePushTestCase(unittest.TestCase): content={ "push": [ {"user_id": "@apple:test", - "state": "online", + "state": u"online", "mtime_age": 0}, ], } @@ -730,10 +738,8 @@ class PresencePushTestCase(unittest.TestCase): ) self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_apple, - observed_user=self.u_potato, - statuscache=ANY), - call(observer_user=self.u_banana, + call(users_to_push=set([self.u_apple]), + room_ids=["a-room"], observed_user=self.u_potato, statuscache=ANY), ], any_order=True) @@ -753,19 +759,17 @@ class PresencePushTestCase(unittest.TestCase): ) self.mock_update_client.assert_has_calls([ - # Apple and Elderberry see each other - call(observer_user=self.u_apple, + call(room_ids=["a-room"], observed_user=self.u_elderberry, + users_to_push=set(), statuscache=ANY), - call(observer_user=self.u_elderberry, + call(users_to_push=set([self.u_elderberry]), observed_user=self.u_apple, + room_ids=[], statuscache=ANY), - # Banana and Elderberry see each other - call(observer_user=self.u_banana, - observed_user=self.u_elderberry, - statuscache=ANY), - call(observer_user=self.u_elderberry, + call(users_to_push=set([self.u_elderberry]), observed_user=self.u_banana, + room_ids=[], statuscache=ANY), ], any_order=True) @@ -887,7 +891,12 @@ class PresencePollingTestCase(unittest.TestCase): self.datastore.get_received_txn_response = get_received_txn_response self.mock_update_client = Mock() - self.mock_update_client.return_value = defer.succeed(None) + + def update(*args,**kwargs): + # print "mock_update_client: Args=%s, kwargs=%s" %(args, kwargs,) + return defer.succeed(None) + + self.mock_update_client.side_effect = update self.handler = hs.get_handlers().presence_handler self.handler.push_update_to_clients = self.mock_update_client @@ -951,10 +960,10 @@ class PresencePollingTestCase(unittest.TestCase): # apple should see both banana and clementine currently offline self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_apple, + call(users_to_push=[self.u_apple], observed_user=self.u_banana, statuscache=ANY), - call(observer_user=self.u_apple, + call(users_to_push=[self.u_apple], observed_user=self.u_clementine, statuscache=ANY), ], any_order=True) @@ -974,10 +983,11 @@ class PresencePollingTestCase(unittest.TestCase): # apple and banana should now both see each other online self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_apple, + call(users_to_push=set([self.u_apple]), observed_user=self.u_banana, + room_ids=[], statuscache=ANY), - call(observer_user=self.u_banana, + call(users_to_push=[self.u_banana], observed_user=self.u_apple, statuscache=ANY), ], any_order=True) @@ -994,8 +1004,9 @@ class PresencePollingTestCase(unittest.TestCase): # banana should now be told apple is offline self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_banana, + call(users_to_push=set([self.u_banana, self.u_apple]), observed_user=self.u_apple, + room_ids=[], statuscache=ANY), ], any_order=True) @@ -1008,7 +1019,7 @@ class PresencePollingTestCase(unittest.TestCase): put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call("remote", - path="/matrix/federation/v1/send/1000000/", + path=ANY, data=_expect_edu("remote", "m.presence", content={ "poll": [ "@potato:remote" ], @@ -1018,6 +1029,18 @@ class PresencePollingTestCase(unittest.TestCase): defer.succeed((200, "OK")) ) + put_json.expect_call_and_return( + call("remote", + path=ANY, + data=_expect_edu("remote", "m.presence", + content={ + "push": [ {"user_id": "@clementine:test" }], + }, + ), + ), + defer.succeed((200, "OK")) + ) + # clementine goes online yield self.handler.set_state( target_user=self.u_clementine, auth_user=self.u_clementine, @@ -1032,15 +1055,28 @@ class PresencePollingTestCase(unittest.TestCase): self.assertTrue(self.u_clementine in self.handler._remote_recvmap[self.u_potato]) + + put_json.expect_call_and_return( + call("remote", + path=ANY, + data=_expect_edu("remote", "m.presence", + content={ + "push": [ {"user_id": "@fig:test" }], + }, + ), + ), + defer.succeed((200, "OK")) + ) + # fig goes online; shouldn't send a second poll yield self.handler.set_state( target_user=self.u_fig, auth_user=self.u_fig, state={"state": ONLINE} ) - reactor.iterate(delay=0) + # reactor.iterate(delay=0) - put_json.assert_had_no_calls() + yield put_json.await_calls() # fig goes offline yield self.handler.set_state( @@ -1054,7 +1090,7 @@ class PresencePollingTestCase(unittest.TestCase): put_json.expect_call_and_return( call("remote", - path="/matrix/federation/v1/send/1000001/", + path=ANY, data=_expect_edu("remote", "m.presence", content={ "unpoll": [ "@potato:remote" ], @@ -1069,7 +1105,7 @@ class PresencePollingTestCase(unittest.TestCase): target_user=self.u_clementine, auth_user=self.u_clementine, state={"state": OFFLINE}) - put_json.await_calls() + yield put_json.await_calls() self.assertFalse(self.u_potato in self.handler._remote_recvmap, msg="expected potato not to be in _remote_recvmap" diff --git a/tests/handlers/test_presencelike.py b/tests/handlers/test_presencelike.py index da06a06647..e81d7ce101 100644 --- a/tests/handlers/test_presencelike.py +++ b/tests/handlers/test_presencelike.py @@ -81,7 +81,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): self.replication = hs.get_replication_layer() self.replication.send_edu = Mock() - self.replication.send_edu.return_value = defer.succeed((200, "OK")) + + def send_edu(*args, **kwargs): + # print "send_edu: %s, %s" % (args, kwargs) + return defer.succeed((200, "OK")) + self.replication.send_edu.side_effect = send_edu def get_profile_displayname(user_localpart): return defer.succeed("Frank") @@ -95,11 +99,12 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): return defer.succeed("http://foo") self.datastore.get_profile_avatar_url = get_profile_avatar_url + self.presence_list = [ + {"observed_user_id": "@banana:test"}, + {"observed_user_id": "@clementine:test"}, + ] def get_presence_list(user_localpart, accepted=None): - return defer.succeed([ - {"observed_user_id": "@banana:test"}, - {"observed_user_id": "@clementine:test"}, - ]) + return defer.succeed(self.presence_list) self.datastore.get_presence_list = get_presence_list def do_users_share_a_room(userlist): @@ -109,7 +114,10 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): self.handlers = hs.get_handlers() self.mock_update_client = Mock() - self.mock_update_client.return_value = defer.succeed(None) + def update(*args, **kwargs): + # print "mock_update_client: %s, %s" %(args, kwargs) + return defer.succeed(None) + self.mock_update_client.side_effect = update self.handlers.presence_handler.push_update_to_clients = ( self.mock_update_client) @@ -130,6 +138,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): @defer.inlineCallbacks def test_set_my_state(self): + self.presence_list = [ + {"observed_user_id": "@banana:test"}, + {"observed_user_id": "@clementine:test"}, + ] + mocked_set = self.datastore.set_presence_state mocked_set.return_value = defer.succeed({"state": OFFLINE}) @@ -142,6 +155,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): @defer.inlineCallbacks def test_push_local(self): + self.presence_list = [ + {"observed_user_id": "@banana:test"}, + {"observed_user_id": "@clementine:test"}, + ] + self.datastore.set_presence_state.return_value = defer.succeed( {"state": ONLINE}) @@ -173,12 +191,10 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): presence) self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_apple, + call(users_to_push=set([self.u_apple, self.u_banana, self.u_clementine]), + room_ids=[], observed_user=self.u_apple, statuscache=ANY), # self-reflection - call(observer_user=self.u_banana, - observed_user=self.u_apple, - statuscache=ANY), ], any_order=True) statuscache = self.mock_update_client.call_args[1]["statuscache"] @@ -198,12 +214,10 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): self.u_apple, "I am an Apple") self.mock_update_client.assert_has_calls([ - call(observer_user=self.u_apple, + call(users_to_push=set([self.u_apple, self.u_banana, self.u_clementine]), + room_ids=[], observed_user=self.u_apple, statuscache=ANY), # self-reflection - call(observer_user=self.u_banana, - observed_user=self.u_apple, - statuscache=ANY), ], any_order=True) statuscache = self.mock_update_client.call_args[1]["statuscache"] @@ -217,6 +231,10 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): @defer.inlineCallbacks def test_push_remote(self): + self.presence_list = [ + {"observed_user_id": "@potato:remote"}, + ] + self.datastore.set_presence_state.return_value = defer.succeed( {"state": ONLINE}) @@ -247,6 +265,11 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): @defer.inlineCallbacks def test_recv_remote(self): + self.presence_list = [ + {"observed_user_id": "@banana:test"}, + {"observed_user_id": "@clementine:test"}, + ] + # TODO(paul): Gut-wrenching potato_set = self.handlers.presence_handler._remote_recvmap.setdefault( self.u_potato, set()) @@ -264,7 +287,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): ) self.mock_update_client.assert_called_with( - observer_user=self.u_apple, + users_to_push=set([self.u_apple]), + room_ids=[], observed_user=self.u_potato, statuscache=ANY) diff --git a/tests/utils.py b/tests/utils.py index 98d4f9ed58..37b759febc 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -21,7 +21,7 @@ from synapse.api.events.room import ( RoomMemberEvent, MessageEvent ) -from twisted.internet import defer +from twisted.internet import defer, reactor from collections import namedtuple from mock import patch, Mock @@ -263,18 +263,43 @@ class DeferredMockCallable(object): d.callback(None) return result - raise AssertionError("Was not expecting call(%s)" % + failure = AssertionError("Was not expecting call(%s)" % _format_call(args, kwargs) ) + for _, _, d in self.expectations: + try: + d.errback(failure) + except: + pass + + raise failure + def expect_call_and_return(self, call, result): self.expectations.append((call, result, defer.Deferred())) @defer.inlineCallbacks - def await_calls(self): - while self.expectations: - (_, _, d) = self.expectations.pop(0) - yield d + def await_calls(self, timeout=1000): + deferred = defer.DeferredList( + [d for _, _, d in self.expectations], + fireOnOneErrback=True + ) + + timer = reactor.callLater( + timeout/1000, + deferred.errback, + AssertionError( + "%d pending calls left: %s"% ( + len([e for e in self.expectations if not e[2].called]), + [e for e in self.expectations if not e[2].called] + ) + ) + ) + + yield deferred + + timer.cancel() + self.calls = [] def assert_had_no_calls(self): -- cgit 1.5.1 From ee06023573811a06853d28b613cc823282aa3490 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 29 Aug 2014 13:28:02 +0100 Subject: Get the equalities right. --- synapse/handlers/message.py | 7 +++++-- synapse/storage/stream.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'synapse') diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 32548e66fb..3d7f97bcff 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -277,10 +277,13 @@ class MessageHandler(BaseRoomHandler): end_token=now_token.events_key, ) + start_token = now_token.copy_and_replace("events_key", token[0]) + end_token = now_token.copy_and_replace("events_key", token[1]) + d["messages"] = { "chunk": [m.get_dict() for m in messages], - "start": token[0], - "end": token[1], + "start": start_token.to_string(), + "end": end_token.to_string(), } current_state = yield self.store.get_current_state( diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py index 4945b0796e..0b78222827 100644 --- a/synapse/storage/stream.py +++ b/synapse/storage/stream.py @@ -205,8 +205,11 @@ class StreamStore(SQLBaseStore): with_feedback=False): # TODO (erikj): Handle compressed feedback - from_comp = '<' if direction =='b' else '>' - to_comp = '>' if direction =='b' else '<' + # Tokens really represent positions between elements, but we use + # the convention of pointing to the event before the gap. Hence + # we have a bit of asymmetry when it comes to equalities. + from_comp = '<=' if direction =='b' else '>' + to_comp = '>' if direction =='b' else '<=' order = "DESC" if direction == 'b' else "ASC" args = [room_id] -- cgit 1.5.1 From d9ebe531ed0c66e06fd2d1d04fa317da287ec88d Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Sun, 31 Aug 2014 16:06:39 +0100 Subject: Add config tree to synapse. Add support for reading config from a file --- synapse/app/homeserver.py | 89 +++++------------------- synapse/config/__init__.py | 14 ++++ synapse/config/_base.py | 99 ++++++++++++++++++++++++++ synapse/config/database.py | 36 ++++++++++ synapse/config/homeserver.py | 26 +++++++ synapse/config/logger.py | 67 ++++++++++++++++++ synapse/config/server.py | 75 ++++++++++++++++++++ synapse/config/tls.py | 106 ++++++++++++++++++++++++++++ synapse/crypto/config.py | 160 ------------------------------------------- 9 files changed, 441 insertions(+), 231 deletions(-) create mode 100644 synapse/config/__init__.py create mode 100644 synapse/config/_base.py create mode 100644 synapse/config/database.py create mode 100644 synapse/config/homeserver.py create mode 100644 synapse/config/logger.py create mode 100644 synapse/config/server.py create mode 100644 synapse/config/tls.py delete mode 100644 synapse/crypto/config.py (limited to 'synapse') diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py index 6d292ccf9a..f56dde846d 100755 --- a/synapse/app/homeserver.py +++ b/synapse/app/homeserver.py @@ -29,6 +29,7 @@ from synapse.http.client import TwistedHttpClient from synapse.api.urls import ( CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX ) +from synapse.config.homeserver import HomeServerConfig from daemonize import Daemonize import twisted.manhole.telnet @@ -211,32 +212,7 @@ class SynapseHomeServer(HomeServer): logger.info("Synapse now listening on port %d", port) -def setup_logging(verbosity=0, filename=None, config_path=None): - """ Sets up logging with verbosity levels. - Args: - verbosity: The verbosity level. - filename: Log to the given file rather than to the console. - config_path: Path to a python logging config file. - """ - - if config_path is None: - log_format = ( - '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s' - ) - - level = logging.INFO - if verbosity: - level = logging.DEBUG - - # FIXME: we need a logging.WARN for a -q quiet option - - logging.basicConfig(level=level, filename=filename, format=log_format) - else: - logging.config.fileConfig(config_path) - - observer = PythonLoggingObserver() - observer.start() def run(): @@ -244,78 +220,49 @@ def run(): def setup(): - parser = argparse.ArgumentParser() - parser.add_argument("-p", "--port", dest="port", type=int, default=8080, - help="The port to listen on.") - parser.add_argument("-d", "--database", dest="db", default="homeserver.db", - help="The database name.") - parser.add_argument("-H", "--host", dest="host", default="localhost", - help="The hostname of the server.") - parser.add_argument('-v', '--verbose', dest="verbose", action='count', - help="The verbosity level.") - parser.add_argument('-f', '--log-file', dest="log_file", default=None, - help="File to log to.") - parser.add_argument('--log-config', dest="log_config", default=None, - help="Python logging config") - parser.add_argument('-D', '--daemonize', action='store_true', - default=False, help="Daemonize the home server") - parser.add_argument('--pid-file', dest="pid", help="When running as a " - "daemon, the file to store the pid in", - default="hs.pid") - parser.add_argument("-W", "--webclient", dest="webclient", default=True, - action="store_false", help="Don't host a web client.") - parser.add_argument("--manhole", dest="manhole", type=int, default=None, - help="Turn on the twisted telnet manhole service.") - args = parser.parse_args() - - verbosity = int(args.verbose) if args.verbose else None - - # Because if/when we daemonize we change to root dir. - db_name = os.path.abspath(args.db) - log_file = args.log_file - if log_file: - log_file = os.path.abspath(log_file) - - setup_logging( + config = HomeServerConfig.load_config("Synapse Homeserver", sys.argv[1:]) + + config.setup_logging( verbosity=verbosity, filename=log_file, config_path=args.log_config, ) - logger.info("Server hostname: %s", args.host) + logger.info("Server hostname: %s", config.server_name) - if re.search(":[0-9]+$", args.host): - domain_with_port = args.host + if re.search(":[0-9]+$", config.server_name): + domain_with_port = config.server_name else: - domain_with_port = "%s:%s" % (args.host, args.port) + domain_with_port = "%s:%s" % (args.server_name, config.bind_port) hs = SynapseHomeServer( - args.host, + config.server_name, domain_with_port=domain_with_port, upload_dir=os.path.abspath("uploads"), - db_name=db_name, + db_name=config.database_path, ) hs.register_servlets() hs.create_resource_tree( - web_client=args.webclient, - redirect_root_to_web_client=True) - hs.start_listening(args.port) + web_client=config.webclient, + redirect_root_to_web_client=True, + ) + hs.start_listening(config.bind_port) hs.get_db_pool() - if args.manhole: + if config.manhole: f = twisted.manhole.telnet.ShellFactory() f.username = "matrix" f.password = "rabbithole" f.namespace['hs'] = hs - reactor.listenTCP(args.manhole, f, interface='127.0.0.1') + reactor.listenTCP(config.manhole, f, interface='127.0.0.1') - if args.daemonize: + if config.daemonize: daemon = Daemonize( app="synapse-homeserver", - pid=args.pid, + pid=config.pid_file, action=run, auto_close_fds=False, verbose=True, diff --git a/synapse/config/__init__.py b/synapse/config/__init__.py new file mode 100644 index 0000000000..fe8a073cd3 --- /dev/null +++ b/synapse/config/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# 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. diff --git a/synapse/config/_base.py b/synapse/config/_base.py new file mode 100644 index 0000000000..b4cf0262f4 --- /dev/null +++ b/synapse/config/_base.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# 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 ConfigParser as configparser +import argparse +import sys +import os + + +class Config(object): + def __init__(self, args): + pass + + @staticmethod + def read_file(file_path): + with open(file_path) as file_stream: + return file_stream.read() + + @staticmethod + def read_config_file(file_path): + config = configparser.SafeConfigParser() + config.read([file_path]) + config_dict = {} + for section in config.sections(): + config_dict.update(config.items(section)) + return config_dict + + @classmethod + def add_arguments(cls, parser): + pass + + @classmethod + def generate_config(cls, args, config_dir_path): + pass + + @classmethod + def load_config(cls, description, argv, generate_section=None): + config_parser = argparse.ArgumentParser(add_help=False) + config_parser.add_argument( + "-c", "--config-path", + metavar="CONFIG_FILE", + help="Specify config file" + ) + config_args, remaining_args = config_parser.parse_known_args(argv) + + if generate_section: + if not config_args.config_path: + config_parser.error( + "Must specify where to generate the config file" + ) + config_dir_path = os.path.dirname(config_args.config_path) + if os.path.exists(config_args.config_path): + defaults = cls.read_config_file(config_args.config_path) + else: + if config_args.config_path: + defaults = cls.read_config_file(config_args.config_path) + else: + defaults = {} + + parser = argparse.ArgumentParser( + parents=[config_parser], + description=description, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.set_defaults(**defaults) + + + cls.add_arguments(parser) + args = parser.parse_args(remaining_args) + + if generate_section: + config_dir_path = os.path.dirname(config_args.config_path) + config_dir_path = os.path.abspath(config_dir_path) + cls.generate_config(args, config_dir_path) + config = configparser.SafeConfigParser() + config.add_section(generate_section) + for key, value in vars(args).items(): + if key != "config_path" and value is not None: + config.set(generate_section, key, str(value)) + with open(config_args.config_path, "w") as config_file: + config.write(config_file) + + return cls(args) + + + diff --git a/synapse/config/database.py b/synapse/config/database.py new file mode 100644 index 0000000000..43f54be437 --- /dev/null +++ b/synapse/config/database.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._base import Config +import os + +class DatabaseConfig(Config): + def __init__(self, args): + self.db_path = os.path.abspath(args.database_path) + + @classmethod + def add_arguments(cls, parser): + super(DatabaseConfig, cls).add_arguments(parser) + db_group = parser.add_argument_group("database") + db_group.add_argument( + "-d", "--database", dest="database_path", default="homeserver.db", + help="The database name." + ) + + @classmethod + def generate_config(cls, args, config_dir_path): + super(DatabaseConfig, cls).generate_config(args, config_dir_path) + args.database_path = os.path.abspath(args.database_path) + diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py new file mode 100644 index 0000000000..18072e3196 --- /dev/null +++ b/synapse/config/homeserver.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# 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 .tls import TlsConfig +from .server import ServerConfig +from .logger import LoggingConfig +from .database import DatabaseConfig + +class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig): + pass + +if __name__=='__main__': + import sys + HomeServerConfig.load_config("Generate config", sys.argv[1:], "HomeServer") diff --git a/synapse/config/logger.py b/synapse/config/logger.py new file mode 100644 index 0000000000..d34532c41a --- /dev/null +++ b/synapse/config/logger.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._base import Config + +from twisted.python.log import PythonLoggingObserver +import logging +import logging.config +import os + +class LoggingConfig(Config): + def __init__(self, args): + self.verbosity = int(args.verbose) if args.verbose else None + self.log_config = os.path.abspath(args.log_config) + self.log_file = os.path.abspath(args.log_file) + + @classmethod + def add_arguments(cls, parser): + super(LoggingConfig, cls).add_arguments(parser) + logging_group = parser.add_argument_group("logging") + logging_group.add_argument( + '-v', '--verbose', dest="verbose", action='count', + help="The verbosity level." + ) + logging_group.add_argument( + '-f', '--log-file', dest="log_file", default=None, + help="File to log to." + ) + logging_group.add_argument( + '--log-config', dest="log_config", default=None, + help="Python logging config file" + ) + + def setup_logging(self): + log_format = ( + '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s' + ) + if self.config_path is None: + + level = logging.INFO + if verbosity: + level = logging.DEBUG + + # FIXME: we need a logging.WARN for a -q quiet option + + logging.basicConfig( + level=level, + filename=filename, + format=log_format + ) + else: + logging.config.fileConfig(config_path) + + observer = PythonLoggingObserver() + observer.start() diff --git a/synapse/config/server.py b/synapse/config/server.py new file mode 100644 index 0000000000..4a656b06ab --- /dev/null +++ b/synapse/config/server.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# 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 nacl.signing +import socket +import os +from ._base import Config +from syutil.base64util import encode_base64, decode_base64 + + +class ServerConfig(Config): + def __init__(self, args): + super(ServerConfig, self).__init__(args) + self.server_name = args.server_name + self.signing_key = self.read_signing_key(args.signing_key_path) + self.bind_port = args.bind_port + self.bind_host = args.bind_host + self.daemonize = args.daemonize + self.pid_file = os.path.abspath(args.pid_file) + + @classmethod + def add_arguments(cls, parser): + super(ServerConfig, cls).add_arguments(parser) + server_group = parser.add_argument_group("server") + server_group.add_argument("-H", "--server-name", default="localhost", + help="The name of the server") + server_group.add_argument("--signing-key-path", + help="The signing key to sign messages with") + server_group.add_argument("-p", "--bind-port", type=int, + help="TCP port to listen on") + server_group.add_argument("--bind-host", default="", + help="Local interface to listen on") + server_group.add_argument("-D", "--daemonize", action='store_true', + help="Daemonize the home server") + server_group.add_argument('--pid-file', default = "hs.pid", + help="When running as a daemon, the file to" + " store the pid in") + server_group.add_argument("-W", "--no-webclient", dest="webclient", + default=True, action="store_false", + help="Don't host a web client.") + server_group.add_argument("--manhole", dest="manhole", type=int, + help="Turn on the twisted telnet manhole" + " service on the given port.") + + def read_signing_key(self, signing_key_path): + signing_key_base64 = self.read_file(signing_key_path) + signing_key_bytes = decode_base64(signing_key_base64) + return nacl.signing.SigningKey(signing_key_bytes) + + @classmethod + def generate_config(cls, args, config_dir_path): + super(ServerConfig, cls).generate_config(args, config_dir_path) + base_key_name = os.path.join(config_dir_path, args.server_name) + + args.pid_file = os.path.abspath(args.pid_file) + + if not args.signing_key_path: + args.signing_key_path = base_key_name + ".signing.key" + + if not os.path.exists(args.signing_key_path): + with open(args.signing_key_path, "w") as signing_key_file: + key = nacl.signing.SigningKey.generate() + signing_key_file.write(encode_base64(key.encode())) diff --git a/synapse/config/tls.py b/synapse/config/tls.py new file mode 100644 index 0000000000..c65487ceb9 --- /dev/null +++ b/synapse/config/tls.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._base import Config + +from OpenSSL import crypto +import subprocess +import os + +class TlsConfig(Config): + def __init__(self, args): + super(TlsConfig, self).__init__(args) + self.tls_certificate = self.read_tls_certificate( + args.tls_certificate_path + ) + self.tls_private_key = self.read_tls_private_key( + args.tls_private_key_path + ) + self.tls_dh_params_path = args.tls_dh_params_path + + @classmethod + def add_arguments(cls, parser): + super(TlsConfig, cls).add_arguments(parser) + tls_group = parser.add_argument_group("tls") + tls_group.add_argument("--tls-certificate-path", + help="PEM encoded X509 certificate for TLS") + tls_group.add_argument("--tls-private-key-path", + help="PEM encoded private key for TLS") + tls_group.add_argument("--tls-dh-params-path", + help="PEM dh parameters for ephemeral keys") + + def read_tls_certificate(self, cert_path): + cert_pem = self.read_file(cert_path) + return crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem) + + def read_tls_private_key(self, private_key_path): + private_key_pem = self.read_file(private_key_path) + return crypto.load_privatekey(crypto.FILETYPE_PEM, private_key_pem) + + @classmethod + def generate_config(cls, args, config_dir_path): + super(TlsConfig, cls).generate_config(args, config_dir_path) + base_key_name = os.path.join(config_dir_path, args.server_name) + + if args.tls_certificate_path is None: + args.tls_certificate_path = base_key_name + ".tls.crt" + + if args.tls_private_key_path is None: + args.tls_private_key_path = base_key_name + ".tls.key" + + if args.tls_dh_params_path is None: + args.tls_dh_params_path = base_key_name + ".tls.dh" + + if not os.path.exists(args.tls_private_key_path): + with open(args.tls_private_key_path, "w") as private_key_file: + tls_private_key = crypto.PKey() + tls_private_key.generate_key(crypto.TYPE_RSA, 2048) + private_key_pem = crypto.dump_privatekey( + crypto.FILETYPE_PEM, tls_private_key + ) + private_key_file.write(private_key_pem) + else: + with open(args.tls_private_key_path) as private_key_file: + private_key_pem = private_key_file.read() + tls_private_key = crypto.load_privatekey( + crypto.FILETYPE_PEM, private_key_pem + ) + + if not os.path.exists(args.tls_certificate_path): + with open(args.tls_certificate_path, "w") as certifcate_file: + cert = crypto.X509() + subject = cert.get_subject() + subject.CN = args.server_name + + cert.set_serial_number(1000) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) + cert.set_issuer(cert.get_subject()) + cert.set_pubkey(tls_private_key) + + cert.sign(tls_private_key, 'sha256') + + cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) + + certifcate_file.write(cert_pem) + + if not os.path.exists(args.tls_dh_params_path): + subprocess.check_call([ + "openssl", "dhparam", + "-outform", "PEM", + "-out", args.tls_dh_params_path, + "2048" + ]) + diff --git a/synapse/crypto/config.py b/synapse/crypto/config.py deleted file mode 100644 index 2330133e71..0000000000 --- a/synapse/crypto/config.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2014 matrix.org -# -# 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 ConfigParser as configparser -import argparse -import socket -import sys -import os -from OpenSSL import crypto -import nacl.signing -from syutil.base64util import encode_base64 -import subprocess - - -def load_config(description, argv): - config_parser = argparse.ArgumentParser(add_help=False) - config_parser.add_argument("-c", "--config-path", metavar="CONFIG_FILE", - help="Specify config file") - config_args, remaining_args = config_parser.parse_known_args(argv) - if config_args.config_path: - config = configparser.SafeConfigParser() - config.read([config_args.config_path]) - defaults = dict(config.items("KeyServer")) - else: - defaults = {} - parser = argparse.ArgumentParser( - parents=[config_parser], - description=description, - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - parser.set_defaults(**defaults) - parser.add_argument("--server-name", default=socket.getfqdn(), - help="The name of the server") - parser.add_argument("--signing-key-path", - help="The signing key to sign responses with") - parser.add_argument("--tls-certificate-path", - help="PEM encoded X509 certificate for TLS") - parser.add_argument("--tls-private-key-path", - help="PEM encoded private key for TLS") - parser.add_argument("--tls-dh-params-path", - help="PEM encoded dh parameters for ephemeral keys") - parser.add_argument("--bind-port", type=int, - help="TCP port to listen on") - parser.add_argument("--bind-host", default="", - help="Local interface to listen on") - - args = parser.parse_args(remaining_args) - - server_config = vars(args) - del server_config["config_path"] - return server_config - - -def generate_config(argv): - parser = argparse.ArgumentParser() - parser.add_argument("-c", "--config-path", help="Specify config file", - metavar="CONFIG_FILE", required=True) - parser.add_argument("--server-name", default=socket.getfqdn(), - help="The name of the server") - parser.add_argument("--signing-key-path", - help="The signing key to sign responses with") - parser.add_argument("--tls-certificate-path", - help="PEM encoded X509 certificate for TLS") - parser.add_argument("--tls-private-key-path", - help="PEM encoded private key for TLS") - parser.add_argument("--tls-dh-params-path", - help="PEM encoded dh parameters for ephemeral keys") - parser.add_argument("--bind-port", type=int, required=True, - help="TCP port to listen on") - parser.add_argument("--bind-host", default="", - help="Local interface to listen on") - - args = parser.parse_args(argv) - - dir_name = os.path.dirname(args.config_path) - base_key_name = os.path.join(dir_name, args.server_name) - - if args.signing_key_path is None: - args.signing_key_path = base_key_name + ".signing.key" - - if args.tls_certificate_path is None: - args.tls_certificate_path = base_key_name + ".tls.crt" - - if args.tls_private_key_path is None: - args.tls_private_key_path = base_key_name + ".tls.key" - - if args.tls_dh_params_path is None: - args.tls_dh_params_path = base_key_name + ".tls.dh" - - if not os.path.exists(args.signing_key_path): - with open(args.signing_key_path, "w") as signing_key_file: - key = nacl.signing.SigningKey.generate() - signing_key_file.write(encode_base64(key.encode())) - - if not os.path.exists(args.tls_private_key_path): - with open(args.tls_private_key_path, "w") as private_key_file: - tls_private_key = crypto.PKey() - tls_private_key.generate_key(crypto.TYPE_RSA, 2048) - private_key_pem = crypto.dump_privatekey( - crypto.FILETYPE_PEM, tls_private_key - ) - private_key_file.write(private_key_pem) - else: - with open(args.tls_private_key_path) as private_key_file: - private_key_pem = private_key_file.read() - tls_private_key = crypto.load_privatekey( - crypto.FILETYPE_PEM, private_key_pem - ) - - if not os.path.exists(args.tls_certificate_path): - with open(args.tls_certificate_path, "w") as certifcate_file: - cert = crypto.X509() - subject = cert.get_subject() - subject.CN = args.server_name - - cert.set_serial_number(1000) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) - cert.set_issuer(cert.get_subject()) - cert.set_pubkey(tls_private_key) - - cert.sign(tls_private_key, 'sha256') - - cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) - - certifcate_file.write(cert_pem) - - if not os.path.exists(args.tls_dh_params_path): - subprocess.check_call([ - "openssl", "dhparam", - "-outform", "PEM", - "-out", args.tls_dh_params_path, - "2048" - ]) - - config = configparser.SafeConfigParser() - config.add_section("KeyServer") - for key, value in vars(args).items(): - if key != "config_path": - config.set("KeyServer", key, str(value)) - - with open(args.config_path, "w") as config_file: - config.write(config_file) - - -if __name__ == "__main__": - generate_config(sys.argv[1:]) -- cgit 1.5.1