예제 #1
0
 def setUp(self):
     self.protocol = mock.Mock()
     self.protocol.ready = defer.Deferred()
     protocol_class = mock.Mock(side_effect=lambda a: self.protocol)
     self.factory = FedoraMessagingFactory(mock.Mock(name="parameters"),
                                           {"binding key": "binding value"})
     self.factory.protocol = protocol_class
예제 #2
0
    def test_passive_declares(self):
        """Assert queues and exchanges are created passively when configured so."""
        factory = FedoraMessagingFactory(
            None,
            queues=[{
                "queue": "my_queue"
            }],
            exchanges=[{
                "exchange": "my_exchange"
            }],
        )
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        def _check(_):
            mock_channel.queue_declare.assert_called_once_with(
                queue="my_queue", passive=True)
            mock_channel.exchange_declare.assert_called_once_with(
                exchange="my_exchange", passive=True)

        with mock.patch.dict(config.conf, {"passive_declares": True}):
            d = factory._on_client_ready()
            d.addCallback(_check)

        return pytest_twisted.blockon(d)
예제 #3
0
    def test_on_client_ready_no_objects(self):
        """Assert factories with no objects to create on startup work."""
        factory = FedoraMessagingFactory(pika.URLParameters("amqp://"))
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)
        d = factory._on_client_ready()

        d.addCallback(lambda _: self.assertTrue(factory._client_ready.called))

        return pytest_twisted.blockon(d)
예제 #4
0
    def test_on_client_ready_queues(self):
        """Assert factories with queues to create on startup work."""
        factory = FedoraMessagingFactory(None, queues=[{"queue": "my_queue"}])
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        d = factory._on_client_ready()

        def _check(_):
            mock_channel.queue_declare.assert_called_once_with(
                queue="my_queue", passive=False)
            self.assertTrue(factory._client_ready.called)

        d.addCallback(_check)

        return pytest_twisted.blockon(d)
예제 #5
0
    def test_on_client_ready_consumers(self):
        """Assert factories with bindings to create on startup work."""
        factory = FedoraMessagingFactory(None)
        factory.buildProtocol(None)
        factory.consume(lambda x: x, "my_queue")
        mock_channel = mock.Mock()
        mock_channel.basic_consume.return_value = mock.Mock(), None
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        d = factory._on_client_ready()

        def _check(_):
            mock_channel.basic_consume.assert_called()
            self.assertIn("my_queue", factory.client._consumers)
            self.assertTrue(factory._client_ready.called)

        d.addCallback(_check)

        return pytest_twisted.blockon(d)
