def get_worker(self, config=None):
        """Get a new MessageForwardingWorker with the provided config"""
        if config is None:
            config = {}

        app_helper = ApplicationHelper(MessageForwardingWorker)
        yield app_helper.setup()
        self.addCleanup(app_helper.cleanup)

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

        config = conjoin(
            persistencehelper.mk_config(
                {
                    "transport_name": "testtransport",
                    "mo_message_url": self.url.decode("utf-8"),
                    "inbound_ttl": 60,
                    "outbound_ttl": 60 * 60 * 24 * 2,
                }
            ),
            config,
        )

        worker = yield app_helper.get_application(config)
        returnValue(worker)
Exemple #2
0
    def get_worker(self, config=None):
        '''Get a new MessageForwardingWorker with the provided config'''
        if config is None:
            config = {}

        self.app_helper = ApplicationHelper(MessageForwardingWorker)
        yield self.app_helper.setup()
        self.addCleanup(self.app_helper.cleanup)

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

        config = conjoin(
            persistencehelper.mk_config({
                'transport_name':
                'testtransport',
                'mo_message_url':
                self.url.decode('utf-8'),
                'inbound_ttl':
                60,
                'outbound_ttl':
                60 * 60 * 24 * 2,
                'metric_window':
                1.0,
            }), config)

        worker = yield self.app_helper.get_application(config)
        returnValue(worker)
Exemple #3
0
 def get_redis(self):
     '''Creates and returns a redis manager'''
     if hasattr(self, 'redis'):
         returnValue(self.redis)
     persistencehelper = PersistenceHelper()
     yield persistencehelper.setup()
     self.redis = yield persistencehelper.get_redis_manager()
     self.addCleanup(persistencehelper.cleanup)
     returnValue(self.redis)
Exemple #4
0
 def get_redis(self):
     '''Creates and returns a redis manager'''
     if hasattr(self, 'redis'):
         returnValue(self.redis)
     persistencehelper = PersistenceHelper()
     yield persistencehelper.setup()
     self.redis = yield persistencehelper.get_redis_manager()
     self.addCleanup(persistencehelper.cleanup)
     returnValue(self.redis)
Exemple #5
0
    def get_worker(self, config=None):
        '''Get a new ChannelStatusWorker with the provided config'''
        if config is None:
            config = {}

        app_helper = ApplicationHelper(ChannelStatusWorker)
        yield app_helper.setup()
        self.addCleanup(app_helper.cleanup)

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

        config = conjoin(persistencehelper.mk_config({
            'channel_id': 'testchannel',
        }), config)

        worker = yield app_helper.get_application(config)
        returnValue(worker)
    def get_worker(self, config=None):
        '''Get a new ChannelStatusWorker with the provided config'''
        if config is None:
            config = {}

        app_helper = ApplicationHelper(ChannelStatusWorker)
        yield app_helper.setup()
        self.addCleanup(app_helper.cleanup)

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

        config = conjoin(persistencehelper.mk_config({
            'channel_id': 'testchannel',
        }), config)

        worker = yield app_helper.get_application(config)
        returnValue(worker)
    def get_worker(self, config=None):
        '''Get a new MessageForwardingWorker with the provided config'''
        if config is None:
            config = {}

        self.app_helper = ApplicationHelper(MessageForwardingWorker)
        yield self.app_helper.setup()
        self.addCleanup(self.app_helper.cleanup)

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

        config = conjoin(persistencehelper.mk_config({
            'transport_name': 'testtransport',
            'mo_message_url': self.url.decode('utf-8'),
            'inbound_ttl': 60,
            'outbound_ttl': 60 * 60 * 24 * 2,
            'metric_window': 1.0,
        }), config)

        worker = yield self.app_helper.get_application(config)
        returnValue(worker)
