Пример #1
0
    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(hs)
        self.streamer = hs.get_replication_streamer()
        self.server = server_factory.buildProtocol(None)

        # Make a new HomeServer object for the worker
        self.reactor.lookups["testserv"] = "1.2.3.4"
        self.worker_hs = self.setup_test_homeserver(
            http_client=None,
            homeserver_to_use=GenericWorkerServer,
            config=self._get_worker_hs_config(),
            reactor=self.reactor,
        )

        # Since we use sqlite in memory databases we need to make sure the
        # databases objects are the same.
        self.worker_hs.get_datastore().db_pool = hs.get_datastore().db_pool

        self.test_handler = self._build_replication_data_handler()
        self.worker_hs.replication_data_handler = self.test_handler

        repl_handler = ReplicationCommandHandler(self.worker_hs)
        self.client = ClientReplicationStreamProtocol(
            self.worker_hs,
            "client",
            "test",
            clock,
            repl_handler,
        )

        self._client_transport = None
        self._server_transport = None
Пример #2
0
class BaseStreamTestCase(unittest.HomeserverTestCase):
    """Base class for tests of the replication streams"""

    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(self.hs)
        self.streamer = server_factory.streamer
        server = server_factory.buildProtocol(None)

        # build a replication client, with a dummy handler
        self.test_handler = TestReplicationClientHandler()
        self.client = ClientReplicationStreamProtocol(
            "client", "test", clock, self.test_handler
        )

        # wire them together
        self.client.makeConnection(FakeTransport(server, reactor))
        server.makeConnection(FakeTransport(self.client, reactor))

    def replicate(self):
        """Tell the master side of replication that something has happened, and then
        wait for the replication to occur.
        """
        self.streamer.on_notifier_poke()
        self.pump(0.1)

    def replicate_stream(self, stream, token="NOW"):
        """Make the client end a REPLICATE command to set up a subscription to a stream"""
        self.client.send_command(ReplicateCommand(stream, token))
Пример #3
0
class BaseStreamTestCase(unittest.HomeserverTestCase):
    """Base class for tests of the replication streams"""
    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(self.hs)
        self.streamer = server_factory.streamer
        server = server_factory.buildProtocol(None)

        # build a replication client, with a dummy handler
        handler_factory = Mock()
        self.test_handler = TestReplicationClientHandler()
        self.test_handler.factory = handler_factory
        self.client = ClientReplicationStreamProtocol("client", "test", clock,
                                                      self.test_handler)

        # wire them together
        self.client.makeConnection(FakeTransport(server, reactor))
        server.makeConnection(FakeTransport(self.client, reactor))

    def replicate(self):
        """Tell the master side of replication that something has happened, and then
        wait for the replication to occur.
        """
        self.streamer.on_notifier_poke()
        self.pump(0.1)

    def replicate_stream(self, stream, token="NOW"):
        """Make the client end a REPLICATE command to set up a subscription to a stream"""
        self.client.send_command(ReplicateCommand(stream, token))
Пример #4
0
    def make_worker_hs(self,
                       worker_app: str,
                       extra_config: dict = {},
                       **kwargs) -> HomeServer:
        """Make a new worker HS instance, correctly connecting replcation
        stream to the master HS.

        Args:
            worker_app: Type of worker, e.g. `synapse.app.federation_sender`.
            extra_config: Any extra config to use for this instances.
            **kwargs: Options that get passed to `self.setup_test_homeserver`,
                useful to e.g. pass some mocks for things like `http_client`

        Returns:
            The new worker HomeServer instance.
        """

        config = self._get_worker_hs_config()
        config["worker_app"] = worker_app
        config.update(extra_config)

        worker_hs = self.setup_test_homeserver(
            homeserverToUse=GenericWorkerServer,
            config=config,
            reactor=self.reactor,
            **kwargs)

        store = worker_hs.get_datastore()
        store.db_pool._db_pool = self.database_pool._db_pool

        repl_handler = ReplicationCommandHandler(worker_hs)
        client = ClientReplicationStreamProtocol(
            worker_hs,
            "client",
            "test",
            self.clock,
            repl_handler,
        )
        server = self.server_factory.buildProtocol(None)

        client_transport = FakeTransport(server, self.reactor)
        client.makeConnection(client_transport)

        server_transport = FakeTransport(client, self.reactor)
        server.makeConnection(server_transport)

        # Set up a resource for the worker
        resource = ReplicationRestResource(self.hs)

        for servlet in self.servlets:
            servlet(worker_hs, resource)

        self._worker_hs_to_resource[worker_hs] = resource

        return worker_hs