예제 #6
0
class FactoryTests(unittest.TestCase):
    def setUp(self):
        self.protocol = mock.Mock()
        self.protocol.ready = defer.Deferred()
        protocol_class = mock.Mock(side_effect=lambda a: self.protocol)
        self.factory = FedoraMessagingFactory(mock.Mock(name="parameters"),
                                              {"binding key": "binding value"})
        self.factory.protocol = protocol_class

    def test_buildProtocol(self):
        # Check the buildProtocol method.
        protocol = self.factory.buildProtocol(None)
        self.assertTrue(protocol is self.protocol)
        self.assertTrue(self.factory.client is self.protocol)
        self.assertTrue(protocol.factory is self.factory)
        self.protocol.ready.callback(None)
        self.assertTrue(self.factory._client_ready.called)

    def test_buildProtocol_already_consuming(self):
        # When a message callback has been previously set, new calls to
        # buildProtocol should setup the reading process.
        self.protocol.setupRead.side_effect = lambda _: defer.succeed(None)
        self.protocol.resumeProducing.side_effect = \
            lambda: defer.succeed(None)
        self.factory._message_callback = mock.Mock(name="callback")
        self.factory.buildProtocol(None)
        self.protocol.ready.callback(None)
        self.protocol.setupRead.assert_called_once_with(
            self.factory._message_callback)
        self.protocol.resumeProducing.assert_called_once()

    def test_clientConnectionLost(self):
        # The _client_ready deferred must be renewed when the connection is
        # lost.
        self.factory._client_ready.callback(None)
        self.factory.clientConnectionLost(mock.Mock(), Failure(RuntimeError()))
        self.assertFalse(self.factory._client_ready.called)

    def test_stopTrying(self):
        # The _client_ready deferred must errback when we stop trying to
        # reconnect.
        self.factory._client_ready.addCallbacks(
            self.fail, lambda f: f.trap(pika.exceptions.AMQPConnectionError))
        self.factory.stopTrying()
        return pytest_twisted.blockon(self.factory._client_ready)

    def test_stopFactory(self):
        # The protocol should be stopped when the factory is stopped.
        self.protocol.stopProducing.side_effect = \
            lambda: defer.succeed(None)
        self.factory.buildProtocol(None)
        d = self.factory.stopFactory()

        def _check(_):
            self.protocol.stopProducing.assert_called_once()

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_consume(self):
        # Check the consume method
        callback = mock.Mock()
        self.protocol.setupRead.side_effect = lambda _: defer.succeed(None)
        self.protocol.resumeProducing.side_effect = \
            lambda: defer.succeed(None)
        self.factory.client = self.protocol
        # Pretend the factory is ready to trigger protocol setup.
        self.factory._client_ready.callback(None)
        d = self.factory.consume(callback)

        def _check(_):
            self.assertTrue(self.factory._message_callback is callback)
            self.protocol.setupRead.assert_called_once_with(callback)
            self.protocol.resumeProducing.assert_called_once()

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_consume_not_ready(self):
        # Check the consume method
        callback = mock.Mock()
        self.factory.client = self.protocol
        d = self.factory.consume(callback)

        def _check(_):
            self.assertTrue(self.factory._message_callback is callback)
            self.protocol.setupRead.assert_not_called()
            self.protocol.resumeProducing.assert_not_called()

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_publish(self):
        self.factory.client = self.protocol
        self.factory._client_ready.callback(None)
        self.protocol.publish.side_effect = lambda *a: defer.succeed(None)
        d = self.factory.publish("test-message", "test-exchange")

        def _check(_):
            self.protocol.publish.assert_called_once_with(
                "test-message", "test-exchange")

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_publish_connection_closed(self):
        self.factory.client = self.protocol
        self.factory._client_ready.callback(None)
        self.protocol.publish.side_effect = [
            pika.exceptions.ConnectionClosed(42, "testing"),
            defer.succeed(None)
        ]
        d = self.factory.publish("test-message", "test-exchange")

        def _check(_):
            self.assertEqual(
                [call[0] for call in self.protocol.publish.call_args_list], [
                    ("test-message", "test-exchange"),
                    ("test-message", "test-exchange"),
                ])

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_publish_channel_closed(self):
        self.factory.client = self.protocol
        self.factory._client_ready.callback(None)
        self.protocol.publish.side_effect = [
            pika.exceptions.ChannelClosed(42, "testing"),
            defer.succeed(None)
        ]
        d = self.factory.publish("test-message", "test-exchange")

        def _check(_):
            self.assertEqual(
                [call[0] for call in self.protocol.publish.call_args_list], [
                    ("test-message", "test-exchange"),
                    ("test-message", "test-exchange"),
                ])

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_publish_nack_unroutable(self):
        self.factory.client = self.protocol
        self.factory._client_ready.callback(None)
        self.protocol.publish.side_effect = [
            pika.exceptions.NackError(messages=[]),
            pika.exceptions.UnroutableError(messages=[]),
        ]
        d1 = self.factory.publish("test-message", "test-exchange")
        d1.addCallbacks(self.fail, lambda f: f.trap(PublishReturned))
        d2 = self.factory.publish("test-message", "test-exchange")
        d2.addCallbacks(self.fail, lambda f: f.trap(PublishReturned))
        return pytest_twisted.blockon(
            defer.DeferredList([d1, d2], fireOnOneErrback=True))

    def test_publish_amqp_error(self):
        self.factory.client = self.protocol
        self.factory._client_ready.callback(None)
        self.protocol.publish.side_effect = pika.exceptions.AMQPError()
        self.protocol.close.side_effect = lambda: defer.succeed(None)
        self.factory.stopTrying = mock.Mock()
        d = self.factory.publish("test-message", "test-exchange")

        def _check(f):
            self.factory.stopTrying.assert_called_once()
            self.protocol.close.assert_called_once()
            f.trap(ConnectionException)

        d.addCallbacks(self.fail, _check)
        return pytest_twisted.blockon(d)