Exemple #8
0
class JunebugTestBase(TestCase):
    '''Base test case that all junebug tests inherit from. Contains useful
    helper functions'''

    default_channel_properties = {
        'type': 'telnet',
        'config': {
            'twisted_endpoint': 'tcp:0',
        },
        'mo_url': 'http://foo.bar',
    }

    default_channel_config = {
        'ttl': 60,
        'amqp': {},
    }

    def patch_logger(self):
        ''' Patches the logger with an in-memory logger, which is acccessable
        at "self.logging_handler".'''
        self.logging_handler = logging.handlers.MemoryHandler(100)
        logging.getLogger().addHandler(self.logging_handler)
        self.addCleanup(self._cleanup_logging_patch)

    def patch_message_rate_clock(self):
        '''Patches the message rate clock, and returns the clock'''
        clock = Clock()
        self.patch(MessageRateStore, 'get_seconds', lambda _: clock.seconds())
        return clock

    def _cleanup_logging_patch(self):
        self.logging_handler.close()
        logging.getLogger().removeHandler(self.logging_handler)

    def create_channel_properties(self, **kw):
        properties = deepcopy(self.default_channel_properties)
        properties.update(kw)
        return properties

    @inlineCallbacks
    def create_channel_config(self, **kw):
        self.persistencehelper = PersistenceHelper()
        yield self.persistencehelper.setup()
        self.addCleanup(self.persistencehelper.cleanup)

        config = deepcopy(self.default_channel_config)
        config.update(kw)
        channel_config = self.persistencehelper.mk_config(config)
        channel_config['redis'] = channel_config['redis_manager']
        returnValue(JunebugConfig(channel_config))

    @inlineCallbacks
    def create_channel(
            self, service, redis, transport_class=None,
            properties=default_channel_properties, id=None, config=None,
            plugins=[]):
        '''Creates and starts, and saves a channel, with a
        TelnetServerTransport transport'''
        self.patch(junebug.logging_service, 'LogFile', DummyLogFile)
        if transport_class is None:
            transport_class = 'vumi.transports.telnet.TelnetServerTransport'

        properties = deepcopy(properties)
        logpath = self.mktemp()
        if config is None:
            config = yield self.create_channel_config(
                channels={
                    properties['type']: transport_class
                },
                logging_path=logpath)

        channel = Channel(
            redis, config, properties, id=id, plugins=plugins)
        yield channel.start(self.service)

        properties['config']['transport_name'] = channel.id

        yield channel.save()
        self.addCleanup(channel.stop)
        returnValue(channel)

    @inlineCallbacks
    def create_channel_from_id(self, redis, config, id, service):
        '''Creates an existing channel given the channel id'''
        config = yield self.create_channel_config(**config)
        channel = yield Channel.from_id(redis, config, id, service)
        returnValue(channel)

    @inlineCallbacks
    def get_redis(self):
        '''Creates and returns a redis manager'''
        if hasattr(self, 'redis'):
            returnValue(self.redis)
        persistencehelper = PersistenceHelper()
        yield persistencehelper.setup()
        self.redis = yield persistencehelper.get_redis_manager()
        self.addCleanup(persistencehelper.cleanup)
        returnValue(self.redis)

    @inlineCallbacks
    def start_server(self, config=None):
        '''Starts a junebug server. Stores the service to "self.service", and
        the url at "self.url"'''
        # TODO: This setup is very manual, because we don't call
        # service.startService. This must be fixed to close mirror the real
        # program with our tests.
        if config is None:
            config = yield self.create_channel_config()
        self.service = JunebugService(config)
        self.api = JunebugApi(
            self.service, config)
        self.service.api = self.api

        redis = yield self.get_redis()
        yield self.api.setup(redis, self.get_message_sender())

        self.config = self.api.config
        self.redis = self.api.redis
        self.inbounds = self.api.inbounds
        self.outbounds = self.api.outbounds
        self.message_sender = self.api.message_sender

        port = reactor.listenTCP(
            0, Site(self.api.app.resource()),
            interface='127.0.0.1')
        self.service._port = port
        self.addCleanup(self.stop_server)
        addr = port.getHost()
        self.url = "http://%s:%s" % (addr.host, addr.port)

    @inlineCallbacks
    def stop_server(self):
        # TODO: This teardown is very messy, because we don't actually call
        # service.startService. This needs to be fixed in order to ensure that
        # our tests are mirroring the real program closely.
        yield self.service.stopService()
        for service in self.service:
            service.disownServiceParent()
        for service in self.service.namedServices.values():
            service.disownServiceParent()

    def get_message_sender(self):
        '''Creates a new MessageSender object, with a fake amqp client'''
        message_sender = MessageSender('amqp-spec-0-8.xml', None)
        spec = get_spec(vumi_resource_path('amqp-spec-0-8.xml'))
        client = FakeAmqpClient(spec)
        message_sender.client = client
        return message_sender

    def get_dispatched_messages(self, queue):
        '''Gets all messages that have been dispatched to the amqp broker.
        Should only be called after start_server, as it looks in the api for
        the amqp client'''
        amqp_client = self.api.message_sender.client
        return amqp_client.broker.get_messages(
            'vumi', queue)

    def assert_was_logged(self, msg):
        self.assertTrue(any(
            msg in log.getMessage()
            for log in self.logging_handler.buffer))

    def assert_request(self, req, method=None, body=None, headers=None):
        if method is not None:
            self.assertEqual(req['request'].method, 'POST')

        if headers is not None:
            for name, values in headers.iteritems():
                self.assertEqual(
                    req['request'].requestHeaders.getRawHeaders(name),
                    values)

        if body is not None:
            self.assertEqual(json.loads(req['body']), body)

    def assert_body_contains(self, req, **fields):
        body = json.loads(req['body'])

        self.assertEqual(
            dict((k, v) for k, v in body.iteritems() if k in fields),
            fields)

    def assert_log(self, log, expected):
        '''Assert that a log matches what is expected.'''
        timestamp = log.pop('timestamp')
        self.assertTrue(isinstance(timestamp, float))
        self.assertEqual(log, expected)

    def generate_status(
            self, level=None, components={}, inbound_message_rate=0,
            outbound_message_rate=0, submitted_event_rate=0,
            rejected_event_rate=0, delivery_succeeded_rate=0,
            delivery_failed_rate=0, delivery_pending_rate=0):
        '''Generates a status that the http API would respond with, given the
        same parameters'''
        return {
            'status': level,
            'components': components,
            'inbound_message_rate': inbound_message_rate,
            'outbound_message_rate': outbound_message_rate,
            'submitted_event_rate': submitted_event_rate,
            'rejected_event_rate': rejected_event_rate,
            'delivery_succeeded_rate': delivery_succeeded_rate,
            'delivery_failed_rate': delivery_failed_rate,
            'delivery_pending_rate': delivery_pending_rate,
        }

    def assert_status(self, status, **kwargs):
        '''Assert that the current channel status is correct'''
        self.assertEqual(status, self.generate_status(**kwargs))
