コード例 #1
0
ファイル: test_router.py プロジェクト: todun/junebug
class TestBaseRouterWorker(VumiTestCase, JunebugTestBase):
    DEFAULT_ROUTER_WORKER_CONFIG = {
        'inbound_ttl': 60,
        'outbound_ttl': 60 * 60 * 24 * 2,
        'metric_window': 1.0,
        'destinations': [],
    }

    @inlineCallbacks
    def setUp(self):
        self.workerhelper = WorkerHelper()
        self.addCleanup(self.workerhelper.cleanup)

        self.persistencehelper = PersistenceHelper()
        yield self.persistencehelper.setup()
        self.addCleanup(self.persistencehelper.cleanup)

        self.messagehelper = MessageHelper()
        self.addCleanup(self.messagehelper.cleanup)

    @inlineCallbacks
    def get_router_worker(self, config=None):
        if config is None:
            config = {}

        config = conjoin(
            self.persistencehelper.mk_config(
                self.DEFAULT_ROUTER_WORKER_CONFIG), config)

        TestRouter._create_worker = self.workerhelper.get_worker
        worker = yield self.workerhelper.get_worker(TestRouter, config)
        returnValue(worker)

    @inlineCallbacks
    def test_start_router_worker_no_destinations(self):
        """
        If there are no destinations specified, no workers should be started.
        The setup_router function should be called on the implementation.
        """
        worker = yield self.get_router_worker()
        self.assertEqual(len(worker.namedServices), 0)
        self.assertTrue(worker.setup_called)

    @inlineCallbacks
    def test_start_router_with_destinations(self):
        """
        If there are destinations specified, then a worker should be started
        for every destination.
        """
        worker = yield self.get_router_worker({
            'destinations': [
                {
                    'id': 'test-destination1',
                },
                {
                    'id': 'test-destination2',
                },
            ],
        })
        self.assertTrue(worker.setup_called)
        self.assertEqual(sorted(worker.namedServices.keys()),
                         ['test-destination1', 'test-destination2'])

        for connector in worker.connectors.values():
            self.assertFalse(connector.paused)

    @inlineCallbacks
    def test_teardown_router(self):
        """
        Tearing down a router should pause all connectors, and call the
        teardown method of the router implementation
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination1'
            }],
        })

        self.assertFalse(worker.teardown_called)
        for connector in worker.connectors.values():
            self.assertFalse(connector.paused)

        yield worker.teardown_worker()

        self.assertTrue(worker.teardown_called)
        for connector in worker.connectors.values():
            self.assertTrue(connector.paused)

    @inlineCallbacks
    def test_consume_channel(self):
        """
        consume_channel should set up the appropriate connector, as well as
        attach the specified callbacks for messages and events.
        """
        worker = yield self.get_router_worker({})

        messages = []
        events = []

        def message_callback(channelid, message):
            assert channelid == 'testchannel'
            messages.append(message)

        def event_callback(channelid, event):
            assert channelid == 'testchannel'
            events.append(event)

        yield worker.consume_channel('testchannel', message_callback,
                                     event_callback)

        # Because this is only called in setup, and we're creating connectors
        # after setup, we need to unpause them
        worker.unpause_connectors()

        self.assertEqual(messages, [])
        inbound = self.messagehelper.make_inbound('test message')
        yield self.workerhelper.dispatch_inbound(inbound, 'testchannel')
        self.assertEqual(messages, [inbound])

        self.assertEqual(events, [])
        event = self.messagehelper.make_ack()
        yield self.workerhelper.dispatch_event(event, 'testchannel')
        self.assertEqual(events, [event])

    @inlineCallbacks
    def test_send_inbound_to_destination(self):
        """
        send_inbound_to_destination should send the provided inbound message
        to the specified destination worker
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination',
                'amqp_queue': 'testqueue',
            }],
        })

        inbound = self.messagehelper.make_inbound('test_message')
        yield worker.send_inbound_to_destination('test-destination', inbound)

        [message] = yield self.workerhelper.wait_for_dispatched_inbound(
            connector_name='testqueue')
        self.assertEqual(message, inbound)

    @inlineCallbacks
    def test_send_event_to_destination(self):
        """
        send_event_to_destination should send the provided event message
        to the specified destination worker
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination',
                'amqp_queue': 'testqueue',
            }],
        })

        ack = self.messagehelper.make_ack()
        yield worker.send_event_to_destination('test-destination', ack)

        [event] = yield self.workerhelper.wait_for_dispatched_events(
            connector_name='testqueue')
        self.assertEqual(event, ack)

    @inlineCallbacks
    def test_consume_destination(self):
        """
        If a callback is attached to a destination, then that callback should
        be called when an outbound is sent from a destination
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination',
                'amqp_queue': 'testqueue',
            }],
        })

        messages = []

        def message_callback(destinationid, message):
            assert destinationid == 'test-destination'
            messages.append(message)

        yield worker.consume_destination('test-destination', message_callback)
        # Because this is only called in setup, and we're creating connectors
        # after setup, we need to unpause them
        worker.unpause_connectors()

        self.assertEqual(messages, [])
        msg = self.messagehelper.make_outbound('testmessage')
        yield self.workerhelper.dispatch_outbound(msg, 'test-destination')
        self.assertEqual(messages, [msg])

    @inlineCallbacks
    def test_send_outbound_to_channel(self):
        """
        send_outbound_to_channel should send the provided outbound message to
        the specified channel
        """
        worker = yield self.get_router_worker({})

        yield worker.consume_channel('testchannel', lambda m: m, lambda e: e)

        outbound = self.messagehelper.make_outbound('test message')
        yield worker.send_outbound_to_channel('testchannel', outbound)

        [message] = yield self.workerhelper.wait_for_dispatched_outbound(
            connector_name='testchannel')
        self.assertEqual(message, outbound)
