示例#1
0
 def test_connect_with_vhost_and_heartbeat(self):
     """
     It's possible to specify a custom vhost and a custom heartbeat.
     """
     endpoint = AMQEndpoint(self.reactor,
                            "1.2.3.4",
                            "1234",
                            username="******",
                            password="******",
                            vhost="foo",
                            heartbeat=10)
     endpoint.connect(self.factory)
     # _WrappingFactory from twisted.internet.endpoints
     factory = self.reactor.tcpClients[0][2]
     protocol = factory.buildProtocol(None)
     protocol.makeConnection(StringTransport())
     client = protocol._wrappedProtocol
     self.assertEqual("foo", client.vhost)
     self.assertEqual(10, client.heartbeatInterval)
示例#2
0
 def test_connect(self):
     """
     The endpoint connects to the broker and performs the AMQP
     authentication.
     """
     endpoint = AMQEndpoint(self.reactor,
                            "1.2.3.4",
                            "1234",
                            username="******",
                            password="******")
     endpoint.connect(self.factory)
     self.assertEqual(("1.2.3.4", 1234), self.reactor.tcpClients[0][:2])
     # _WrappingFactory from twisted.internet.endpoints
     factory = self.reactor.tcpClients[0][2]
     protocol = factory.buildProtocol(None)
     protocol.makeConnection(StringTransport())
     client = protocol._wrappedProtocol
     self.assertEqual({"LOGIN": "******", "PASSWORD": "******"}, client.response)
     self.assertEqual("AMQPLAIN", client.mechanism)
示例#3
0
 def test_connect(self):
     """
     The endpoint returns a connected and authenticated client.
     """
     factory = AMQFactory(spec=self.spec)
     endpoint = AMQEndpoint(reactor,
                            self.host,
                            self.port,
                            username=self.user,
                            password=self.password,
                            vhost=self.vhost)
     client = yield endpoint.connect(factory)
     channel = yield client.channel(1)
     yield channel.channel_open()
     yield client.close()