Exemple #9
0
class JunebugTestBase(TestCase):
    '''Base test case that all junebug tests inherit from. Contains useful
    helper functions'''

    default_channel_properties = {
        'type': 'telnet',
        'config': {
            'twisted_endpoint': 'tcp:0',
        },
        'mo_url': 'http://foo.bar',
    }

    default_router_properties = {
        'type': 'testing',
        'config': {
            'test': 'pass',
        },
    }

    default_destination_properties = {
        'config': {
            'target': 'valid',
        },
    }

    default_channel_config = {
        'ttl': 60,
        'routers': {
            'testing': 'junebug.tests.helpers.TestRouter',
        }
    }

    def patch_logger(self):
        ''' Patches the logger with an in-memory logger, which is acccessable
        at "self.logging_handler".'''
        self.logging_handler = logging.handlers.MemoryHandler(100)
        logging.getLogger().addHandler(self.logging_handler)
        self.addCleanup(self._cleanup_logging_patch)

    def patch_message_rate_clock(self):
        '''Patches the message rate clock, and returns the clock'''
        clock = Clock()
        self.patch(MessageRateStore, 'get_seconds', lambda _: clock.seconds())
        return clock

    def _cleanup_logging_patch(self):
        self.logging_handler.close()
        logging.getLogger().removeHandler(self.logging_handler)

    def create_channel_properties(self, **kw):
        properties = deepcopy(self.default_channel_properties)
        config = kw.pop('config', {})
        properties['config'].update(config)
        properties.update(kw)
        return properties

    @inlineCallbacks
    def create_channel_config(self, **kw):
        self.persistencehelper = PersistenceHelper()
        yield self.persistencehelper.setup()
        self.addCleanup(self.persistencehelper.cleanup)

        config = deepcopy(self.default_channel_config)
        config.update(kw)
        channel_config = self.persistencehelper.mk_config(config)
        channel_config['redis'] = channel_config['redis_manager']
        returnValue(JunebugConfig(channel_config))

    def create_router_config(self, **kw):
        properties = deepcopy(self.default_router_properties)
        config = kw.pop('config', {})
        properties['config'].update(config)
        properties.update(kw)
        return properties

    def create_destination_config(self, **kw):
        properties = deepcopy(self.default_destination_properties)
        properties.update(kw)
        return properties

    @inlineCallbacks
    def create_channel(self,
                       service,
                       redis,
                       transport_class=None,
                       properties=default_channel_properties,
                       id=None,
                       config=None,
                       plugins=[]):
        '''Creates and starts, and saves a channel, with a
        TelnetServerTransport transport'''
        self.patch(junebug.logging_service, 'LogFile', DummyLogFile)
        if transport_class is None:
            transport_class = 'vumi.transports.telnet.TelnetServerTransport'

        properties = deepcopy(properties)
        logpath = self.mktemp()
        if config is None:
            config = yield self.create_channel_config(
                channels={properties['type']: transport_class},
                logging_path=logpath)

        channel = Channel(redis, config, properties, id=id, plugins=plugins)
        yield channel.start(self.service)

        properties['config']['transport_name'] = channel.id

        yield channel.save()
        self.addCleanup(channel.stop)
        returnValue(channel)

    @inlineCallbacks
    def create_channel_from_id(self, redis, config, id, service):
        '''Creates an existing channel given the channel id'''
        config = yield self.create_channel_config(**config)
        channel = yield Channel.from_id(redis, config, id, service)
        returnValue(channel)

    @inlineCallbacks
    def get_redis(self):
        '''Creates and returns a redis manager'''
        if hasattr(self, 'redis'):
            returnValue(self.redis)
        persistencehelper = PersistenceHelper()
        yield persistencehelper.setup()
        self.redis = yield persistencehelper.get_redis_manager()
        self.addCleanup(persistencehelper.cleanup)
        returnValue(self.redis)

    @inlineCallbacks
    def start_server(self, config=None):
        '''Starts a junebug server. Stores the service to "self.service", and
        the url at "self.url"'''
        # TODO: This setup is very manual, because we don't call
        # service.startService. This must be fixed to close mirror the real
        # program with our tests.
        if config is None:
            config = yield self.create_channel_config()
        self.service = JunebugService(config)
        self.api = JunebugApi(self.service, config)
        self.service.api = self.api

        redis = yield self.get_redis()
        yield self.api.setup(redis, self.get_message_sender())

        self.config = self.api.config
        self.redis = self.api.redis
        self.inbounds = self.api.inbounds
        self.outbounds = self.api.outbounds
        self.message_sender = self.api.message_sender

        port = reactor.listenTCP(0,
                                 Site(self.api.app.resource()),
                                 interface='127.0.0.1')
        self.service._port = port
        self.addCleanup(self.stop_server)
        addr = port.getHost()
        self.url = "http://%s:%s" % (addr.host, addr.port)

    @inlineCallbacks
    def stop_server(self):
        # TODO: This teardown is very messy, because we don't actually call
        # service.startService. This needs to be fixed in order to ensure that
        # our tests are mirroring the real program closely.
        yield self.service.stopService()
        for service in self.service:
            service.disownServiceParent()
        for service in self.service.namedServices.values():
            service.disownServiceParent()

    def get_message_sender(self):
        '''Creates a new MessageSender object, with a fake amqp client'''
        message_sender = MessageSender('amqp-spec-0-8.xml', None)
        spec = get_spec(vumi_resource_path('amqp-spec-0-8.xml'))
        client = FakeAmqpClient(spec)
        message_sender.client = client
        return message_sender

    def get_dispatched_messages(self, queue):
        '''Gets all messages that have been dispatched to the amqp broker.
        Should only be called after start_server, as it looks in the api for
        the amqp client'''
        amqp_client = self.api.message_sender.client
        return amqp_client.broker.get_messages('vumi', queue)

    def assert_was_logged(self, msg):
        self.assertTrue(
            any(msg in log.getMessage()
                for log in self.logging_handler.buffer))

    def assert_request(self, req, method=None, body=None, headers=None):
        if method is not None:
            self.assertEqual(req['request'].method, 'POST')

        if headers is not None:
            for name, values in headers.iteritems():
                self.assertEqual(
                    req['request'].requestHeaders.getRawHeaders(name), values)

        if body is not None:
            self.assertEqual(json.loads(req['body']), body)

    def assert_body_contains(self, req, **fields):
        body = json.loads(req['body'])

        self.assertEqual(
            dict((k, v) for k, v in body.iteritems() if k in fields), fields)

    def assert_log(self, log, expected):
        '''Assert that a log matches what is expected.'''
        timestamp = log.pop('timestamp')
        self.assertTrue(isinstance(timestamp, float))
        self.assertEqual(log, expected)

    def generate_status(self,
                        level=None,
                        components={},
                        inbound_message_rate=0,
                        outbound_message_rate=0,
                        submitted_event_rate=0,
                        rejected_event_rate=0,
                        delivery_succeeded_rate=0,
                        delivery_failed_rate=0,
                        delivery_pending_rate=0):
        '''Generates a status that the http API would respond with, given the
        same parameters'''
        return {
            'status': level,
            'components': components,
            'inbound_message_rate': inbound_message_rate,
            'outbound_message_rate': outbound_message_rate,
            'submitted_event_rate': submitted_event_rate,
            'rejected_event_rate': rejected_event_rate,
            'delivery_succeeded_rate': delivery_succeeded_rate,
            'delivery_failed_rate': delivery_failed_rate,
            'delivery_pending_rate': delivery_pending_rate,
        }

    def assert_status(self, status, **kwargs):
        '''Assert that the current channel status is correct'''
        self.assertEqual(status, self.generate_status(**kwargs))