Пример #5
0
    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(self.hs)
        self.streamer = server_factory.streamer
        server = server_factory.buildProtocol(None)

        # build a replication client, with a dummy handler
        self.test_handler = TestReplicationClientHandler()
        self.client = ClientReplicationStreamProtocol("client", "test", clock,
                                                      self.test_handler)

        # wire them together
        self.client.makeConnection(FakeTransport(server, reactor))
        server.makeConnection(FakeTransport(self.client, reactor))
Пример #6
0
 def buildProtocol(self, addr):
     logger.info("Connected to replication: %r", addr)
     return ClientReplicationStreamProtocol(
         self.hs,
         self.client_name,
         self.server_name,
         self._clock,
         self.command_handler,
     )
Пример #7
0
    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(hs)
        self.streamer = hs.get_replication_streamer()
        self.server: ServerReplicationStreamProtocol = server_factory.buildProtocol(
            IPv4Address("TCP", "127.0.0.1", 0))

        # Make a new HomeServer object for the worker
        self.reactor.lookups["testserv"] = "1.2.3.4"
        self.worker_hs = self.setup_test_homeserver(
            federation_http_client=None,
            homeserver_to_use=GenericWorkerServer,
            config=self._get_worker_hs_config(),
            reactor=self.reactor,
        )

        # Since we use sqlite in memory databases we need to make sure the
        # databases objects are the same.
        self.worker_hs.get_datastores().main.db_pool = hs.get_datastores(
        ).main.db_pool

        # Normally we'd pass in the handler to `setup_test_homeserver`, which would
        # eventually hit "Install @cache_in_self attributes" in tests/utils.py.
        # Unfortunately our handler wants a reference to the homeserver. That leaves
        # us with a chicken-and-egg problem.
        # We can workaround this: create the homeserver first, create the handler
        # and bodge it in after the fact. The bodging requires us to know the
        # dirty details of how `cache_in_self` works. We politely ask mypy to
        # ignore our dirty dealings.
        self.test_handler = self._build_replication_data_handler()
        self.worker_hs._replication_data_handler = self.test_handler  # type: ignore[attr-defined]

        repl_handler = ReplicationCommandHandler(self.worker_hs)
        self.client = ClientReplicationStreamProtocol(
            self.worker_hs,
            "client",
            "test",
            clock,
            repl_handler,
        )

        self._client_transport = None
        self._server_transport = None
Пример #8
0
    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(self.hs)
        self.streamer = server_factory.streamer
        server = server_factory.buildProtocol(None)

        # build a replication client, with a dummy handler
        self.test_handler = TestReplicationClientHandler()
        self.client = ClientReplicationStreamProtocol(
            "client", "test", clock, self.test_handler
        )

        # wire them together
        self.client.makeConnection(FakeTransport(server, reactor))
        server.makeConnection(FakeTransport(self.client, reactor))
