Ejemplo n.º 1
0
class GoMessageHelper(object):
    implements(IHelper)

    def __init__(self, vumi_helper=None, **kw):
        self._msg_helper = MessageHelper(**kw)
        self.transport_name = self._msg_helper.transport_name
        self._vumi_helper = vumi_helper
        self.mdb = None
        if self._vumi_helper is not None:
            self.mdb = self._vumi_helper.get_vumi_api().mdb

    def setup(self):
        pass

    def cleanup(self):
        return self._msg_helper.cleanup()

    @proxyable
    def add_router_metadata(self, msg, router):
        msg.payload.setdefault('helper_metadata', {})
        md = MessageMetadataHelper(None, msg)
        md.set_router_info(router.router_type, router.key)
        md.set_user_account(router.user_account.key)

    @proxyable
    def add_conversation_metadata(self, msg, conv):
        msg.payload.setdefault('helper_metadata', {})
        md = MessageMetadataHelper(None, msg)
        md.set_conversation_info(conv.conversation_type, conv.key)
        md.set_user_account(conv.user_account.key)

    @proxyable
    def _add_go_metadata(self, msg, conv, router):
        if conv is not None:
            self.add_conversation_metadata(msg, conv)
        if router is not None:
            self.add_router_metadata(msg, router)

    @proxyable
    def _add_go_routing_metadata(self, msg, hops, outbound_hops):
        rmeta = RoutingMetadata(msg)
        if hops is not None:
            rmeta.set_hops(hops)
        if outbound_hops is not None:
            rmeta.set_outbound_hops(outbound_hops)

    @proxyable
    def make_inbound(self, content, conv=None, router=None,
                     hops=None, outbound_hops=None, **kw):
        msg = self._msg_helper.make_inbound(content, **kw)
        self._add_go_metadata(msg, conv, router)
        self._add_go_routing_metadata(msg, hops, outbound_hops)
        return msg

    @proxyable
    def make_outbound(self, content, conv=None, router=None,
                      hops=None, outbound_hops=None, **kw):
        msg = self._msg_helper.make_outbound(content, **kw)
        self._add_go_metadata(msg, conv, router)
        self._add_go_routing_metadata(msg, hops, outbound_hops)
        return msg

    @proxyable
    def make_ack(self, msg=None, conv=None, router=None,
                 hops=None, outbound_hops=None, **kw):
        ack = self._msg_helper.make_ack(msg, **kw)
        self._add_go_metadata(ack, conv, router)
        self._add_go_routing_metadata(ack, hops, outbound_hops)
        return ack

    @proxyable
    def make_nack(self, msg=None, conv=None, router=None,
                  hops=None, outbound_hops=None, **kw):
        nack = self._msg_helper.make_nack(msg, **kw)
        self._add_go_metadata(nack, conv, router)
        self._add_go_routing_metadata(nack, hops, outbound_hops)
        return nack

    @proxyable
    def make_delivery_report(self, msg=None, conv=None, router=None,
                             hops=None, outbound_hops=None, **kw):
        dr = self._msg_helper.make_delivery_report(msg, **kw)
        self._add_go_metadata(dr, conv, router)
        self._add_go_routing_metadata(dr, hops, outbound_hops)
        return dr

    @proxyable
    def make_reply(self, msg, content, **kw):
        return self._msg_helper.make_reply(msg, content, **kw)

    @proxyable
    def store_inbound(self, conv, msg):
        if self.mdb is None:
            raise ValueError("No message store provided.")
        return self.mdb.add_inbound_message(msg, batch_id=conv.batch.key)

    @proxyable
    def store_outbound(self, conv, msg):
        if self.mdb is None:
            raise ValueError("No message store provided.")
        return self.mdb.add_outbound_message(msg, batch_id=conv.batch.key)

    @proxyable
    def store_event(self, event):
        if self.mdb is None:
            raise ValueError("No message store provided.")
        return self.mdb.add_event(event)

    @proxyable
    def make_stored_inbound(self, conv, content, **kw):
        msg = self.make_inbound(content, conv=conv, **kw)
        return maybe_async_return(msg, self.store_inbound(conv, msg))

    @proxyable
    def make_stored_outbound(self, conv, content, **kw):
        msg = self.make_outbound(content, conv=conv, **kw)
        return maybe_async_return(msg, self.store_outbound(conv, msg))

    @proxyable
    def make_stored_ack(self, conv, msg, **kw):
        event = self.make_ack(msg, conv=conv, **kw)
        return maybe_async_return(event, self.store_event(event))

    @proxyable
    def make_stored_nack(self, conv, msg, **kw):
        event = self.make_nack(msg, conv=conv, **kw)
        return maybe_async_return(event, self.store_event(event))

    @proxyable
    def make_stored_delivery_report(self, conv, msg, **kw):
        event = self.make_delivery_report(msg, conv=conv, **kw)
        return maybe_async_return(event, self.store_event(event))

    @proxyable
    def add_inbound_to_conv(self, conv, count, start_date=None,
                            time_multiplier=10):
        now = start_date or datetime.now().date()

        messages = []
        for i in range(count):
            timestamp = now - timedelta(hours=i * time_multiplier)
            messages.append(self.make_stored_inbound(
                conv, "inbound %s" % (i,), from_addr='from-%s' % (i,),
                timestamp=timestamp))
        # We can't use `maybe_async_return` here because we need gatherResults.
        if isinstance(messages[0], Deferred):
            return gatherResults(messages)
        else:
            return messages

    @proxyable
    def add_outbound_to_conv(self, conv, count, start_date=None,
                             time_multiplier=10):
        now = start_date or datetime.now().date()

        messages = []
        for i in range(count):
            timestamp = now - timedelta(hours=i * time_multiplier)
            messages.append(self.make_stored_outbound(
                conv, "outbound %s" % (i,), to_addr='to-%s' % (i,),
                timestamp=timestamp))
        # We can't use `maybe_async_return` here because we need gatherResults.
        if isinstance(messages[0], Deferred):
            return gatherResults(messages)
        else:
            return messages

    @proxyable
    def add_replies_to_conv(self, conv, msgs):
        messages = []
        ds = []
        for msg in msgs:
            timestamp = msg['timestamp'] + timedelta(seconds=1)
            reply = self.make_reply(msg, "reply", timestamp=timestamp)
            messages.append(reply)
            ds.append(self.store_outbound(conv, reply))
        # We can't use `maybe_async_return` here because we need gatherResults.
        if isinstance(ds[0], Deferred):
            return gatherResults(ds).addCallback(lambda r: messages)
        else:
            return messages