Exemple #10
0
class TransportHelper(object):
    """
    Test helper for transport workers.

    This helper construct and wraps several lower-level helpers and provides
    higher-level functionality for transport tests.

    :param transport_class:
        The worker class for the transport being tested.

    :param bool use_riak:
        Set to ``True`` if the test requires Riak. This is passed to the
        underlying :class:`~vumi.tests.helpers.PersistenceHelper`.

    :param \**msg_helper_args:
        All other keyword params are passed to the underlying
        :class:`~vumi.tests.helpers.MessageHelper`.
    """

    implements(IHelper)

    def __init__(self, transport_class, use_riak=False, **msg_helper_args):
        self.transport_class = transport_class
        self.persistence_helper = PersistenceHelper(use_riak=use_riak)
        self.msg_helper = MessageHelper(**msg_helper_args)
        self.transport_name = self.msg_helper.transport_name
        self.worker_helper = WorkerHelper(self.transport_name)
        self.dispatch_helper = MessageDispatchHelper(self.msg_helper,
                                                     self.worker_helper)

        # Proxy methods from our helpers.
        generate_proxies(self, self.msg_helper)
        generate_proxies(self, self.worker_helper)
        generate_proxies(self, self.dispatch_helper)
        generate_proxies(self, self.persistence_helper)

    def setup(self):
        self.persistence_helper.setup()
        self.worker_helper.setup()

    @inlineCallbacks
    def cleanup(self):
        yield self.worker_helper.cleanup()
        yield self.persistence_helper.cleanup()

    def get_transport(self, config, cls=None, start=True):
        """
        Get an instance of a transport class.

        :param config: Config dict.
        :param cls: The transport class to instantiate.
                    Defaults to :attr:`transport_class`
        :param start: True to start the transport (default), False otherwise.

        Some default config values are helpfully provided in the
        interests of reducing boilerplate:

        * ``transport_name`` defaults to :attr:`self.transport_name`
        """

        if cls is None:
            cls = self.transport_class
        config = self.mk_config(config)
        config.setdefault('transport_name', self.transport_name)
        return self.get_worker(cls, config, start)

    def get_dispatched_failures(self, connector_name=None):
        """
        Get failures dispatched by a transport.

        :param str connector_name:
            Connector name. If ``None``, the default connector name for the
            helper instance will be used.

        :returns:
            A list of :class:`~vumi.transports.failures.FailureMessage`
            instances.
        """
        return self.get_dispatched(connector_name, 'failures', FailureMessage)