예제 #7
0
class FactoryTests(unittest.TestCase):
    def setUp(self):
        self.protocol = mock.Mock()
        self.protocol.ready = defer.Deferred()
        protocol_class = mock.Mock(side_effect=lambda a: self.protocol)
        self.factory = FedoraMessagingFactory(mock.Mock(name="parameters"),
                                              {"binding key": "binding value"})
        self.factory.protocol = protocol_class

    def test_started_connection(self):
        """Assert connection attempts are logged."""
        with mock.patch("fedora_messaging.twisted.factory._legacy_twisted_log"
                        ) as mock_log:
            self.factory.startedConnecting(None)
        mock_log.msg.assert_called_once_with(
            "Started new connection to the AMQP broker")

    def test_on_client_ready_no_objects(self):
        """Assert factories with no objects to create on startup work."""
        factory = FedoraMessagingFactory(pika.URLParameters("amqp://"))
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)
        d = factory._on_client_ready()

        d.addCallback(lambda _: self.assertTrue(factory._client_ready.called))

        return pytest_twisted.blockon(d)

    def test_on_client_ready_queues(self):
        """Assert factories with queues to create on startup work."""
        factory = FedoraMessagingFactory(None, queues=[{"queue": "my_queue"}])
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        d = factory._on_client_ready()

        def _check(_):
            mock_channel.queue_declare.assert_called_once_with(
                queue="my_queue", passive=False)
            self.assertTrue(factory._client_ready.called)

        d.addCallback(_check)

        return pytest_twisted.blockon(d)

    def test_on_client_ready_exchanges(self):
        """Assert factories with exchanges to create on startup work."""
        factory = FedoraMessagingFactory(None,
                                         exchanges=[{
                                             "exchange": "my_exchange"
                                         }])
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        d = factory._on_client_ready()

        def _check(_):
            mock_channel.exchange_declare.assert_called_once_with(
                exchange="my_exchange", passive=False)
            self.assertTrue(factory._client_ready.called)

        d.addCallback(_check)

        return pytest_twisted.blockon(d)

    def test_passive_declares(self):
        """Assert queues and exchanges are created passively when configured so."""
        factory = FedoraMessagingFactory(
            None,
            queues=[{
                "queue": "my_queue"
            }],
            exchanges=[{
                "exchange": "my_exchange"
            }],
        )
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        def _check(_):
            mock_channel.queue_declare.assert_called_once_with(
                queue="my_queue", passive=True)
            mock_channel.exchange_declare.assert_called_once_with(
                exchange="my_exchange", passive=True)

        with mock.patch.dict(config.conf, {"passive_declares": True}):
            d = factory._on_client_ready()
            d.addCallback(_check)

        return pytest_twisted.blockon(d)

    def test_on_client_ready_consumers(self):
        """Assert factories with bindings to create on startup work."""
        factory = FedoraMessagingFactory(None)
        factory.buildProtocol(None)
        factory.consume(lambda x: x, "my_queue")
        mock_channel = mock.Mock()
        mock_channel.basic_consume.return_value = mock.Mock(), None
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        d = factory._on_client_ready()

        def _check(_):
            mock_channel.basic_consume.assert_called()
            self.assertIn("my_queue", factory.client._consumers)
            self.assertTrue(factory._client_ready.called)

        d.addCallback(_check)

        return pytest_twisted.blockon(d)

    def test_on_client_ready_bindings(self):
        """Assert factories with bindings to create on startup work."""
        factory = FedoraMessagingFactory(None,
                                         bindings=[{
                                             "queue": "my_queue",
                                             "exchange": "my_exchange"
                                         }])
        factory.buildProtocol(None)
        mock_channel = mock.Mock()
        factory.client._allocate_channel = mock.Mock(return_value=mock_channel)

        d = factory._on_client_ready()

        def _check(_):
            mock_channel.queue_bind.assert_called_once_with(
                queue="my_queue", exchange="my_exchange")
            self.assertTrue(factory._client_ready.called)

        d.addCallback(_check)

        return pytest_twisted.blockon(d)

    def test_buildProtocol(self):
        # Check the buildProtocol method.
        protocol = self.factory.buildProtocol(None)
        self.assertTrue(protocol is self.protocol)
        self.assertTrue(self.factory.client is self.protocol)
        self.assertTrue(protocol.factory is self.factory)
        self.protocol.ready.callback(None)
        self.assertTrue(self.factory._client_ready.called)

    def test_clientConnectionLost(self):
        # The _client_ready deferred must be renewed when the connection is
        # lost.
        self.factory._client_ready.callback(None)
        self.factory.clientConnectionLost(mock.Mock(), Failure(RuntimeError()))
        self.assertFalse(self.factory._client_ready.called)

    @mock.patch(
        "fedora_messaging.twisted.factory.protocol.ReconnectingClientFactory."
        "clientConnectionFailed",
        mock.Mock(),
    )
    def test_connection_failed(self):
        """Assert when the connection fails it is logged."""
        with mock.patch("fedora_messaging.twisted.factory._legacy_twisted_log"
                        ) as mock_log:
            self.factory.clientConnectionFailed(None,
                                                mock.Mock(value="something"))
        mock_log.msg.assert_called_once_with(
            "Connection to the AMQP broker failed (something)",
            logLevel=logging.WARNING)

    def test_stopTrying(self):
        # The _client_ready deferred must errback when we stop trying to
        # reconnect.
        self.factory._client_ready.addCallbacks(
            self.fail, lambda f: f.trap(pika.exceptions.AMQPConnectionError))
        self.factory.stopTrying()
        return pytest_twisted.blockon(self.factory._client_ready)

    def test_stopFactory(self):
        # The protocol should be stopped when the factory is stopped.
        self.protocol.stopProducing.side_effect = lambda: defer.succeed(None)
        self.factory.buildProtocol(None)
        d = self.factory.stopFactory()

        def _check(_):
            self.protocol.stopProducing.assert_called_once()

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_consume(self):
        """Assert when there is an active protocol, consume calls are forwarded to it."""
        callback = mock.Mock()
        self.protocol.consume.side_effect = lambda cb, queue: defer.succeed(
            None)
        self.factory.client = self.protocol
        # Pretend the factory is ready to trigger protocol setup.
        self.factory._client_ready.callback(None)
        d = self.factory.consume(callback, "my_queue")

        def _check(_):
            self.assertEqual({"my_queue": callback}, self.factory.consumers)

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_consume_not_ready(self):
        """Assert when a client isn't ready, consume doesn't return a deferred."""
        # Check the consume method
        callback = mock.Mock()
        self.factory.client = self.protocol
        result = self.factory.consume(callback, "my_queue")
        self.assertIsNone(result)

    def test_cancel_not_ready(self):
        """Assert when a client isn't ready, cancel happens immediately."""
        self.factory.consume(mock.Mock(), "my_queue")

        result = self.factory.cancel("my_queue")

        self.assertIsNone(result)
        self.assertEqual({}, self.factory.consumers)

    def test_cancel_invalid(self):
        """Assert when a client isn't ready, cancel happens immediately."""
        cb = mock.Mock()
        self.factory.consume(cb, "my_queue")

        result = self.factory.cancel("my_other_queue")

        self.assertIsNone(result)
        self.assertEqual({"my_queue": cb}, self.factory.consumers)

    def test_cancel_with_client(self):
        """Assert when a client isn't ready, cancel happens immediately."""
        cb = mock.Mock()
        self.factory.consume(cb, "my_queue")
        self.factory.client = mock.Mock()

        self.factory.cancel("my_queue")

        self.factory.client.cancel.assert_called_once_with("my_queue")
        self.assertEqual({}, self.factory.consumers)

    def test_when_connected(self):
        """Assert whenConnected returns the current client once _client_ready fires"""
        self.factory.client = mock.Mock()
        self.factory._client_ready.callback(None)
        d = self.factory.whenConnected()
        d.addCallback(
            lambda client: self.assertEqual(self.factory.client, client))
        return pytest_twisted.blockon(d)

    def test_publish(self):
        """Assert publish forwards to the next available protocol instance."""
        self.factory.whenConnected = mock.Mock(
            return_value=defer.succeed(self.protocol))
        self.protocol.publish.side_effect = lambda *a: defer.succeed(None)
        d = self.factory.publish("test-message", "test-exchange")

        def _check(_):
            self.protocol.publish.assert_called_once_with(
                "test-message", "test-exchange")

        d.addCallback(_check)
        return pytest_twisted.blockon(d)

    def test_publish_connection_closed(self):
        """Assert publish retries when a connection error occurs."""
        self.factory.whenConnected = mock.Mock(side_effect=[
            defer.succeed(self.protocol),
            defer.succeed(self.protocol)
        ])
        self.protocol.publish.side_effect = [
            ConnectionException(reason="I wanted to"),
            defer.succeed(None),
        ]
        d = self.factory.publish("test-message", "test-exchange")

        def _check(_):
            self.assertEqual(
                [call[0] for call in self.protocol.publish.call_args_list],
                [("test-message", "test-exchange"),
                 ("test-message", "test-exchange")],
            )

        d.addCallback(_check)
        return pytest_twisted.blockon(d)