Ejemplo n.º 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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
class GoMessageHelper(object):
    implements(IHelper)

    def __init__(self, vumi_helper=None, **kw):
        self._msg_helper = MessageHelper(**kw)
        self.transport_name = self._msg_helper.transport_name
        self._vumi_helper = vumi_helper
        self.mdb = None
        if self._vumi_helper is not None:
            self.mdb = self._vumi_helper.get_vumi_api().mdb

    def setup(self):
        pass

    def cleanup(self):
        return self._msg_helper.cleanup()

    @proxyable
    def add_router_metadata(self, msg, router):
        msg.payload.setdefault('helper_metadata', {})
        md = MessageMetadataHelper(None, msg)
        md.set_router_info(router.router_type, router.key)
        md.set_user_account(router.user_account.key)

    @proxyable
    def add_conversation_metadata(self, msg, conv):
        msg.payload.setdefault('helper_metadata', {})
        md = MessageMetadataHelper(None, msg)
        md.set_conversation_info(conv.conversation_type, conv.key)
        md.set_user_account(conv.user_account.key)

    @proxyable
    def _add_go_metadata(self, msg, conv, router):
        if conv is not None:
            self.add_conversation_metadata(msg, conv)
        if router is not None:
            self.add_router_metadata(msg, router)

    @proxyable
    def _add_go_routing_metadata(self, msg, hops, outbound_hops):
        rmeta = RoutingMetadata(msg)
        if hops is not None:
            rmeta.set_hops(hops)
        if outbound_hops is not None:
            rmeta.set_outbound_hops(outbound_hops)

    @proxyable
    def make_inbound(self,
                     content,
                     conv=None,
                     router=None,
                     hops=None,
                     outbound_hops=None,
                     **kw):
        msg = self._msg_helper.make_inbound(content, **kw)
        self._add_go_metadata(msg, conv, router)
        self._add_go_routing_metadata(msg, hops, outbound_hops)
        return msg

    @proxyable
    def make_outbound(self,
                      content,
                      conv=None,
                      router=None,
                      hops=None,
                      outbound_hops=None,
                      **kw):
        msg = self._msg_helper.make_outbound(content, **kw)
        self._add_go_metadata(msg, conv, router)
        self._add_go_routing_metadata(msg, hops, outbound_hops)
        return msg

    @proxyable
    def make_ack(self,
                 msg=None,
                 conv=None,
                 router=None,
                 hops=None,
                 outbound_hops=None,
                 **kw):
        ack = self._msg_helper.make_ack(msg, **kw)
        self._add_go_metadata(ack, conv, router)
        self._add_go_routing_metadata(ack, hops, outbound_hops)
        return ack

    @proxyable
    def make_nack(self,
                  msg=None,
                  conv=None,
                  router=None,
                  hops=None,
                  outbound_hops=None,
                  **kw):
        nack = self._msg_helper.make_nack(msg, **kw)
        self._add_go_metadata(nack, conv, router)
        self._add_go_routing_metadata(nack, hops, outbound_hops)
        return nack

    @proxyable
    def make_delivery_report(self,
                             msg=None,
                             conv=None,
                             router=None,
                             hops=None,
                             outbound_hops=None,
                             **kw):
        dr = self._msg_helper.make_delivery_report(msg, **kw)
        self._add_go_metadata(dr, conv, router)
        self._add_go_routing_metadata(dr, hops, outbound_hops)
        return dr

    @proxyable
    def make_reply(self, msg, content, **kw):
        return self._msg_helper.make_reply(msg, content, **kw)

    @proxyable
    def store_inbound(self, conv, msg):
        if self.mdb is None:
            raise ValueError("No message store provided.")
        return self.mdb.add_inbound_message(msg, batch_id=conv.batch.key)

    @proxyable
    def store_outbound(self, conv, msg):
        if self.mdb is None:
            raise ValueError("No message store provided.")
        return self.mdb.add_outbound_message(msg, batch_id=conv.batch.key)

    @proxyable
    def store_event(self, event):
        if self.mdb is None:
            raise ValueError("No message store provided.")
        return self.mdb.add_event(event)

    @proxyable
    def make_stored_inbound(self, conv, content, **kw):
        msg = self.make_inbound(content, conv=conv, **kw)
        return maybe_async_return(msg, self.store_inbound(conv, msg))

    @proxyable
    def make_stored_outbound(self, conv, content, **kw):
        msg = self.make_outbound(content, conv=conv, **kw)
        return maybe_async_return(msg, self.store_outbound(conv, msg))

    @proxyable
    def make_stored_ack(self, conv, msg, **kw):
        event = self.make_ack(msg, conv=conv, **kw)
        return maybe_async_return(event, self.store_event(event))

    @proxyable
    def make_stored_nack(self, conv, msg, **kw):
        event = self.make_nack(msg, conv=conv, **kw)
        return maybe_async_return(event, self.store_event(event))

    @proxyable
    def make_stored_delivery_report(self, conv, msg, **kw):
        event = self.make_delivery_report(msg, conv=conv, **kw)
        return maybe_async_return(event, self.store_event(event))

    @proxyable
    def add_inbound_to_conv(self,
                            conv,
                            count,
                            start_date=None,
                            time_multiplier=10):
        now = start_date or datetime.now().date()

        messages = []
        for i in range(count):
            timestamp = now - timedelta(hours=i * time_multiplier)
            messages.append(
                self.make_stored_inbound(conv,
                                         "inbound %s" % (i, ),
                                         from_addr='from-%s' % (i, ),
                                         timestamp=timestamp))
        # We can't use `maybe_async_return` here because we need gatherResults.
        if isinstance(messages[0], Deferred):
            return gatherResults(messages)
        else:
            return messages

    @proxyable
    def add_outbound_to_conv(self,
                             conv,
                             count,
                             start_date=None,
                             time_multiplier=10):
        now = start_date or datetime.now().date()

        messages = []
        for i in range(count):
            timestamp = now - timedelta(hours=i * time_multiplier)
            messages.append(
                self.make_stored_outbound(conv,
                                          "outbound %s" % (i, ),
                                          to_addr='to-%s' % (i, ),
                                          timestamp=timestamp))
        # We can't use `maybe_async_return` here because we need gatherResults.
        if isinstance(messages[0], Deferred):
            return gatherResults(messages)
        else:
            return messages

    @proxyable
    def add_replies_to_conv(self, conv, msgs):
        messages = []
        ds = []
        for msg in msgs:
            timestamp = msg['timestamp'] + timedelta(seconds=1)
            reply = self.make_reply(msg, "reply", timestamp=timestamp)
            messages.append(reply)
            ds.append(self.store_outbound(conv, reply))
        # We can't use `maybe_async_return` here because we need gatherResults.
        if isinstance(ds[0], Deferred):
            return gatherResults(ds).addCallback(lambda r: messages)
        else:
            return messages
Ejemplo n.º 5
0
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)