class NotificationSourceIntegrationTest(IntegrationTest):

    @inlineCallbacks
    def setUp(self):
        super(NotificationSourceIntegrationTest, self).setUp()
        self.endpoint = AMQEndpoint(
            reactor, self.rabbit.config.hostname, self.rabbit.config.port,
            username="******", password="******", heartbeat=1)
        self.policy = backoffPolicy(initialDelay=0)
        self.factory = AMQFactory(spec=AMQP0_8_SPEC_PATH)
        self.service = ClientService(
            self.endpoint, self.factory, retryPolicy=self.policy)
        self.connector = NotificationConnector(self.service)
        self.source = NotificationSource(self.connector)

        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        self.service.startService()

    @inlineCallbacks
    def tearDown(self):
        self.service.stopService()
        super(NotificationSourceIntegrationTest, self).tearDown()
        # Wrap resetting queues and client in a try/except, since the broker
        # may have been stopped (e.g. when this is the last test being run).
        try:
            yield self.channel.queue_delete(queue="uuid")
        except:
            pass
        finally:
            yield self.client.close()

    @inlineCallbacks
    def test_get_after_publish(self):
        """
        Calling get() after a message has been published in the associated
        queue returns a Notification for that message.
        """
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))
        notification = yield self.source.get("uuid", 0)
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_before_publish(self):
        """
        Calling get() before a message has been published in the associated
        queue will wait until publication.
        """
        deferred = self.source.get("uuid", 0)
        self.assertFalse(deferred.called)
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))
        notification = yield deferred
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_with_error(self):
        """
        If an error occurs in during get(), the client is closed so
        we can query messages again.
        """
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))
        with self.assertRaises(NotFound):
            yield self.source.get("uuid-unknown", 0)
        notification = yield self.source.get("uuid", 0)
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_concurrent_with_error(self):
        """
        If an error occurs in a call to get(), other calls don't
        fail, and are retried on reconnection instead.
        """
        client1 = yield self.service.whenConnected()
        deferred = self.source.get("uuid", 0)

        with self.assertRaises(NotFound):
            yield self.source.get("uuid-unknown", 0)

        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))

        notification = yield deferred
        self.assertEqual("hello", notification.payload)
        client2 = yield self.service.whenConnected()
        # The ClientService has reconnected, yielding a new client.
        self.assertIsNot(client1, client2)

    @inlineCallbacks
    def test_get_timeout(self):
        """
        Calls to get() timeout after a certain amount of time if no message
        arrived on the queue.
        """
        self.source.timeout = 1
        with self.assertRaises(Timeout):
            yield self.source.get("uuid", 0)
        client = yield self.service.whenConnected()
        channel = yield client.channel(1)
        # The channel is still opened
        self.assertFalse(channel.closed)
        # The consumer has been deleted
        self.assertNotIn("uuid.0", client.queues)

    @inlineCallbacks
    def test_get_with_broker_shutdown_during_consume(self):
        """
        If rabbitmq gets shutdown during the basic-consume call, we wait
        for the reconection and retry transparently.
        """
        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        yield self.connector()

        d = self.source.get("uuid", 0)

        # Restart rabbitmq
        yield self.client.close()
        yield self.client.disconnected.wait()
        self.rabbit.cleanUp()
        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()

        # Get a new channel and re-declare the queue, since the restart has
        # destroyed it.
        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        # Publish a message in the queue
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))

        notification = yield d
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_with_broker_die_during_consume(self):
        """
        If rabbitmq dies during the basic-consume call, we wait for the
        reconection and retry transparently.
        """
        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        yield self.connector()

        d = self.source.get("uuid", 0)

        # Kill rabbitmq and start it again
        yield self.client.close()
        yield self.client.disconnected.wait()
        self.rabbit.runner.kill()
        self.rabbit.cleanUp()
        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()

        # Get a new channel and re-declare the queue, since the crash has
        # destroyed it.
        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        # Publish a message in the queue
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))

        notification = yield d
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_wb_get_with_broker_shutdown_during_message_wait(self):
        """
        If rabbitmq gets shutdown while we wait for messages, we transparently
        wait for the reconnection and try again.
        """
        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        yield self.connector()

        d = self.source.get("uuid", 0)

        # Acquiring the channel lock makes sure that basic-consume has
        # succeeded and we started waiting for the message.
        yield self.source._channel_lock.acquire()
        self.source._channel_lock.release()

        # Restart rabbitmq
        yield self.client.close()
        yield self.client.disconnected.wait()
        self.rabbit.cleanUp()
        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()

        # Get a new channel and re-declare the queue, since the restart has
        # destroyed it.
        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        # Publish a message in the queue
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))

        notification = yield d
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_wb_heartbeat(self):
        """
        If heartbeat checks fail due to network issues, we keep re-trying
        until the network recovers.
        """
        self.service.stopService()

        # Put a TCP proxy between NotificationSource and RabbitMQ, to simulate
        # packets getting dropped on the floor.
        proxy = ProxyService(
            self.rabbit.config.hostname, self.rabbit.config.port)
        proxy.startService()
        self.addCleanup(proxy.stopService)
        self.endpoint._port = proxy.port
        self.service = ClientService(
            self.endpoint, self.factory, retryPolicy=self.policy)
        self.connector._service = self.service
        self.service.startService()

        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        channel = yield self.connector()

        deferred = self.source.get("uuid", 0)

        # Start dropping packets on the floor
        proxy.block()

        # Publish a notification, which won't be delivered just yet.
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))

        # Wait for the first connection to terminate, because heartbeat
        # checks will fail.
        yield channel.client.disconnected.wait()

        # Now let packets flow again.
        proxy.unblock()

        # The situation got recovered.
        notification = yield deferred
        self.assertEqual("hello", notification.payload)
        self.assertEqual(2, proxy.connections)

    @inlineCallbacks
    def test_reject_notification(self):
        """
        Calling reject() on a Notification puts the associated message back in
        the queue so that it's available to subsequent get() calls.
        """
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))
        notification = yield self.source.get("uuid", 0)
        yield notification.reject()

        notification = yield self.source.get("uuid", 1)
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_ack_message(self):
        """
        Calling ack() on a Notification confirms the removal of the
        associated message from the queue, making subsequent calls
        waiting for another message.
        """
        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))
        notification = yield self.source.get("uuid", 0)
        yield notification.ack()

        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello 2"))
        notification = yield self.source.get("uuid", 1)
        self.assertEqual("hello 2", notification.payload)

    @inlineCallbacks
    def test_ack_with_broker_shutdown(self):
        """
        If rabbitmq gets shutdown before we ack a Notification, an error is
        raised.
        """
        client = yield self.service.whenConnected()

        yield self.channel.basic_publish(
            routing_key="uuid", content=Content("hello"))
        notification = yield self.source.get("uuid", 0)

        self.rabbit.cleanUp()

        yield client.disconnected.wait()

        try:
            yield notification.ack()
        except Bounced:
            pass
        else:
            self.fail("Notification not bounced")

        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()