Exemple #11
0
class VumiApiHelper(object):
    # TODO: Clear bucket properties.
    #       We need two things for this:
    #        * The ability to clear bucket properties in our Riak layer.
    #        * Tracking accounts created so we know which buckets to clear.
    #
    #       The first needs to happen in vumi and requires an updated Riak
    #       client. The second isn't really worth doing unitl the first is
    #       done.

    implements(IHelper)

    def __init__(self, is_sync=False, use_riak=True):
        self.is_sync = is_sync
        self._patch_helper = PatchHelper()
        generate_proxies(self, self._patch_helper)

        self._persistence_helper = PersistenceHelper(
            use_riak=use_riak, is_sync=is_sync)
        self.broker = None  # Will be replaced by the first worker_helper.
        self._worker_helpers = {}
        self._users_created = 0
        self._user_helpers = {}
        self._vumi_api = None

        generate_proxies(self, self._persistence_helper)

    def setup(self, setup_vumi_api=True):
        self._persistence_helper.setup()
        if self.is_sync:
            self._django_amqp_setup()
        if setup_vumi_api:
            return self.setup_vumi_api()

    @maybe_async
    def cleanup(self):
        for worker_helper in self._worker_helpers.values():
            # All of these will wait for the same broker, but that's fine.
            yield worker_helper.cleanup()
        yield self._persistence_helper.cleanup()
        self._patch_helper.cleanup()

    def _django_amqp_setup(self):
        import go.base.amqp
        import go.base.utils
        # We might need an AMQP connection at some point.
        broker = self.get_worker_helper().broker
        broker.exchange_declare('vumi', 'direct')
        self.django_amqp_connection = FakeAmqpConnection(broker)
        self.monkey_patch(
            go.base.utils, 'connection', self.django_amqp_connection)
        self.monkey_patch(
            go.base.amqp, 'connection', self.django_amqp_connection)

    def get_worker_helper(self, connector_name=None):
        if connector_name not in self._worker_helpers:
            worker_helper = WorkerHelper(connector_name, self.broker)
            # If this is our first worker helper, we need to grab the broker it
            # created. If it isn't, its broker will be self.broker anyway.
            self.broker = worker_helper.broker
            self._worker_helpers[connector_name] = worker_helper
        return self._worker_helpers[connector_name]

    @proxyable
    def get_vumi_api(self):
        assert self._vumi_api is not None, "No vumi_api provided."
        return self._vumi_api

    @proxyable
    def set_vumi_api(self, vumi_api):
        assert self._vumi_api is None, "Can't override existing vumi_api."
        self._vumi_api = vumi_api
        # TODO: Find a nicer way to give everything the same fake redis.
        pcfg = self._persistence_helper._config_overrides
        pcfg['redis_manager']['FAKE_REDIS'] = vumi_api.redis

    @proxyable
    def setup_vumi_api(self):
        if self.is_sync:
            return self.setup_sync_vumi_api()
        else:
            return self.setup_async_vumi_api()

    def setup_sync_vumi_api(self):
        from django.conf import settings
        import go.base.amqp
        self._vumi_api = VumiApi.from_config_sync(
            settings.VUMI_API_CONFIG, go.base.amqp.connection)

    def setup_async_vumi_api(self):
        worker_helper = self.get_worker_helper()
        amqp_client = worker_helper.get_fake_amqp_client(worker_helper.broker)
        d = amqp_client.start_publisher(ApiCommandPublisher)
        d.addCallback(lambda cmd_publisher: VumiApi.from_config_async(
            self.mk_config({}), cmd_publisher))
        return d.addCallback(self.set_vumi_api)

    @proxyable
    @maybe_async
    def make_user(self, username, enable_search=True, django_user_pk=None):
        # NOTE: We use bytes instead of unicode here because that's what the
        #       real new_user gives us.
        key = "test-%s-user" % (len(self._user_helpers),)
        user = self.get_vumi_api().account_store.users(key, username=username)
        yield user.save()
        user_helper = UserApiHelper(self, key, django_user_pk=django_user_pk)
        self._user_helpers[key] = user_helper
        if enable_search:
            contact_store = user_helper.user_api.contact_store
            yield contact_store.contacts.enable_search()
            yield contact_store.groups.enable_search()
        returnValue(self.get_user_helper(user.key))

    @proxyable
    def get_user_helper(self, account_key):
        return self._user_helpers[account_key]

    @proxyable
    @maybe_async
    def get_or_create_user(self):
        assert len(self._user_helpers) <= 1, "Too many users."
        if not self._user_helpers:
            yield self.make_user(u"testuser")
        returnValue(self._user_helpers.values()[0])

    @proxyable
    @maybe_async
    def setup_tagpool(self, pool, tags, metadata=None):
        tags = [(pool, tag) for tag in tags]
        yield self.get_vumi_api().tpm.declare_tags(tags)
        if metadata:
            yield self.get_vumi_api().tpm.set_metadata(pool, metadata)
        returnValue(tags)

    def get_dispatched_commands(self):
        return self.get_worker_helper().get_dispatched(
            'vumi', 'api', VumiApiCommand)