예제 #8
0
    def __init__(self):
        service.MultiService.__init__(self)
        self.email_producer = None
        self.irc_producer = None
        self.irc_client = None

        # Map queue names to service instances
        self._queues = {}
        self._irc_queues = {}
        self._email_queues = {}
        self._irc_services = []
        self._email_services = []

        db.initialize(config.conf)

        if config.conf["IRC_ENABLED"]:
            queues, bindings = self.get_queues(db.DeliveryType.irc)
            consumers = {q["queue"]: self._dispatch_irc for q in queues}
            producer = FedoraMessagingService(
                queues=queues, bindings=bindings, consumers=consumers)
            producer.setName("irc-{}".format(len(self._irc_services)))
            self._irc_services.append(producer)
            for queue in queues:
                self._queues[queue["queue"]] = producer
            self.addService(producer)

        if config.conf["EMAIL_ENABLED"]:
            queues, bindings = self.get_queues(db.DeliveryType.email)
            consumers = {q["queue"]: mail.deliver for q in queues}
            producer = FedoraMessagingService(
                queues=queues, bindings=bindings, consumers=consumers)
            producer.setName("email-{}".format(len(self._email_services)))
            self._email_services.append(producer)
            for queue in queues:
                self._queues[queue["queue"]] = producer
            self.addService(producer)

        amqp_endpoint = endpoints.clientFromString(
            reactor, 'tcp:localhost:5672'
        )
        params = pika.URLParameters('amqp://')
        control_queue = {
            "queue": "fedora-notifications-control-queue",
            "durable": True,
        }
        factory = FedoraMessagingFactory(
            params,
            queues=[control_queue],
        )
        factory.consume(self._manage_service, control_queue["queue"])
        self.amqp_service = internet.ClientService(amqp_endpoint, factory)
        self.addService(self.amqp_service)
        # TODO set up a listener for messages about new queues.
        # Then we need an API to poke a message service to start a new subscription
        # or stop an existing one.

        if self._irc_services:
            irc_endpoint = endpoints.clientFromString(
                reactor, config.conf["IRC_ENDPOINT"]
            )
            irc_factory = protocol.Factory.forProtocol(irc.IrcProtocol)
            self.irc_client = internet.ClientService(irc_endpoint, irc_factory)
            self.addService(self.irc_client)