Пример #9
0
class BaseStreamTestCase(unittest.HomeserverTestCase):
    """Base class for tests of the replication streams"""

    # hiredis is an optional dependency so we don't want to require it for running
    # the tests.
    if not hiredis:
        skip = "Requires hiredis"

    servlets = [
        streams.register_servlets,
    ]

    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(hs)
        self.streamer = hs.get_replication_streamer()
        self.server = server_factory.buildProtocol(None)

        # Make a new HomeServer object for the worker
        self.reactor.lookups["testserv"] = "1.2.3.4"
        self.worker_hs = self.setup_test_homeserver(
            http_client=None,
            homeserver_to_use=GenericWorkerServer,
            config=self._get_worker_hs_config(),
            reactor=self.reactor,
        )

        # Since we use sqlite in memory databases we need to make sure the
        # databases objects are the same.
        self.worker_hs.get_datastore().db_pool = hs.get_datastore().db_pool

        self.test_handler = self._build_replication_data_handler()
        self.worker_hs.replication_data_handler = self.test_handler

        repl_handler = ReplicationCommandHandler(self.worker_hs)
        self.client = ClientReplicationStreamProtocol(
            self.worker_hs,
            "client",
            "test",
            clock,
            repl_handler,
        )

        self._client_transport = None
        self._server_transport = None

    def _get_worker_hs_config(self) -> dict:
        config = self.default_config()
        config["worker_app"] = "synapse.app.generic_worker"
        config["worker_replication_host"] = "testserv"
        config["worker_replication_http_port"] = "8765"
        return config

    def _build_replication_data_handler(self):
        return TestReplicationDataHandler(self.worker_hs)

    def reconnect(self):
        if self._client_transport:
            self.client.close()

        if self._server_transport:
            self.server.close()

        self._client_transport = FakeTransport(self.server, self.reactor)
        self.client.makeConnection(self._client_transport)

        self._server_transport = FakeTransport(self.client, self.reactor)
        self.server.makeConnection(self._server_transport)

    def disconnect(self):
        if self._client_transport:
            self._client_transport = None
            self.client.close()

        if self._server_transport:
            self._server_transport = None
            self.server.close()

    def replicate(self):
        """Tell the master side of replication that something has happened, and then
        wait for the replication to occur.
        """
        self.streamer.on_notifier_poke()
        self.pump(0.1)

    def handle_http_replication_attempt(self) -> SynapseRequest:
        """Asserts that a connection attempt was made to the master HS on the
        HTTP replication port, then proxies it to the master HS object to be
        handled.

        Returns:
            The request object received by master HS.
        """

        # We should have an outbound connection attempt.
        clients = self.reactor.tcpClients
        self.assertEqual(len(clients), 1)
        (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
        self.assertEqual(host, "1.2.3.4")
        self.assertEqual(port, 8765)

        # Set up client side protocol
        client_protocol = client_factory.buildProtocol(None)

        request_factory = OneShotRequestFactory()

        # Set up the server side protocol
        channel = _PushHTTPChannel(self.reactor)
        channel.requestFactory = request_factory
        channel.site = self.site

        # Connect client to server and vice versa.
        client_to_server_transport = FakeTransport(channel, self.reactor,
                                                   client_protocol)
        client_protocol.makeConnection(client_to_server_transport)

        server_to_client_transport = FakeTransport(client_protocol,
                                                   self.reactor, channel)
        channel.makeConnection(server_to_client_transport)

        # The request will now be processed by `self.site` and the response
        # streamed back.
        self.reactor.advance(0)

        # We tear down the connection so it doesn't get reused without our
        # knowledge.
        server_to_client_transport.loseConnection()
        client_to_server_transport.loseConnection()

        return request_factory.request

    def assert_request_is_get_repl_stream_updates(self,
                                                  request: SynapseRequest,
                                                  stream_name: str):
        """Asserts that the given request is a HTTP replication request for
        fetching updates for given stream.
        """

        self.assertRegex(
            request.path,
            br"^/_synapse/replication/get_repl_stream_updates/%s/[^/]+$" %
            (stream_name.encode("ascii"), ),
        )

        self.assertEqual(request.method, b"GET")
Пример #10
0
    def make_worker_hs(self,
                       worker_app: str,
                       extra_config: dict = {},
                       **kwargs) -> HomeServer:
        """Make a new worker HS instance, correctly connecting replcation
        stream to the master HS.

        Args:
            worker_app: Type of worker, e.g. `synapse.app.federation_sender`.
            extra_config: Any extra config to use for this instances.
            **kwargs: Options that get passed to `self.setup_test_homeserver`,
                useful to e.g. pass some mocks for things like `http_client`

        Returns:
            The new worker HomeServer instance.
        """

        config = self._get_worker_hs_config()
        config["worker_app"] = worker_app
        config.update(extra_config)

        worker_hs = self.setup_test_homeserver(
            homeserver_to_use=GenericWorkerServer,
            config=config,
            reactor=self.reactor,
            **kwargs,
        )

        # If the instance is in the `instance_map` config then workers may try
        # and send HTTP requests to it, so we register it with
        # `_handle_http_replication_attempt` like we do with the master HS.
        instance_name = worker_hs.get_instance_name()
        instance_loc = worker_hs.config.worker.instance_map.get(instance_name)
        if instance_loc:
            # Ensure the host is one that has a fake DNS entry.
            if instance_loc.host not in self.reactor.lookups:
                raise Exception(
                    "Host does not have an IP for instance_map[%r].host = %r" %
                    (
                        instance_name,
                        instance_loc.host,
                    ))

            self.reactor.add_tcp_client_callback(
                self.reactor.lookups[instance_loc.host],
                instance_loc.port,
                lambda: self._handle_http_replication_attempt(
                    worker_hs, instance_loc.port),
            )

        store = worker_hs.get_datastore()
        store.db_pool._db_pool = self.database_pool._db_pool

        # Set up TCP replication between master and the new worker if we don't
        # have Redis support enabled.
        if not worker_hs.config.redis_enabled:
            repl_handler = ReplicationCommandHandler(worker_hs)
            client = ClientReplicationStreamProtocol(
                worker_hs,
                "client",
                "test",
                self.clock,
                repl_handler,
            )
            server = self.server_factory.buildProtocol(None)

            client_transport = FakeTransport(server, self.reactor)
            client.makeConnection(client_transport)

            server_transport = FakeTransport(client, self.reactor)
            server.makeConnection(server_transport)

        # Set up a resource for the worker
        resource = ReplicationRestResource(worker_hs)

        for servlet in self.servlets:
            servlet(worker_hs, resource)

        self._hs_to_site[worker_hs] = SynapseSite(
            logger_name="synapse.access.http.fake",
            site_tag="{}-{}".format(worker_hs.config.server.server_name,
                                    worker_hs.get_instance_name()),
            config=worker_hs.config.server.listeners[0],
            resource=resource,
            server_version_string="1",
        )

        if worker_hs.config.redis.redis_enabled:
            worker_hs.get_tcp_replication().start_replication(worker_hs)

        return worker_hs
Пример #11
0
class BaseStreamTestCase(unittest.HomeserverTestCase):
    """Base class for tests of the replication streams"""

    # hiredis is an optional dependency so we don't want to require it for running
    # the tests.
    if not hiredis:
        skip = "Requires hiredis"

    def prepare(self, reactor, clock, hs):
        # build a replication server
        server_factory = ReplicationStreamProtocolFactory(hs)
        self.streamer = hs.get_replication_streamer()
        self.server: ServerReplicationStreamProtocol = server_factory.buildProtocol(
            None)

        # Make a new HomeServer object for the worker
        self.reactor.lookups["testserv"] = "1.2.3.4"
        self.worker_hs = self.setup_test_homeserver(
            federation_http_client=None,
            homeserver_to_use=GenericWorkerServer,
            config=self._get_worker_hs_config(),
            reactor=self.reactor,
        )

        # Since we use sqlite in memory databases we need to make sure the
        # databases objects are the same.
        self.worker_hs.get_datastore().db_pool = hs.get_datastore().db_pool

        # Normally we'd pass in the handler to `setup_test_homeserver`, which would
        # eventually hit "Install @cache_in_self attributes" in tests/utils.py.
        # Unfortunately our handler wants a reference to the homeserver. That leaves
        # us with a chicken-and-egg problem.
        # We can workaround this: create the homeserver first, create the handler
        # and bodge it in after the fact. The bodging requires us to know the
        # dirty details of how `cache_in_self` works. We politely ask mypy to
        # ignore our dirty dealings.
        self.test_handler = self._build_replication_data_handler()
        self.worker_hs._replication_data_handler = self.test_handler  # type: ignore[attr-defined]

        repl_handler = ReplicationCommandHandler(self.worker_hs)
        self.client = ClientReplicationStreamProtocol(
            self.worker_hs,
            "client",
            "test",
            clock,
            repl_handler,
        )

        self._client_transport = None
        self._server_transport = None

    def create_resource_dict(self) -> Dict[str, Resource]:
        d = super().create_resource_dict()
        d["/_synapse/replication"] = ReplicationRestResource(self.hs)
        return d

    def _get_worker_hs_config(self) -> dict:
        config = self.default_config()
        config["worker_app"] = "synapse.app.generic_worker"
        config["worker_replication_host"] = "testserv"
        config["worker_replication_http_port"] = "8765"
        return config

    def _build_replication_data_handler(self):
        return TestReplicationDataHandler(self.worker_hs)

    def reconnect(self):
        if self._client_transport:
            self.client.close()

        if self._server_transport:
            self.server.close()

        self._client_transport = FakeTransport(self.server, self.reactor)
        self.client.makeConnection(self._client_transport)

        self._server_transport = FakeTransport(self.client, self.reactor)
        self.server.makeConnection(self._server_transport)

    def disconnect(self):
        if self._client_transport:
            self._client_transport = None
            self.client.close()

        if self._server_transport:
            self._server_transport = None
            self.server.close()

    def replicate(self):
        """Tell the master side of replication that something has happened, and then
        wait for the replication to occur.
        """
        self.streamer.on_notifier_poke()
        self.pump(0.1)

    def handle_http_replication_attempt(self) -> SynapseRequest:
        """Asserts that a connection attempt was made to the master HS on the
        HTTP replication port, then proxies it to the master HS object to be
        handled.

        Returns:
            The request object received by master HS.
        """

        # We should have an outbound connection attempt.
        clients = self.reactor.tcpClients
        self.assertEqual(len(clients), 1)
        (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
        self.assertEqual(host, "1.2.3.4")
        self.assertEqual(port, 8765)

        # Set up client side protocol
        client_protocol = client_factory.buildProtocol(None)

        # Set up the server side protocol
        channel = self.site.buildProtocol(None)

        # hook into the channel's request factory so that we can keep a record
        # of the requests
        requests: List[SynapseRequest] = []
        real_request_factory = channel.requestFactory

        def request_factory(*args, **kwargs):
            request = real_request_factory(*args, **kwargs)
            requests.append(request)
            return request

        channel.requestFactory = request_factory

        # Connect client to server and vice versa.
        client_to_server_transport = FakeTransport(channel, self.reactor,
                                                   client_protocol)
        client_protocol.makeConnection(client_to_server_transport)

        server_to_client_transport = FakeTransport(client_protocol,
                                                   self.reactor, channel)
        channel.makeConnection(server_to_client_transport)

        # The request will now be processed by `self.site` and the response
        # streamed back.
        self.reactor.advance(0)

        # We tear down the connection so it doesn't get reused without our
        # knowledge.
        server_to_client_transport.loseConnection()
        client_to_server_transport.loseConnection()

        # there should have been exactly one request
        self.assertEqual(len(requests), 1)

        return requests[0]

    def assert_request_is_get_repl_stream_updates(self,
                                                  request: SynapseRequest,
                                                  stream_name: str):
        """Asserts that the given request is a HTTP replication request for
        fetching updates for given stream.
        """

        path: bytes = request.path  # type: ignore
        self.assertRegex(
            path,
            br"^/_synapse/replication/get_repl_stream_updates/%s/[^/]+$" %
            (stream_name.encode("ascii"), ),
        )

        self.assertEqual(request.method, b"GET")