コード例 #2
0
class TestRouter(JunebugTestBase):
    DEFAULT_ROUTER_WORKER_CONFIG = {
        'inbound_ttl': 60,
        'outbound_ttl': 60 * 60 * 24 * 2,
        'metric_window': 1.0,
        'destinations': [],
    }

    @inlineCallbacks
    def setUp(self):
        yield self.start_server()

        self.workerhelper = WorkerHelper()
        self.addCleanup(self.workerhelper.cleanup)

        self.persistencehelper = PersistenceHelper()
        yield self.persistencehelper.setup()
        self.addCleanup(self.persistencehelper.cleanup)

        self.messagehelper = MessageHelper()
        self.addCleanup(self.messagehelper.cleanup)

    @inlineCallbacks
    def get_router_worker(self, config=None):
        if config is None:
            config = {}

        config = conjoin(
            self.persistencehelper.mk_config(
                self.DEFAULT_ROUTER_WORKER_CONFIG), config)

        FromAddressRouter._create_worker = self.workerhelper.get_worker
        worker = yield self.workerhelper.get_worker(FromAddressRouter, config)
        returnValue(worker)

    @inlineCallbacks
    def test_validate_router_config_invalid_channel_uuid(self):
        """
        If the provided channel UUID is not a valid UUID a config error should
        be raised
        """
        with self.assertRaises(InvalidRouterConfig) as e:
            yield FromAddressRouter.validate_router_config(
                self.api, {'channel': "bad-uuid"})

        self.assertEqual(e.exception.message,
                         "Field 'channel' is not a valid UUID")

    @inlineCallbacks
    def test_validate_router_config_missing_channel(self):
        """
        If the provided channel UUID is not for an existing channel, a config
        error should be raised
        """
        channel_id = str(uuid.uuid4())

        with self.assertRaises(InvalidRouterConfig) as e:
            yield FromAddressRouter.validate_router_config(
                self.api, {'channel': channel_id})

        self.assertEqual(e.exception.message,
                         "Channel {} does not exist".format(channel_id))

    @inlineCallbacks
    def test_validate_router_config_existing_destination(self):
        """
        If the specified channel already has a destination specified, then
        a config error should be raised
        """
        channel = yield self.create_channel(self.api.service, self.redis)

        with self.assertRaises(InvalidRouterConfig) as e:
            yield FromAddressRouter.validate_router_config(
                self.api, {'channel': channel.id})

        self.assertEqual(
            e.exception.message,
            "Channel {} already has a destination specified".format(
                channel.id))

    @inlineCallbacks
    def test_validate_router_config_existing_router(self):
        """
        If an existing router is already listening to the specified channel,
        then a config error should be raised
        """
        channel = yield self.create_channel(self.api.service,
                                            self.redis,
                                            properties={
                                                'type': 'telnet',
                                                'config': {
                                                    'twisted_endpoint':
                                                    'tcp:0',
                                                },
                                            })

        config = self.create_router_config(config={
            'test': 'pass',
            'channel': channel.id
        })
        router = Router(self.api, config)
        yield router.save()
        router.start(self.api.service)

        with self.assertRaises(InvalidRouterConfig) as e:
            yield FromAddressRouter.validate_router_config(
                self.api, {'channel': channel.id})

        self.assertEqual(
            e.exception.message,
            "Router {} is already routing channel {}".format(
                router.id, channel.id))

    @inlineCallbacks
    def test_validate_router_destination_config_invalid_regex(self):
        """
        If invalid regex is passed into the regex field, a config error should
        be raised
        """
        with self.assertRaises(InvalidRouterDestinationConfig) as e:
            yield FromAddressRouter.validate_destination_config(
                self.api, {'regular_expression': "("})

        self.assertEqual(
            e.exception.message,
            "Field 'regular_expression' is not a valid regular expression: "
            "unbalanced parenthesis")

    @inlineCallbacks
    def test_validate_router_destination_config_missing_field(self):
        """
        regular_expression should be a required field
        """
        with self.assertRaises(InvalidRouterDestinationConfig) as e:
            yield FromAddressRouter.validate_destination_config(self.api, {})

        self.assertEqual(e.exception.message,
                         "Missing required config field 'regular_expression'")

    @inlineCallbacks
    def test_inbound_message_routing(self):
        """
        Inbound messages should be routed to the correct destination worker(s)
        """
        yield self.get_router_worker({
            'destinations': [{
                'id': "test-destination1",
                'amqp_queue': "testqueue1",
                'config': {
                    'regular_expression': '^1.*$'
                },
            }, {
                'id': "test-destination2",
                'amqp_queue': "testqueue2",
                'config': {
                    'regular_expression': '^2.*$'
                },
            }, {
                'id': "test-destination3",
                'amqp_queue': "testqueue3",
                'config': {
                    'regular_expression': '^2.*$'
                },
            }],
            'channel':
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14',
        })

        inbound = self.messagehelper.make_inbound('test message',
                                                  to_addr='1234')
        yield self.workerhelper.dispatch_inbound(
            inbound, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [message] = yield self.workerhelper.wait_for_dispatched_inbound(
            connector_name='testqueue1')
        self.assertEqual(inbound, message)

        inbound = self.messagehelper.make_inbound('test message',
                                                  to_addr='2234')
        yield self.workerhelper.dispatch_inbound(
            inbound, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [message] = yield self.workerhelper.wait_for_dispatched_inbound(
            connector_name='testqueue2')
        self.assertEqual(inbound, message)
        [message] = yield self.workerhelper.wait_for_dispatched_inbound(
            connector_name='testqueue3')
        self.assertEqual(inbound, message)

    @inlineCallbacks
    def test_inbound_message_routing_no_to_addr(self):
        """
        If an inbound message doesn't have a to address, then an error should
        be logged
        """
        yield self.get_router_worker({
            'channel':
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14',
        })
        logs = []
        log.addObserver(logs.append)

        inbound = self.messagehelper.make_inbound('test message', to_addr=None)
        yield self.workerhelper.dispatch_inbound(
            inbound, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [error_log] = logs
        self.assertIn("Message has no to address, cannot route message: ",
                      error_log['log_text'])

    @inlineCallbacks
    def test_inbound_event_routing(self):
        """
        Inbound events should be routed to the correct destination worker(s)
        """
        yield self.get_router_worker({
            'destinations': [{
                'id': "test-destination1",
                'amqp_queue': "testqueue1",
                'config': {
                    'regular_expression': '^1.*$'
                },
            }, {
                'id': "test-destination2",
                'amqp_queue': "testqueue2",
                'config': {
                    'regular_expression': '^2.*$'
                },
            }, {
                'id': "test-destination3",
                'amqp_queue': "testqueue3",
                'config': {
                    'regular_expression': '^2.*$'
                },
            }],
            'channel':
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14',
        })

        outbound = self.messagehelper.make_outbound("test message",
                                                    from_addr="1234")
        yield self.workerhelper.dispatch_outbound(outbound, 'testqueue1')
        ack = self.messagehelper.make_ack(outbound)
        yield self.workerhelper.dispatch_event(
            ack, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [event] = yield self.workerhelper.wait_for_dispatched_events(
            connector_name='testqueue1')
        self.assertEqual(ack, event)

        outbound = self.messagehelper.make_outbound("test message",
                                                    from_addr="2234")
        yield self.workerhelper.dispatch_outbound(outbound, 'testqueue2')
        ack = self.messagehelper.make_ack(outbound)
        yield self.workerhelper.dispatch_event(
            ack, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [event] = yield self.workerhelper.wait_for_dispatched_events(
            connector_name='testqueue2')
        self.assertEqual(ack, event)
        [event] = yield self.workerhelper.wait_for_dispatched_events(
            connector_name='testqueue3')
        self.assertEqual(ack, event)

    @inlineCallbacks
    def test_inbound_event_routing_no_inbound_message(self):
        """
        If no message can be found in the message store for the event, then an
        error message should be logged
        """
        yield self.get_router_worker({
            'destinations': [{
                'id': "test-destination1",
                'amqp_queue': "testqueue1",
                'config': {
                    'regular_expression': '^1.*$'
                },
            }],
            'channel':
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14',
        })
        logs = []
        log.addObserver(logs.append)

        ack = self.messagehelper.make_ack()
        yield self.workerhelper.dispatch_event(
            ack, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [error_log] = logs
        self.assertIn("Cannot find message", error_log['log_text'])
        self.assertIn("for event, not routing event: ", error_log['log_text'])

    @inlineCallbacks
    def test_inbound_event_routing_no_from_address(self):
        """
        If the message for an event doesn't have a from address, then an error
        message should be logged
        """
        yield self.get_router_worker({
            'destinations': [{
                'id': "test-destination1",
                'amqp_queue': "testqueue1",
                'config': {
                    'regular_expression': '^1.*$'
                },
            }],
            'channel':
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14',
        })
        logs = []
        log.addObserver(logs.append)

        outbound = self.messagehelper.make_outbound("test message",
                                                    from_addr=None)
        yield self.workerhelper.dispatch_outbound(outbound, 'testqueue1')
        ack = self.messagehelper.make_ack(outbound)
        yield self.workerhelper.dispatch_event(
            ack, '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        [error_log] = logs
        self.assertIn("Message has no from address, cannot route event: ",
                      error_log['log_text'])

    @inlineCallbacks
    def test_outbound_message_routing(self):
        """
        Outbound messages should be routed to the configured channel, no matter
        which destination they came from. They should also be stored so that
        events can be routed correctly.
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': "test-destination1",
                'amqp_queue': "testqueue1",
                'config': {
                    'regular_expression': '^1.*$'
                },
            }, {
                'id': "test-destination2",
                'amqp_queue': "testqueue2",
                'config': {
                    'regular_expression': '^2.*$'
                },
            }],
            'channel':
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14',
        })

        outbound = self.messagehelper.make_outbound('test message')
        yield self.workerhelper.dispatch_outbound(outbound, 'testqueue1')
        [message] = yield self.workerhelper.wait_for_dispatched_outbound(
            connector_name='41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        self.assertEqual(outbound, message)
        stored_message = yield worker.outbounds.load_message(
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14', outbound['message_id'])
        self.assertEqual(api_from_message(outbound), stored_message)

        yield self.workerhelper.clear_dispatched_outbound(
            connector_name='41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        outbound = self.messagehelper.make_outbound('test message')
        yield self.workerhelper.dispatch_outbound(outbound, 'testqueue2')
        [message] = yield self.workerhelper.wait_for_dispatched_outbound(
            connector_name='41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14')
        self.assertEqual(outbound, message)
        stored_message = yield worker.outbounds.load_message(
            '41e58f4a-2acc-442f-b3e5-3cf2b2f1cf14', outbound['message_id'])
        self.assertEqual(api_from_message(outbound), stored_message)
コード例 #3
0
ファイル: test_router.py プロジェクト: praekelt/junebug
class TestBaseRouterWorker(VumiTestCase, JunebugTestBase):
    DEFAULT_ROUTER_WORKER_CONFIG = {
        'inbound_ttl': 60,
        'outbound_ttl': 60 * 60 * 24 * 2,
        'metric_window': 1.0,
        'destinations': [],
    }

    @inlineCallbacks
    def setUp(self):
        self.workerhelper = WorkerHelper()
        self.addCleanup(self.workerhelper.cleanup)

        self.persistencehelper = PersistenceHelper()
        yield self.persistencehelper.setup()
        self.addCleanup(self.persistencehelper.cleanup)

        self.messagehelper = MessageHelper()
        self.addCleanup(self.messagehelper.cleanup)

    @inlineCallbacks
    def get_router_worker(self, config=None):
        if config is None:
            config = {}

        config = conjoin(
            self.persistencehelper.mk_config(
                self.DEFAULT_ROUTER_WORKER_CONFIG),
            config)

        TestRouter._create_worker = self.workerhelper.get_worker
        worker = yield self.workerhelper.get_worker(TestRouter, config)
        returnValue(worker)

    @inlineCallbacks
    def test_start_router_worker_no_destinations(self):
        """
        If there are no destinations specified, no workers should be started.
        The setup_router function should be called on the implementation.
        """
        worker = yield self.get_router_worker()
        self.assertEqual(len(worker.namedServices), 0)
        self.assertTrue(worker.setup_called)

    @inlineCallbacks
    def test_start_router_with_destinations(self):
        """
        If there are destinations specified, then a worker should be started
        for every destination.
        """
        worker = yield self.get_router_worker({
            'destinations': [
                {
                    'id': 'test-destination1',
                },
                {
                    'id': 'test-destination2',
                },
            ],
        })
        self.assertTrue(worker.setup_called)
        self.assertEqual(sorted(worker.namedServices.keys()), [
            'test-destination1', 'test-destination2'])

        for connector in worker.connectors.values():
            self.assertFalse(connector.paused)

    @inlineCallbacks
    def test_teardown_router(self):
        """
        Tearing down a router should pause all connectors, and call the
        teardown method of the router implementation
        """
        worker = yield self.get_router_worker({
            'destinations': [{'id': 'test-destination1'}],
        })

        self.assertFalse(worker.teardown_called)
        for connector in worker.connectors.values():
            self.assertFalse(connector.paused)

        yield worker.teardown_worker()

        self.assertTrue(worker.teardown_called)
        for connector in worker.connectors.values():
            self.assertTrue(connector.paused)

    @inlineCallbacks
    def test_consume_channel(self):
        """
        consume_channel should set up the appropriate connector, as well as
        attach the specified callbacks for messages and events.
        """
        worker = yield self.get_router_worker({})

        messages = []
        events = []

        def message_callback(channelid, message):
            assert channelid == 'testchannel'
            messages.append(message)

        def event_callback(channelid, event):
            assert channelid == 'testchannel'
            events.append(event)

        yield worker.consume_channel(
            'testchannel', message_callback, event_callback)

        # Because this is only called in setup, and we're creating connectors
        # after setup, we need to unpause them
        worker.unpause_connectors()

        self.assertEqual(messages, [])
        inbound = self.messagehelper.make_inbound('test message')
        yield self.workerhelper.dispatch_inbound(inbound, 'testchannel')
        self.assertEqual(messages, [inbound])

        self.assertEqual(events, [])
        event = self.messagehelper.make_ack()
        yield self.workerhelper.dispatch_event(event, 'testchannel')
        self.assertEqual(events, [event])

    @inlineCallbacks
    def test_send_inbound_to_destination(self):
        """
        send_inbound_to_destination should send the provided inbound message
        to the specified destination worker
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination',
                'amqp_queue': 'testqueue',
            }],
        })

        inbound = self.messagehelper.make_inbound('test_message')
        yield worker.send_inbound_to_destination('test-destination', inbound)

        [message] = yield self.workerhelper.wait_for_dispatched_inbound(
            connector_name='testqueue')
        self.assertEqual(message, inbound)

    @inlineCallbacks
    def test_send_event_to_destination(self):
        """
        send_event_to_destination should send the provided event message
        to the specified destination worker
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination',
                'amqp_queue': 'testqueue',
            }],
        })

        ack = self.messagehelper.make_ack()
        yield worker.send_event_to_destination('test-destination', ack)

        [event] = yield self.workerhelper.wait_for_dispatched_events(
            connector_name='testqueue')
        self.assertEqual(event, ack)

    @inlineCallbacks
    def test_consume_destination(self):
        """
        If a callback is attached to a destination, then that callback should
        be called when an outbound is sent from a destination
        """
        worker = yield self.get_router_worker({
            'destinations': [{
                'id': 'test-destination',
                'amqp_queue': 'testqueue',
            }],
        })

        messages = []

        def message_callback(destinationid, message):
            assert destinationid == 'test-destination'
            messages.append(message)

        yield worker.consume_destination('test-destination', message_callback)
        # Because this is only called in setup, and we're creating connectors
        # after setup, we need to unpause them
        worker.unpause_connectors()

        self.assertEqual(messages, [])
        msg = self.messagehelper.make_outbound('testmessage')
        yield self.workerhelper.dispatch_outbound(msg, 'test-destination')
        self.assertEqual(messages, [msg])

    @inlineCallbacks
    def test_send_outbound_to_channel(self):
        """
        send_outbound_to_channel should send the provided outbound message to
        the specified channel
        """
        worker = yield self.get_router_worker({})

        yield worker.consume_channel('testchannel', lambda m: m, lambda e: e)

        outbound = self.messagehelper.make_outbound('test message')
        yield worker.send_outbound_to_channel('testchannel', outbound)

        [message] = yield self.workerhelper.wait_for_dispatched_outbound(
            connector_name='testchannel')
        self.assertEqual(message, outbound)