Exemple #12
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)
Exemple #13
0
class DispatcherHelper(object):
    """
    Test helper for dispatcher workers.

    This helper construct and wraps several lower-level helpers and provides
    higher-level functionality for dispatcher tests.

    :param dispatcher_class:
        The worker class for the dispatcher being tested.

    :param bool use_riak:
        Set to ``True`` if the test requires Riak. This is passed to the
        underlying :class:`~vumi.tests.helpers.PersistenceHelper`.

    :param \**msg_helper_args:
        All other keyword params are passed to the underlying
        :class:`~vumi.tests.helpers.MessageHelper`.
    """

    implements(IHelper)

    def __init__(self, dispatcher_class, use_riak=False, **msg_helper_args):
        self.dispatcher_class = dispatcher_class
        self.worker_helper = WorkerHelper()
        self.persistence_helper = PersistenceHelper(use_riak=use_riak)
        self.msg_helper = MessageHelper(**msg_helper_args)
        self.dispatch_helper = MessageDispatchHelper(self.msg_helper,
                                                     self.worker_helper)

        # Proxy methods from our helpers.
        generate_proxies(self, self.msg_helper)
        generate_proxies(self, self.worker_helper)
        generate_proxies(self, self.dispatch_helper)

    def setup(self):
        self.persistence_helper.setup()
        self.worker_helper.setup()

    @inlineCallbacks
    def cleanup(self):
        yield self.worker_helper.cleanup()
        yield self.persistence_helper.cleanup()

    def get_dispatcher(self, config, cls=None, start=True):
        """
        Get an instance of a dispatcher class.

        :param dict config: Config dict.
        :param cls:
            The transport class to instantiate. Defaults to
            :attr:`dispatcher_class`
        :param bool start:
            ``True`` to start the dispatcher (default), ``False`` otherwise.
        """
        if cls is None:
            cls = self.dispatcher_class
        config = self.persistence_helper.mk_config(config)
        return self.get_worker(cls, config, start)

    def get_connector_helper(self, connector_name):
        """
        Construct a :class:`~DispatcherConnectorHelper` for the provided
        ``connector_name``.
        """
        return DispatcherConnectorHelper(self, connector_name)
Exemple #14
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)
Exemple #15
0
class DispatcherHelper(object):
    """
    Test helper for dispatcher workers.

    This helper construct and wraps several lower-level helpers and provides
    higher-level functionality for dispatcher tests.

    :param dispatcher_class:
        The worker class for the dispatcher being tested.

    :param bool use_riak:
        Set to ``True`` if the test requires Riak. This is passed to the
        underlying :class:`~vumi.tests.helpers.PersistenceHelper`.

    :param \**msg_helper_args:
        All other keyword params are passed to the underlying
        :class:`~vumi.tests.helpers.MessageHelper`.
    """

    implements(IHelper)

    def __init__(self, dispatcher_class, use_riak=False, **msg_helper_args):
        self.dispatcher_class = dispatcher_class
        self.worker_helper = WorkerHelper()
        self.persistence_helper = PersistenceHelper(use_riak=use_riak)
        self.msg_helper = MessageHelper(**msg_helper_args)
        self.dispatch_helper = MessageDispatchHelper(self.msg_helper, self.worker_helper)

        # Proxy methods from our helpers.
        generate_proxies(self, self.msg_helper)
        generate_proxies(self, self.worker_helper)
        generate_proxies(self, self.dispatch_helper)

    def setup(self):
        self.persistence_helper.setup()
        self.worker_helper.setup()

    @inlineCallbacks
    def cleanup(self):
        yield self.worker_helper.cleanup()
        yield self.persistence_helper.cleanup()

    def get_dispatcher(self, config, cls=None, start=True):
        """
        Get an instance of a dispatcher class.

        :param dict config: Config dict.
        :param cls:
            The transport class to instantiate. Defaults to
            :attr:`dispatcher_class`
        :param bool start:
            ``True`` to start the dispatcher (default), ``False`` otherwise.
        """
        if cls is None:
            cls = self.dispatcher_class
        config = self.persistence_helper.mk_config(config)
        return self.get_worker(cls, config, start)

    def get_connector_helper(self, connector_name):
        """
        Construct a :class:`~DispatcherConnectorHelper` for the provided
        ``connector_name``.
        """
        return DispatcherConnectorHelper(self, connector_name)