class NotificationSourceIntegrationTest(IntegrationTest):
    @inlineCallbacks
    def setUp(self):
        super(NotificationSourceIntegrationTest, self).setUp()
        self.endpoint = AMQEndpoint(reactor,
                                    self.rabbit.config.hostname,
                                    self.rabbit.config.port,
                                    username="******",
                                    password="******",
                                    heartbeat=1)
        self.policy = backoffPolicy(initialDelay=0)
        self.factory = AMQFactory(spec=AMQP0_8_SPEC_PATH)
        self.service = ClientService(self.endpoint,
                                     self.factory,
                                     retryPolicy=self.policy)
        self.connector = NotificationConnector(self.service)
        self.source = NotificationSource(self.connector)

        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        self.service.startService()

    @inlineCallbacks
    def tearDown(self):
        self.service.stopService()
        super(NotificationSourceIntegrationTest, self).tearDown()
        # Wrap resetting queues and client in a try/except, since the broker
        # may have been stopped (e.g. when this is the last test being run).
        try:
            yield self.channel.queue_delete(queue="uuid")
        except:
            pass
        finally:
            yield self.client.close()

    @inlineCallbacks
    def test_get_after_publish(self):
        """
        Calling get() after a message has been published in the associated
        queue returns a Notification for that message.
        """
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))
        notification = yield self.source.get("uuid", 0)
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_before_publish(self):
        """
        Calling get() before a message has been published in the associated
        queue will wait until publication.
        """
        deferred = self.source.get("uuid", 0)
        self.assertFalse(deferred.called)
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))
        notification = yield deferred
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_with_error(self):
        """
        If an error occurs in during get(), the client is closed so
        we can query messages again.
        """
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))
        with self.assertRaises(NotFound):
            yield self.source.get("uuid-unknown", 0)
        notification = yield self.source.get("uuid", 0)
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_concurrent_with_error(self):
        """
        If an error occurs in a call to get(), other calls don't
        fail, and are retried on reconnection instead.
        """
        client1 = yield self.service.whenConnected()
        deferred = self.source.get("uuid", 0)

        with self.assertRaises(NotFound):
            yield self.source.get("uuid-unknown", 0)

        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))

        notification = yield deferred
        self.assertEqual("hello", notification.payload)
        client2 = yield self.service.whenConnected()
        # The ClientService has reconnected, yielding a new client.
        self.assertIsNot(client1, client2)

    @inlineCallbacks
    def test_get_timeout(self):
        """
        Calls to get() timeout after a certain amount of time if no message
        arrived on the queue.
        """
        self.source.timeout = 1
        with self.assertRaises(Timeout):
            yield self.source.get("uuid", 0)
        client = yield self.service.whenConnected()
        channel = yield client.channel(1)
        # The channel is still opened
        self.assertFalse(channel.closed)
        # The consumer has been deleted
        self.assertNotIn("uuid.0", client.queues)

    @inlineCallbacks
    def test_get_with_broker_shutdown_during_consume(self):
        """
        If rabbitmq gets shutdown during the basic-consume call, we wait
        for the reconection and retry transparently.
        """
        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        yield self.connector()

        d = self.source.get("uuid", 0)

        # Restart rabbitmq
        yield self.client.close()
        yield self.client.disconnected.wait()
        self.rabbit.cleanUp()
        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()

        # Get a new channel and re-declare the queue, since the restart has
        # destroyed it.
        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        # Publish a message in the queue
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))

        notification = yield d
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_get_with_broker_die_during_consume(self):
        """
        If rabbitmq dies during the basic-consume call, we wait for the
        reconection and retry transparently.
        """
        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        yield self.connector()

        d = self.source.get("uuid", 0)

        # Kill rabbitmq and start it again
        yield self.client.close()
        yield self.client.disconnected.wait()
        self.rabbit.runner.kill()
        self.rabbit.cleanUp()
        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()

        # Get a new channel and re-declare the queue, since the crash has
        # destroyed it.
        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        # Publish a message in the queue
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))

        notification = yield d
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_wb_get_with_broker_shutdown_during_message_wait(self):
        """
        If rabbitmq gets shutdown while we wait for messages, we transparently
        wait for the reconnection and try again.
        """
        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        yield self.connector()

        d = self.source.get("uuid", 0)

        # Acquiring the channel lock makes sure that basic-consume has
        # succeeded and we started waiting for the message.
        yield self.source._channel_lock.acquire()
        self.source._channel_lock.release()

        # Restart rabbitmq
        yield self.client.close()
        yield self.client.disconnected.wait()
        self.rabbit.cleanUp()
        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()

        # Get a new channel and re-declare the queue, since the restart has
        # destroyed it.
        self.client = yield self.endpoint.connect(self.factory)
        self.channel = yield self.client.channel(1)
        yield self.channel.channel_open()
        yield self.channel.queue_declare(queue="uuid")

        # Publish a message in the queue
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))

        notification = yield d
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_wb_heartbeat(self):
        """
        If heartbeat checks fail due to network issues, we keep re-trying
        until the network recovers.
        """
        self.service.stopService()

        # Put a TCP proxy between NotificationSource and RabbitMQ, to simulate
        # packets getting dropped on the floor.
        proxy = ProxyService(self.rabbit.config.hostname,
                             self.rabbit.config.port)
        proxy.startService()
        self.addCleanup(proxy.stopService)
        self.endpoint._port = proxy.port
        self.service = ClientService(self.endpoint,
                                     self.factory,
                                     retryPolicy=self.policy)
        self.connector._service = self.service
        self.service.startService()

        # This will make the connector setup the channel before we call
        # get(), so by the time we call it in the next line all
        # connector-related deferreds will fire synchronously and the
        # code will block on basic-consume.
        channel = yield self.connector()

        deferred = self.source.get("uuid", 0)

        # Start dropping packets on the floor
        proxy.block()

        # Publish a notification, which won't be delivered just yet.
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))

        # Wait for the first connection to terminate, because heartbeat
        # checks will fail.
        yield channel.client.disconnected.wait()

        # Now let packets flow again.
        proxy.unblock()

        # The situation got recovered.
        notification = yield deferred
        self.assertEqual("hello", notification.payload)
        self.assertEqual(2, proxy.connections)

    @inlineCallbacks
    def test_reject_notification(self):
        """
        Calling reject() on a Notification puts the associated message back in
        the queue so that it's available to subsequent get() calls.
        """
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))
        notification = yield self.source.get("uuid", 0)
        yield notification.reject()

        notification = yield self.source.get("uuid", 1)
        self.assertEqual("hello", notification.payload)

    @inlineCallbacks
    def test_ack_message(self):
        """
        Calling ack() on a Notification confirms the removal of the
        associated message from the queue, making subsequent calls
        waiting for another message.
        """
        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))
        notification = yield self.source.get("uuid", 0)
        yield notification.ack()

        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello 2"))
        notification = yield self.source.get("uuid", 1)
        self.assertEqual("hello 2", notification.payload)

    @inlineCallbacks
    def test_ack_with_broker_shutdown(self):
        """
        If rabbitmq gets shutdown before we ack a Notification, an error is
        raised.
        """
        client = yield self.service.whenConnected()

        yield self.channel.basic_publish(routing_key="uuid",
                                         content=Content("hello"))
        notification = yield self.source.get("uuid", 0)

        self.rabbit.cleanUp()

        yield client.disconnected.wait()

        try:
            yield notification.ack()
        except Bounced:
            pass
        else:
            self.fail("Notification not bounced")

        self.rabbit.config = RabbitServerResources(
            port=self.rabbit.config.port)  # Ensure that we use the same port
        self.rabbit.setUp()