Exemple #16
0
class VumiApiHelper(object):
    # TODO: Clear bucket properties.
    #       We need two things for this:
    #        * The ability to clear bucket properties in our Riak layer.
    #        * Tracking accounts created so we know which buckets to clear.
    #
    #       The first needs to happen in vumi and requires an updated Riak
    #       client. The second isn't really worth doing unitl the first is
    #       done.

    implements(IHelper)

    def __init__(self, is_sync=False, use_riak=True):
        self.is_sync = is_sync
        self._patch_helper = PatchHelper()
        generate_proxies(self, self._patch_helper)

        self._persistence_helper = PersistenceHelper(use_riak=use_riak,
                                                     is_sync=is_sync)
        self.broker = None  # Will be replaced by the first worker_helper.
        self._worker_helpers = {}
        self._users_created = 0
        self._user_helpers = {}
        self._vumi_api = None

        generate_proxies(self, self._persistence_helper)

    def setup(self, setup_vumi_api=True):
        self._persistence_helper.setup()
        if self.is_sync:
            self._django_amqp_setup()
        if setup_vumi_api:
            return self.setup_vumi_api()

    @maybe_async
    def cleanup(self):
        for worker_helper in self._worker_helpers.values():
            # All of these will wait for the same broker, but that's fine.
            yield worker_helper.cleanup()
        yield self._persistence_helper.cleanup()
        self._patch_helper.cleanup()

    def _django_amqp_setup(self):
        import go.base.amqp
        import go.base.utils
        # We might need an AMQP connection at some point.
        broker = self.get_worker_helper().broker
        broker.exchange_declare('vumi', 'direct')
        self.django_amqp_connection = FakeAmqpConnection(broker)
        self.monkey_patch(go.base.utils, 'connection',
                          self.django_amqp_connection)
        self.monkey_patch(go.base.amqp, 'connection',
                          self.django_amqp_connection)

    def get_worker_helper(self, connector_name=None):
        if connector_name not in self._worker_helpers:
            worker_helper = WorkerHelper(connector_name, self.broker)
            # If this is our first worker helper, we need to grab the broker it
            # created. If it isn't, its broker will be self.broker anyway.
            self.broker = worker_helper.broker
            self._worker_helpers[connector_name] = worker_helper
        return self._worker_helpers[connector_name]

    @proxyable
    def get_vumi_api(self):
        assert self._vumi_api is not None, "No vumi_api provided."
        return self._vumi_api

    @proxyable
    def set_vumi_api(self, vumi_api):
        assert self._vumi_api is None, "Can't override existing vumi_api."
        self._vumi_api = vumi_api
        # TODO: Find a nicer way to give everything the same fake redis.
        pcfg = self._persistence_helper._config_overrides
        pcfg['redis_manager']['FAKE_REDIS'] = vumi_api.redis

    @proxyable
    def setup_vumi_api(self):
        if self.is_sync:
            return self.setup_sync_vumi_api()
        else:
            return self.setup_async_vumi_api()

    def setup_sync_vumi_api(self):
        from django.conf import settings
        import go.base.amqp
        self._vumi_api = VumiApi.from_config_sync(settings.VUMI_API_CONFIG,
                                                  go.base.amqp.connection)

    def setup_async_vumi_api(self):
        worker_helper = self.get_worker_helper()
        amqp_client = worker_helper.get_fake_amqp_client(worker_helper.broker)
        d = amqp_client.start_publisher(ApiCommandPublisher)
        d.addCallback(lambda cmd_publisher: VumiApi.from_config_async(
            self.mk_config({}), cmd_publisher))
        return d.addCallback(self.set_vumi_api)

    @proxyable
    @maybe_async
    def make_user(self, username, enable_search=True, django_user_pk=None):
        # NOTE: We use bytes instead of unicode here because that's what the
        #       real new_user gives us.
        key = "test-%s-user" % (len(self._user_helpers), )
        user = self.get_vumi_api().account_store.users(key, username=username)
        yield user.save()
        user_helper = UserApiHelper(self, key, django_user_pk=django_user_pk)
        self._user_helpers[key] = user_helper
        if enable_search:
            contact_store = user_helper.user_api.contact_store
            yield contact_store.contacts.enable_search()
            yield contact_store.groups.enable_search()
        returnValue(self.get_user_helper(user.key))

    @proxyable
    def get_user_helper(self, account_key):
        return self._user_helpers[account_key]

    @proxyable
    @maybe_async
    def get_or_create_user(self):
        assert len(self._user_helpers) <= 1, "Too many users."
        if not self._user_helpers:
            yield self.make_user(u"testuser")
        returnValue(self._user_helpers.values()[0])

    @proxyable
    @maybe_async
    def setup_tagpool(self, pool, tags, metadata=None):
        tags = [(pool, tag) for tag in tags]
        yield self.get_vumi_api().tpm.declare_tags(tags)
        if metadata:
            yield self.get_vumi_api().tpm.set_metadata(pool, metadata)
        returnValue(tags)

    def get_dispatched_commands(self):
        return self.get_worker_helper().get_dispatched('vumi', 'api',
                                                       VumiApiCommand)
Exemple #17
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)
Exemple #18
0
class TransportHelper(object):
    """
    Test helper for transport workers.

    This helper construct and wraps several lower-level helpers and provides
    higher-level functionality for transport tests.

    :param transport_class:
        The worker class for the transport being tested.

    :param bool use_riak:
        Set to ``True`` if the test requires Riak. This is passed to the
        underlying :class:`~vumi.tests.helpers.PersistenceHelper`.

    :param \**msg_helper_args:
        All other keyword params are passed to the underlying
        :class:`~vumi.tests.helpers.MessageHelper`.
    """

    implements(IHelper)

    def __init__(self, transport_class, use_riak=False, **msg_helper_args):
        self.transport_class = transport_class
        self.persistence_helper = PersistenceHelper(use_riak=use_riak)
        self.msg_helper = MessageHelper(**msg_helper_args)
        self.transport_name = self.msg_helper.transport_name
        self.worker_helper = WorkerHelper(self.transport_name)
        self.dispatch_helper = MessageDispatchHelper(
            self.msg_helper, self.worker_helper)

        # Proxy methods from our helpers.
        generate_proxies(self, self.msg_helper)
        generate_proxies(self, self.worker_helper)
        generate_proxies(self, self.dispatch_helper)
        generate_proxies(self, self.persistence_helper)

    def setup(self):
        self.persistence_helper.setup()
        self.worker_helper.setup()

    @inlineCallbacks
    def cleanup(self):
        yield self.worker_helper.cleanup()
        yield self.persistence_helper.cleanup()

    def get_transport(self, config, cls=None, start=True):
        """
        Get an instance of a transport class.

        :param config: Config dict.
        :param cls: The transport class to instantiate.
                    Defaults to :attr:`transport_class`
        :param start: True to start the transport (default), False otherwise.

        Some default config values are helpfully provided in the
        interests of reducing boilerplate:

        * ``transport_name`` defaults to :attr:`self.transport_name`
        """

        if cls is None:
            cls = self.transport_class
        config = self.mk_config(config)
        config.setdefault('transport_name', self.transport_name)
        return self.get_worker(cls, config, start)

    def get_dispatched_failures(self, connector_name=None):
        """
        Get failures dispatched by a transport.

        :param str connector_name:
            Connector name. If ``None``, the default connector name for the
            helper instance will be used.

        :returns:
            A list of :class:`~vumi.transports.failures.FailureMessage`
            instances.
        """
        return self.get_dispatched(connector_name, 'failures', FailureMessage)
Exemple #19
0
class JunebugTestBase(TestCase):
    '''Base test case that all junebug tests inherit from. Contains useful
    helper functions'''

    default_channel_properties = {
        'type': 'telnet',
        'config': {
            'twisted_endpoint': 'tcp:0',
            'worker_name': 'unnamed',
        },
        'mo_url': 'http://foo.bar',
    }

    default_channel_config = {
        'ttl': 60,
        'amqp': {},
    }

    def patch_logger(self):
        ''' Patches the logger with an in-memory logger, which is acccessable
        at "self.logging_handler".'''
        self.logging_handler = logging.handlers.MemoryHandler(100)
        logging.getLogger().addHandler(self.logging_handler)
        self.addCleanup(self._cleanup_logging_patch)

    def _cleanup_logging_patch(self):
        self.logging_handler.close()
        logging.getLogger().removeHandler(self.logging_handler)

    def create_channel_properties(self, **kw):
        properties = deepcopy(self.default_channel_properties)
        properties.update(kw)
        return properties

    @inlineCallbacks
    def create_channel_config(self, **kw):
        self.persistencehelper = PersistenceHelper()
        yield self.persistencehelper.setup()
        self.addCleanup(self.persistencehelper.cleanup)

        config = deepcopy(self.default_channel_config)
        config.update(kw)
        channel_config = self.persistencehelper.mk_config(config)
        channel_config['redis'] = channel_config['redis_manager']
        returnValue(JunebugConfig(channel_config))

    @inlineCallbacks
    def create_channel(
            self, service, redis, transport_class,
            properties=default_channel_properties, id=None):
        '''Creates and starts, and saves a channel, with a
        TelnetServerTransport transport'''
        properties = deepcopy(properties)
        config = yield self.create_channel_config()
        channel = Channel(
            redis, config, properties, id=id)
        properties['config']['transport_name'] = channel.id
        yield channel.start(self.service)
        yield channel.save()
        self.addCleanup(channel.stop)
        returnValue(channel)

    @inlineCallbacks
    def create_channel_from_id(self, redis, config, id, service):
        '''Creates an existing channel given the channel id'''
        config = yield self.create_channel_config(**config)
        channel = yield Channel.from_id(redis, config, id, service)
        returnValue(channel)

    @inlineCallbacks
    def get_redis(self):
        '''Creates and returns a redis manager'''
        if hasattr(self, 'redis'):
            returnValue(self.redis)
        persistencehelper = PersistenceHelper()
        yield persistencehelper.setup()
        self.redis = yield persistencehelper.get_redis_manager()
        self.addCleanup(persistencehelper.cleanup)
        returnValue(self.redis)

    @inlineCallbacks
    def start_server(self):
        '''Starts a junebug server. Stores the service to "self.service", and
        the url at "self.url"'''
        config = yield self.create_channel_config()
        self.service = JunebugService(config)
        self.api = JunebugApi(
            self.service, config)

        redis = yield self.persistencehelper.get_redis_manager()
        self.api = JunebugApi(self.service, config)
        yield self.api.setup(redis, self.get_message_sender())

        self.config = self.api.config
        self.redis = self.api.redis
        self.inbounds = self.api.inbounds
        self.outbounds = self.api.outbounds
        self.message_sender = self.api.message_sender

        port = reactor.listenTCP(
            0, Site(self.api.app.resource()),
            interface='127.0.0.1')
        self.addCleanup(port.stopListening)
        addr = port.getHost()
        self.url = "http://%s:%s" % (addr.host, addr.port)

    def get_message_sender(self):
        '''Creates a new MessageSender object, with a fake amqp client'''
        message_sender = MessageSender('amqp-spec-0-8.xml', None)
        spec = get_spec(vumi_resource_path('amqp-spec-0-8.xml'))
        client = FakeAmqpClient(spec)
        message_sender.client = client
        return message_sender

    def get_dispatched_messages(self, queue):
        '''Gets all messages that have been dispatched to the amqp broker.
        Should only be called after start_server, as it looks in the api for
        the amqp client'''
        amqp_client = self.api.message_sender.client
        return amqp_client.broker.get_messages(
            'vumi', queue)