Exemple #1
0
    def test_callback_with_exception_multiple_calls(self):
        class EchoException(Exception):
            pass

        class Echo(object):
            def error(self):
                raise exc

        echo = Echo()
        exc = EchoException("error!")

        callback = Mock()
        callback.side_effect = [False, True]

        with wait_for_call(echo, 'error', callback):
            with pytest.raises(EchoException):
                echo.error()
            with pytest.raises(EchoException):
                echo.error()

        assert callback.called
        assert callback.call_args_list == [
            call((), {}, None, (EchoException, exc, ANY)),
            call((), {}, None, (EchoException, exc, ANY))
        ]
Exemple #2
0
    def test_callback_multiple_calls(self):

        class Echo(object):

            count = 0

            def upper(self, arg):
                self.count += 1
                return "{}-{}".format(arg.upper(), self.count)

        echo = Echo()
        arg = "hello"

        callback = Mock()
        callback.side_effect = [False, True]

        with wait_for_call(echo, 'upper', callback):
            res1 = echo.upper(arg)
            assert res1 == "HELLO-1"
            res2 = echo.upper(arg)
            assert res2 == "HELLO-2"

        assert callback.called
        assert callback.call_args_list == [
            call((arg,), {}, res1, None),
            call((arg,), {}, res2, None),
        ]
Exemple #3
0
    def test_wait_until_stops_raising(self, forever):

        class ThresholdNotReached(Exception):
            pass

        class CounterWithThreshold(object):

            def __init__(self, threshold):
                self.value = 0
                self.threshold = threshold

            def count(self):
                self.value += 1
                if self.value < self.threshold:
                    raise ThresholdNotReached(self.threshold)
                return self.value

        threshold = 10
        counter = CounterWithThreshold(threshold)

        def count_forever():
            while forever:
                try:
                    counter.count()
                except ThresholdNotReached:
                    pass
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return exc_info is None

        with wait_for_call(counter, 'count', callback=cb) as result:
            Thread(target=count_forever).start()

        assert result.get() == threshold
Exemple #4
0
    def test_callback_multiple_calls(self):
        class Echo(object):

            count = 0

            def upper(self, arg):
                self.count += 1
                return "{}-{}".format(arg.upper(), self.count)

        echo = Echo()
        arg = "hello"

        callback = Mock()
        callback.side_effect = [False, True]

        with wait_for_call(echo, 'upper', callback):
            res1 = echo.upper(arg)
            assert res1 == "HELLO-1"
            res2 = echo.upper(arg)
            assert res2 == "HELLO-2"

        assert callback.called
        assert callback.call_args_list == [
            call((arg, ), {}, res1, None),
            call((arg, ), {}, res2, None),
        ]
Exemple #5
0
    def test_callback_with_exception_multiple_calls(self):

        class EchoException(Exception):
            pass

        class Echo(object):

            def error(self):
                raise exc

        echo = Echo()
        exc = EchoException("error!")

        callback = Mock()
        callback.side_effect = [False, True]

        with wait_for_call(echo, 'error', callback):
            with pytest.raises(EchoException):
                echo.error()
            with pytest.raises(EchoException):
                echo.error()

        assert callback.called
        assert callback.call_args_list == [
            call((), {}, None, (EchoException, exc, ANY)),
            call((), {}, None, (EchoException, exc, ANY))
        ]
Exemple #6
0
    def test_wait_until_raises(self, forever):
        class LimitExceeded(Exception):
            pass

        class CounterWithLimit(object):
            def __init__(self, limit):
                self.value = 0
                self.limit = limit

            def count(self):
                self.value += 1
                if self.value >= self.limit:
                    raise LimitExceeded(self.limit)
                return self.value

        limit = 10
        counter = CounterWithLimit(limit)

        def count_forever():
            while forever:
                counter.count()
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return exc_info is not None

        with wait_for_call(counter, 'count', callback=cb) as result:
            Thread(target=count_forever).start()

        with pytest.raises(LimitExceeded):
            result.get()
Exemple #7
0
    def test_wait_until_raises(self, forever):

        class LimitExceeded(Exception):
            pass

        class CounterWithLimit(object):
            def __init__(self, limit):
                self.value = 0
                self.limit = limit

            def count(self):
                self.value += 1
                if self.value >= self.limit:
                    raise LimitExceeded(self.limit)
                return self.value

        limit = 10
        counter = CounterWithLimit(limit)

        def count_forever():
            while forever:
                counter.count()
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return exc_info is not None

        with wait_for_call(counter, 'count', callback=cb) as result:
            Thread(target=count_forever).start()

        with pytest.raises(LimitExceeded):
            result.get()
Exemple #8
0
def test_run_with_rename_single_service(rabbit_config):
    parser = setup_parser()
    broker = rabbit_config['AMQP_URI']
    ser_name = Service.name
    args = parser.parse_args([
        'run',
        '--broker',
        broker,
        '--backdoor-port',
        0,
        'test.sample:Service',
        '--rename',
        'renamed_service',
    ])

    with wait_for_call(ServiceRunner, 'start'):
        gt = eventlet.spawn(main, args)

    with ClusterRpcProxy(rabbit_config) as proxy:
        proxy.renamed_service.ping()

    pid = os.getpid()
    os.kill(pid, signal.SIGTERM)
    gt.wait()

    Service.name = ser_name
Exemple #9
0
    def test_wait_until_stops_raising(self, forever):
        class ThresholdNotReached(Exception):
            pass

        class CounterWithThreshold(object):
            def __init__(self, threshold):
                self.value = 0
                self.threshold = threshold

            def count(self):
                self.value += 1
                if self.value < self.threshold:
                    raise ThresholdNotReached(self.threshold)
                return self.value

        threshold = 10
        counter = CounterWithThreshold(threshold)

        def count_forever():
            while forever:
                try:
                    counter.count()
                except ThresholdNotReached:
                    pass
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return exc_info is None

        with wait_for_call(counter, 'count', callback=cb) as result:
            Thread(target=count_forever).start()

        assert result.get() == threshold
Exemple #10
0
    def test_wait_until_called_with_argument(self, forever):
        class CounterWithSet(object):
            value = 0

            def count(self):
                self.value += 1
                return self.value

            def set(self, value):
                self.value = value
                return self.value

        counter = CounterWithSet()

        def increment_forever_via_set():
            while forever:
                counter.set(counter.value + 1)
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return args == (10, )

        with wait_for_call(counter, 'set', callback=cb) as result:
            Thread(target=increment_forever_via_set).start()

        assert result.get() == 10
Exemple #11
0
    def test_disconnect_and_successfully_reconnect(self, container_factory,
                                                   rabbit_manager,
                                                   rabbit_config,
                                                   toxic_rpc_proxy, toxiproxy):
        block = Event()

        class Service(object):
            name = "service"

            @rpc
            def method(self, arg):
                block.wait()
                return arg

        container = container_factory(Service, rabbit_config)
        container.start()

        # make an async call that will block,
        # wait for the worker to have spawned
        with wait_for_call(container, 'spawn_worker'):
            res = toxic_rpc_proxy.service.method.call_async('msg1')

        # disconnect toxiproxy
        toxiproxy.disable()

        # reconnect toxiproxy just before the consumer attempts to reconnect;
        # (consumer.cancel is the only hook we have)
        def reconnect(args, kwargs, res, exc_info):
            block.send(True)
            toxiproxy.enable()
            return True

        with wait_for_call(
                toxic_rpc_proxy._reply_listener.queue_consumer.consumer,
                'cancel',
                callback=reconnect):
            # rpc proxy should return an error for the request in flight.
            with pytest.raises(RpcConnectionError):
                res.result()

        # proxy should work again after reconnection
        assert toxic_rpc_proxy.service.method("msg3") == "msg3"
Exemple #12
0
    def test_patch_class(self):
        class Echo(object):
            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(Echo, 'upper'):
            res = echo.upper(arg)
            assert res == "HELLO"
Exemple #13
0
    def test_disconnect_and_successfully_reconnect(
        self, container_factory, rabbit_manager, rabbit_config,
        toxic_rpc_proxy, toxiproxy
    ):
        block = Event()

        class Service(object):
            name = "service"

            @rpc
            def method(self, arg):
                block.wait()
                return arg

        container = container_factory(Service, rabbit_config)
        container.start()

        # make an async call that will block,
        # wait for the worker to have spawned
        with wait_for_call(container, 'spawn_worker'):
            res = toxic_rpc_proxy.service.method.call_async('msg1')

        # disable toxiproxy to kill connections
        toxiproxy.disable()

        # re-enable toxiproxy just before the consumer attempts to reconnect;
        # (consumer.cancel is the only hook we have)
        def reconnect(args, kwargs, res, exc_info):
            block.send(True)
            toxiproxy.enable()
            return True

        with wait_for_call(
            toxic_rpc_proxy._reply_listener.queue_consumer.consumer, 'cancel',
            callback=reconnect
        ):
            # rpc proxy should recover the message in flight
            res.result() == "msg1"

            # proxy should work again after reconnection
            assert toxic_rpc_proxy.service.method("msg2") == "msg2"
Exemple #14
0
    def test_result(self):
        class Echo(object):
            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(echo, 'upper') as result:
            res = echo.upper(arg)

        assert result.get() == res
Exemple #15
0
    def test_patch_class(self):

        class Echo(object):

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(Echo, 'upper'):
            res = echo.upper(arg)
            assert res == "HELLO"
Exemple #16
0
    def test_indirect(self):
        class Echo(object):
            def proxy(self, arg):
                return self.upper(arg)

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(echo, 'upper'):
            assert echo.proxy(arg) == "HELLO"
Exemple #17
0
    def test_wrapped_method_raises(self):
        class EchoException(Exception):
            pass

        class Echo(object):
            def error(self):
                raise EchoException("error!")

        echo = Echo()

        with wait_for_call(echo, 'error'):
            with pytest.raises(EchoException):
                echo.error()
Exemple #18
0
    def test_result_is_none(self):
        class Echo(object):
            def nothing(self):
                return None

        echo = Echo()

        with wait_for_call(echo, 'nothing') as result:
            res = echo.nothing()

        assert res is None
        assert result.get() is None
        assert result.has_result is True
Exemple #19
0
def test_main_with_logging_config(rabbit_config, tmpdir):

    config = """
        AMQP_URI: {amqp_uri}
        LOGGING:
            version: 1
            disable_existing_loggers: false
            formatters:
                simple:
                    format: "%(name)s - %(levelname)s - %(message)s"
            handlers:
                capture:
                    class: logging.FileHandler
                    level: INFO
                    formatter: simple
                    filename: {capture_file}
            root:
                level: INFO
                handlers: [capture]
    """

    capture_file = tmpdir.join('capture.log')

    config_file = tmpdir.join('config.yaml')
    config_file.write(
        dedent(config.format(
            capture_file=capture_file.strpath,
            amqp_uri=rabbit_config['AMQP_URI']
        ))
    )

    parser = setup_parser()
    args = parser.parse_args([
        'run',
        '--config',
        config_file.strpath,
        'test.sample',
    ])

    # start runner and wait for it to come up
    with wait_for_call(ServiceRunner, 'start'):
        gt = eventlet.spawn(main, args)

    with ClusterRpcProxy(rabbit_config) as proxy:
        proxy.service.ping()

    pid = os.getpid()
    os.kill(pid, signal.SIGTERM)
    gt.wait()

    assert "test.sample - INFO - ping!" in capture_file.read()
Exemple #20
0
    def test_result_not_ready(self):
        class Echo(object):
            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(echo, 'upper') as result:
            with pytest.raises(result.NotReady):
                result.get()
            res = echo.upper(arg)

        assert result.get() == res
Exemple #21
0
    def test_result(self):

        class Echo(object):

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(echo, 'upper') as result:
            res = echo.upper(arg)

        assert result.get() == res
Exemple #22
0
def test_main_with_logging_config(rabbit_config, tmpdir):

    config = """
        AMQP_URI: {amqp_uri}
        LOGGING:
            version: 1
            disable_existing_loggers: false
            formatters:
                simple:
                    format: "%(name)s - %(levelname)s - %(message)s"
            handlers:
                capture:
                    class: logging.FileHandler
                    level: INFO
                    formatter: simple
                    filename: {capture_file}
            root:
                level: INFO
                handlers: [capture]
    """

    capture_file = tmpdir.join('capture.log')

    config_file = tmpdir.join('config.yaml')
    config_file.write(
        dedent(
            config.format(capture_file=capture_file.strpath,
                          amqp_uri=rabbit_config['AMQP_URI'])))

    parser = setup_parser()
    args = parser.parse_args([
        'run',
        '--config',
        config_file.strpath,
        'test.sample',
    ])

    # start runner and wait for it to come up
    with wait_for_call(ServiceRunner, 'start'):
        gt = eventlet.spawn(main, args)

    with ClusterRpcProxy(rabbit_config) as proxy:
        proxy.service.ping()

    pid = os.getpid()
    os.kill(pid, signal.SIGTERM)
    gt.wait()

    assert "test.sample - INFO - ping!" in capture_file.read()
Exemple #23
0
    def test_indirect(self):

        class Echo(object):

            def proxy(self, arg):
                return self.upper(arg)

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(echo, 'upper'):
            assert echo.proxy(arg) == "HELLO"
Exemple #24
0
    def test_result_is_none(self):

        class Echo(object):

            def nothing(self):
                return None

        echo = Echo()

        with wait_for_call(echo, 'nothing') as result:
            res = echo.nothing()

        assert res is None
        assert result.get() is None
        assert result.has_result is True
Exemple #25
0
    def test_wrapped_method_raises(self):

        class EchoException(Exception):
            pass

        class Echo(object):

            def error(self):
                raise EchoException("error!")

        echo = Echo()

        with wait_for_call(echo, 'error'):
            with pytest.raises(EchoException):
                echo.error()
Exemple #26
0
def test_entrypoint_waiter_result_teardown_race(
    container_factory, rabbit_config, counter
):
    tracker = Mock()

    class TrackingDependency(DependencyProvider):

        def worker_result(self, worker_ctx, res, exc_info):
            tracker.worker_result()

        def worker_teardown(self, worker_ctx):
            tracker.worker_teardown()

    class Service(object):
        name = "service"

        tracker = TrackingDependency()

        @event_handler('srcservice', 'eventtype')
        def handle(self, msg):
            tracker.handle(msg)

    container = container_factory(Service, rabbit_config)
    container.start()

    def wait_for_two_calls(worker_ctx, res, exc_info):
        return counter.count() > 1

    dispatch = event_dispatcher(rabbit_config)
    with entrypoint_waiter(container, 'handle', callback=wait_for_two_calls):

        # dispatch the first message
        # wait until teardown has happened
        with wait_for_call(TrackingDependency, 'worker_teardown'):
            dispatch('srcservice', 'eventtype', "msg")

        assert tracker.worker_teardown.call_count == 1
        assert tracker.worker_result.call_count == 1
        assert tracker.handle.call_count == 1

        # dispatch the second event
        dispatch('srcservice', 'eventtype', "msg")

    # we should wait for the second teardown to complete before exiting
    # the entrypoint waiter
    assert tracker.worker_teardown.call_count == 2
    assert tracker.worker_result.call_count == 2
    assert tracker.handle.call_count == 2
Exemple #27
0
def test_entrypoint_waiter_result_teardown_race(
    container_factory, rabbit_config, counter
):
    tracker = Mock()

    class TrackingDependency(DependencyProvider):

        def worker_result(self, worker_ctx, res, exc_info):
            tracker.worker_result()

        def worker_teardown(self, worker_ctx):
            tracker.worker_teardown()

    class Service(object):
        name = "service"

        tracker = TrackingDependency()

        @event_handler('srcservice', 'eventtype')
        def handle(self, msg):
            tracker.handle(msg)

    container = container_factory(Service, rabbit_config)
    container.start()

    def wait_for_two_calls(worker_ctx, res, exc_info):
        return counter.count() > 1

    dispatch = event_dispatcher(rabbit_config)
    with entrypoint_waiter(container, 'handle', callback=wait_for_two_calls):

        # dispatch the first message
        # wait until teardown has happened
        with wait_for_call(TrackingDependency, 'worker_teardown'):
            dispatch('srcservice', 'eventtype', "msg")

        assert tracker.worker_teardown.call_count == 1
        assert tracker.worker_result.call_count == 1
        assert tracker.handle.call_count == 1

        # dispatch the second event
        dispatch('srcservice', 'eventtype', "msg")

    # we should wait for the second teardown to complete before exiting
    # the entrypoint waiter
    assert tracker.worker_teardown.call_count == 2
    assert tracker.worker_result.call_count == 2
    assert tracker.handle.call_count == 2
Exemple #28
0
    def test_result_get_raises(self):
        class EchoException(Exception):
            pass

        class Echo(object):
            def error(self):
                raise EchoException("error!")

        echo = Echo()

        with wait_for_call(echo, 'error') as result:
            with pytest.raises(EchoException):
                echo.error()

            with pytest.raises(EchoException):
                result.get()
Exemple #29
0
    def test_result_not_ready(self):

        class Echo(object):

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        with wait_for_call(echo, 'upper') as result:
            with pytest.raises(result.NotReady):
                result.get()
            res = echo.upper(arg)

        assert result.get() == res
Exemple #30
0
    def test_callback(self):
        class Echo(object):
            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        callback = Mock()
        callback.return_value = True

        with wait_for_call(echo, 'upper', callback):
            res = echo.upper(arg)
            assert res == "HELLO"

        assert callback.called
        assert callback.call_args_list == [call((arg, ), {}, res, None)]
Exemple #31
0
    def test_target_as_mock(self):
        class Klass(object):
            def __init__(self):
                self.attr = "value"

            def method(self):
                return self.attr.upper()

        instance = Klass()

        with patch.object(instance, 'attr') as patched_attr:

            with wait_for_call(patched_attr, 'upper'):
                instance.method()

            assert patched_attr.upper.called
            assert instance.attr.upper.called
Exemple #32
0
    def test_with_retry_policy(self, service_rpc, toxiproxy):
        """ Verify we automatically recover from stale connections.

        Publish confirms are required for this functionality. Without confirms
        the later messages are silently lost and the test hangs waiting for a
        response.
        """
        assert service_rpc.echo(1) == 1

        toxiproxy.disable()

        def enable_after_retry(args, kwargs, res, exc_info):
            toxiproxy.enable()
            return True

        # subsequent calls succeed (after reconnecting via retry policy)
        with wait_for_call(Connection, 'connect', callback=enable_after_retry):
            assert service_rpc.echo(2) == 2
Exemple #33
0
    def test_with_retry_policy(self, service_rpc, toxiproxy):
        """ Verify we automatically recover from stale connections.

        Publish confirms are required for this functionality. Without confirms
        the later messages are silently lost and the test hangs waiting for a
        response.
        """
        assert service_rpc.echo(1) == 1

        toxiproxy.disable()

        def enable_after_retry(args, kwargs, res, exc_info):
            toxiproxy.enable()
            return True

        # subsequent calls succeed (after reconnecting via retry policy)
        with wait_for_call(Connection, 'connect', callback=enable_after_retry):
            assert service_rpc.echo(2) == 2
Exemple #34
0
    def test_result_get_raises(self):

        class EchoException(Exception):
            pass

        class Echo(object):

            def error(self):
                raise EchoException("error!")

        echo = Echo()

        with wait_for_call(echo, 'error') as result:
            with pytest.raises(EchoException):
                echo.error()

            with pytest.raises(EchoException):
                result.get()
Exemple #35
0
    def test_callback(self):

        class Echo(object):

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        callback = Mock()
        callback.return_value = True

        with wait_for_call(echo, 'upper', callback):
            res = echo.upper(arg)
            assert res == "HELLO"

        assert callback.called
        assert callback.call_args_list == [call((arg,), {}, res, None)]
Exemple #36
0
    def test_target_as_mock(self):

        class Klass(object):

            def __init__(self):
                self.attr = "value"

            def method(self):
                return self.attr.upper()

        instance = Klass()

        with patch.object(instance, 'attr') as patched_attr:

            with wait_for_call(patched_attr, 'upper'):
                instance.method()

            assert patched_attr.upper.called
            assert instance.attr.upper.called
Exemple #37
0
    def test_with_new_thread(self):
        class Echo(object):
            def proxy(self, arg):
                Thread(target=self.upper, args=(arg, )).start()

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        callback = Mock()
        callback.return_value = True

        with wait_for_call(echo, 'upper', callback):
            res = echo.proxy(arg)
            assert res is None

        assert callback.called
        assert callback.call_args_list == [call((arg, ), {}, "HELLO", None)]
Exemple #38
0
    def test_disconnect_and_fail_to_reconnect(self, container_factory,
                                              rabbit_manager, rabbit_config,
                                              toxic_rpc_proxy, toxiproxy):
        block = Event()

        class Service(object):
            name = "service"

            @rpc
            def method(self, arg):
                block.wait()
                return arg

        container = container_factory(Service, rabbit_config)
        container.start()

        # make an async call that will block,
        # wait for the worker to have spawned
        with wait_for_call(container, 'spawn_worker'):
            res = toxic_rpc_proxy.service.method.call_async('msg1')

        try:
            # disconnect
            toxiproxy.disable()

            # rpc proxy should return an error for the request in flight.
            # it will also attempt to reconnect and throw on failure
            # because toxiproxy is still disconnected
            with pytest.raises(socket.error):
                with pytest.raises(RpcConnectionError):
                    res.result()

        finally:
            # reconnect toxiproxy
            block.send(True)
            toxiproxy.enable()

        # proxy will not work afterwards because the queueconsumer connection
        # was not recovered on the second attempt
        with pytest.raises(RuntimeError):
            toxic_rpc_proxy.service.method("msg3")
Exemple #39
0
    def test_failed_delivery(self, container, publish_message, exchange, queue,
                             rabbit_config):
        backoff_publisher = get_extension(container, BackoffPublisher)
        make_queue = backoff_publisher.make_queue

        # patch make_queue so that the return value does not have
        # a matching binding; this forces an unroutable messsage
        with patch.object(backoff_publisher, 'make_queue') as patched:

            patched.return_value = make_queue(999999)

            # patch get_producer so we can wait until publish is called
            # multiple times, demonstrating the retry
            with patch('nameko_amqp_retry.backoff.get_producer') as patched:

                # create a replacement producer that we can hook into
                # and make our patched get_producer return that
                amqp_uri = rabbit_config['AMQP_URI']
                with get_producer(amqp_uri) as replacement_producer:

                    @contextmanager
                    def producer_context(*a, **k):
                        yield replacement_producer

                    patched.side_effect = producer_context

                    # fire entrypoint and wait for retry of the backoff publish
                    counter = itertools.count()
                    with wait_for_call(
                            replacement_producer,
                            'publish',
                            callback=lambda *a, **k: next(counter) == 2):
                        publish_message(exchange,
                                        "",
                                        routing_key=queue.routing_key)

                    # when the retry also fails,
                    # the container is killed so that the request is requeued
                    with pytest.raises(UndeliverableMessage):
                        container.wait()
Exemple #40
0
    def test_callback_with_exception(self):
        class EchoException(Exception):
            pass

        class Echo(object):
            def error(self):
                raise exc

        echo = Echo()
        exc = EchoException("error!")

        callback = Mock()
        callback.return_value = True

        with wait_for_call(echo, 'error', callback):
            with pytest.raises(EchoException):
                echo.error()

        assert callback.called
        assert callback.call_args_list == [
            call((), {}, None, (EchoException, exc, ANY))
        ]
Exemple #41
0
    def test_with_new_thread(self):

        class Echo(object):

            def proxy(self, arg):
                Thread(target=self.upper, args=(arg,)).start()

            def upper(self, arg):
                return arg.upper()

        echo = Echo()
        arg = "hello"

        callback = Mock()
        callback.return_value = True

        with wait_for_call(echo, 'upper', callback):
            res = echo.proxy(arg)
            assert res is None

        assert callback.called
        assert callback.call_args_list == [call((arg,), {}, "HELLO", None)]
Exemple #42
0
    def test_wait_for_specific_result(self, forever):
        class Counter(object):
            value = 0

            def count(self):
                self.value += 1
                return self.value

        counter = Counter()

        def count_forever():
            while forever:
                counter.count()
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return res == 10

        with wait_for_call(counter, 'count', callback=cb) as result:
            Thread(target=count_forever).start()

        assert result.get() == 10
Exemple #43
0
    def test_wait_until_called_with_argument(self, forever):

        class CounterWithSet(object):
            value = 0

            def set(self, value):
                self.value = value
                return self.value

        counter = CounterWithSet()

        def increment_forever_via_set():
            while forever:
                counter.set(counter.value + 1)
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return args == (10,)

        with wait_for_call(counter, 'set', callback=cb) as result:
            Thread(target=increment_forever_via_set).start()

        assert result.get() == 10
Exemple #44
0
    def test_wait_for_specific_result(self, forever):

        class Counter(object):
            value = 0

            def count(self):
                self.value += 1
                return self.value

        counter = Counter()

        def count_forever():
            while forever:
                counter.count()
                time.sleep(0)

        def cb(args, kwargs, res, exc_info):
            return res == 10

        with wait_for_call(counter, 'count', callback=cb) as result:
            Thread(target=count_forever).start()

        assert result.get() == 10
Exemple #45
0
    def test_disconnect_and_fail_to_reconnect(
        self, container_factory, rabbit_manager, rabbit_config,
        toxic_rpc_proxy, toxiproxy
    ):
        block = Event()

        class Service(object):
            name = "service"

            @rpc
            def method(self, arg):
                block.wait()
                return arg

        container = container_factory(Service, rabbit_config)
        container.start()

        # make an async call that will block,
        # wait for the worker to have spawned
        with wait_for_call(container, 'spawn_worker'):
            res = toxic_rpc_proxy.service.method.call_async('msg1')

        # disable toxiproxy to kill connections
        with toxiproxy.disabled():

            # toxiproxy remains disabled when the proxy attempts to reconnect,
            # so we should return an error for the request in flight
            with pytest.raises(socket.error):
                res.result()

        # unblock worker
        block.send(True)

        # proxy will not work afterwards because the queueconsumer connection
        # was not recovered on the second attempt
        with pytest.raises(RuntimeError):
            toxic_rpc_proxy.service.method("msg2")
Exemple #46
0
    def test_disconnect_and_fail_to_reconnect(
        self, container_factory, rabbit_manager, rabbit_config,
        toxic_rpc_proxy, toxiproxy
    ):
        block = Event()

        class Service(object):
            name = "service"

            @rpc
            def method(self, arg):
                block.wait()
                return arg

        container = container_factory(Service, rabbit_config)
        container.start()

        # make an async call that will block,
        # wait for the worker to have spawned
        with wait_for_call(container, 'spawn_worker'):
            res = toxic_rpc_proxy.service.method.call_async('msg1')

        # disable toxiproxy to kill connections
        with toxiproxy.disabled():

            # toxiproxy remains disabled when the proxy attempts to reconnect,
            # so we should return an error for the request in flight
            with pytest.raises(socket.error):
                res.result()

        # unblock worker
        block.send(True)

        # proxy will not work afterwards because the queueconsumer connection
        # was not recovered on the second attempt
        with pytest.raises(RuntimeError):
            toxic_rpc_proxy.service.method("msg2")
Exemple #47
0
    def test_close_socket(self, server, load_stubs, spec_dir, grpc_port, protobufs):
        """ Regression test for https://github.com/nameko/nameko-grpc/issues/39
        """
        stubs = load_stubs("example")

        client = Client(
            "//localhost:{}".format(grpc_port),
            stubs.exampleStub,
            "none",
            "high",
            False,
        )
        proxy = client.start()

        container = server
        grpc_server = get_extension(container, GrpcServer)
        connection_ref = grpc_server.channel.conn_pool.connections.queue[0]
        connection = connection_ref()

        response = proxy.unary_unary(protobufs.ExampleRequest(value="A"))
        assert response.message == "A"

        with wait_for_call(connection.sock, "close"):
            client.stop()
Exemple #48
0
def test_run(rabbit_config):
    parser = setup_parser()
    broker = rabbit_config['AMQP_URI']
    args = parser.parse_args([
        'run',
        '--broker',
        broker,
        '--backdoor-port',
        0,
        'test.sample:Service',
    ])

    # start runner and wait for it to come up
    with wait_for_call(ServiceRunner, 'start'):
        gt = eventlet.spawn(main, args)

    # make sure service launches ok
    with ClusterRpcProxy(rabbit_config) as proxy:
        proxy.service.ping()

    # stop service
    pid = os.getpid()
    os.kill(pid, signal.SIGTERM)
    gt.wait()
Exemple #49
0
def test_run(rabbit_config):
    parser = setup_parser()
    broker = rabbit_config['AMQP_URI']
    args = parser.parse_args([
        'run',
        '--broker',
        broker,
        '--backdoor-port',
        0,
        'test.sample:Service',
    ])

    # start runner and wait for it to come up
    with wait_for_call(ServiceRunner, 'start'):
        gt = eventlet.spawn(main, args)

    # make sure service launches ok
    with ClusterRpcProxy(rabbit_config) as proxy:
        proxy.service.ping()

    # stop service
    pid = os.getpid()
    os.kill(pid, signal.SIGTERM)
    gt.wait()
Exemple #50
0
    def test_callback_with_exception(self):

        class EchoException(Exception):
            pass

        class Echo(object):

            def error(self):
                raise exc

        echo = Echo()
        exc = EchoException("error!")

        callback = Mock()
        callback.return_value = True

        with wait_for_call(echo, 'error', callback):
            with pytest.raises(EchoException):
                echo.error()

        assert callback.called
        assert callback.call_args_list == [
            call((), {}, None, (EchoException, exc, ANY))
        ]
Exemple #51
0
def entrypoint_waiter(container, method_name, timeout=30, callback=None):
    """ Context manager that waits until an entrypoint has fired, and
    the generated worker has exited and been torn down.

    It yields a :class:`nameko.testing.waiting.WaitResult` object that can be
    used to get the result returned (exception raised) by the entrypoint
    after the waiter has exited.

    :Parameters:
        container : ServiceContainer
            The container hosting the service owning the entrypoint
        method_name : str
            The name of the entrypoint decorated method on the service class
        timeout : int
            Maximum seconds to wait
        callback : callable
            Function to conditionally control whether the entrypoint_waiter
            should exit for a particular invocation

    The `timeout` argument specifies the maximum number of seconds the
    `entrypoint_waiter` should wait before exiting. It can be disabled by
    passing `None`. The default is 30 seconds.

    Optionally allows a `callback` to be provided which is invoked whenever
    the entrypoint fires. If provided, the callback must return `True`
    for the `entrypoint_waiter` to exit. The signature for the callback
    function is::

        def callback(worker_ctx, result, exc_info):
            pass

    Where there parameters are as follows:

        worker_ctx (WorkerContext): WorkerContext of the entrypoint call.

        result (object): The return value of the entrypoint.

        exc_info (tuple): Tuple as returned by `sys.exc_info` if the
            entrypoint raised an exception, otherwise `None`.

    **Usage**

    ::
        class Service(object):
            name = "service"

            @event_handler('srcservice', 'eventtype')
            def handle_event(self, msg):
                return msg

        container = ServiceContainer(Service, config)
        container.start()

        # basic
        with entrypoint_waiter(container, 'handle_event'):
            ...  # action that dispatches event

        # giving access to the result
        with entrypoint_waiter(container, 'handle_event') as result:
            ...  # action that dispatches event
        res = result.get()

        # with custom timeout
        with entrypoint_waiter(container, 'handle_event', timeout=5):
            ...  # action that dispatches event

        # with callback that waits until entrypoint stops raising
        def callback(worker_ctx, result, exc_info):
            if exc_info is None:
                return True

        with entrypoint_waiter(container, 'handle_event', callback=callback):
            ...  # action that dispatches event

    """
    if not get_extension(container, Entrypoint, method_name=method_name):
        raise RuntimeError("{} has no entrypoint `{}`".format(
            container.service_name, method_name))

    class Result(WaitResult):
        worker_ctx = None

        def send(self, worker_ctx, result, exc_info):
            self.worker_ctx = worker_ctx
            super(Result, self).send(result, exc_info)

    waiter_callback = callback
    waiter_result = Result()

    def on_worker_result(worker_ctx, result, exc_info):
        complete = False
        if worker_ctx.entrypoint.method_name == method_name:
            if not callable(waiter_callback):
                complete = True
            else:
                complete = waiter_callback(worker_ctx, result, exc_info)

        if complete:
            waiter_result.send(worker_ctx, result, exc_info)
        return complete

    def on_worker_teardown(worker_ctx):
        if waiter_result.worker_ctx is worker_ctx:
            return True
        return False

    exc = entrypoint_waiter.Timeout("Timeout on {}.{} after {} seconds".format(
        container.service_name, method_name, timeout))

    with eventlet.Timeout(timeout, exception=exc):
        with wait_for_call(
                container, '_worker_teardown',
                lambda args, kwargs, res, exc: on_worker_teardown(*args)):
            with wait_for_call(
                    container, '_worker_result',
                    lambda args, kwargs, res, exc: on_worker_result(*args)):
                yield waiter_result
Exemple #52
0
def entrypoint_waiter(container, method_name, timeout=30, callback=None):
    """ Context manager that waits until an entrypoint has fired, and
    the generated worker has exited and been torn down.

    It yields a :class:`nameko.testing.waiting.WaitResult` object that can be
    used to get the result returned (exception raised) by the entrypoint
    after the waiter has exited.

    :Parameters:
        container : ServiceContainer
            The container hosting the service owning the entrypoint
        method_name : str
            The name of the entrypoint decorated method on the service class
        timeout : int
            Maximum seconds to wait
        callback : callable
            Function to conditionally control whether the entrypoint_waiter
            should exit for a particular invocation

    The `timeout` argument specifies the maximum number of seconds the
    `entrypoint_waiter` should wait before exiting. It can be disabled by
    passing `None`. The default is 30 seconds.

    Optionally allows a `callback` to be provided which is invoked whenever
    the entrypoint fires. If provided, the callback must return `True`
    for the `entrypoint_waiter` to exit. The signature for the callback
    function is::

        def callback(worker_ctx, result, exc_info):
            pass

    Where there parameters are as follows:

        worker_ctx (WorkerContext): WorkerContext of the entrypoint call.

        result (object): The return value of the entrypoint.

        exc_info (tuple): Tuple as returned by `sys.exc_info` if the
            entrypoint raised an exception, otherwise `None`.

    **Usage**

    ::
        class Service(object):
            name = "service"

            @event_handler('srcservice', 'eventtype')
            def handle_event(self, msg):
                return msg

        container = ServiceContainer(Service, config)
        container.start()

        # basic
        with entrypoint_waiter(container, 'handle_event'):
            ...  # action that dispatches event

        # giving access to the result
        with entrypoint_waiter(container, 'handle_event') as result:
            ...  # action that dispatches event
        res = result.get()

        # with custom timeout
        with entrypoint_waiter(container, 'handle_event', timeout=5):
            ...  # action that dispatches event

        # with callback that waits until entrypoint stops raising
        def callback(worker_ctx, result, exc_info):
            if exc_info is None:
                return True

        with entrypoint_waiter(container, 'handle_event', callback=callback):
            ...  # action that dispatches event

    """
    if not get_extension(container, Entrypoint, method_name=method_name):
        raise RuntimeError("{} has no entrypoint `{}`".format(
            container.service_name, method_name))

    class Result(WaitResult):
        worker_ctx = None

        def send(self, worker_ctx, result, exc_info):
            self.worker_ctx = worker_ctx
            super(Result, self).send(result, exc_info)

    waiter_callback = callback
    waiter_result = Result()

    def on_worker_result(worker_ctx, result, exc_info):
        complete = False
        if worker_ctx.entrypoint.method_name == method_name:
            if not callable(waiter_callback):
                complete = True
            else:
                complete = waiter_callback(worker_ctx, result, exc_info)

        if complete:
            waiter_result.send(worker_ctx, result, exc_info)
        return complete

    def on_worker_teardown(worker_ctx):
        if waiter_result.worker_ctx is worker_ctx:
            return True
        return False

    exc = entrypoint_waiter.Timeout(
        "Timeout on {}.{} after {} seconds".format(
            container.service_name, method_name, timeout)
    )

    with eventlet.Timeout(timeout, exception=exc):
        with wait_for_call(
            container, '_worker_teardown',
            lambda args, kwargs, res, exc: on_worker_teardown(*args)
        ):
            with wait_for_call(
                container, '_worker_result',
                lambda args, kwargs, res, exc: on_worker_result(*args)
            ):
                yield waiter_result
Exemple #53
0
def test_disconnect_with_pending_reply(container_factory, rabbit_manager,
                                       rabbit_config):
    block = Event()

    class ExampleService(object):
        name = "exampleservice"

        def hook(self):
            pass  # pragma: no cover

        @rpc
        def method(self, arg):
            self.hook()
            block.wait()
            return arg

    container = container_factory(ExampleService, rabbit_config)
    container.start()

    vhost = rabbit_config['vhost']

    # get exampleservice's queue consumer connection while we know it's the
    # only active connection
    connections = get_rabbit_connections(vhost, rabbit_manager)
    assert len(connections) == 1
    container_connection = connections[0]

    with ServiceRpcProxy('exampleservice', rabbit_config) as proxy:

        # grab the proxy's connection too, the only other connection
        connections = get_rabbit_connections(vhost, rabbit_manager)
        assert len(connections) == 2
        proxy_connection = [
            conn for conn in connections if conn != container_connection
        ][0]

        counter = itertools.count(start=1)

        class ConnectionStillOpen(Exception):
            pass

        @retry(for_exceptions=ConnectionStillOpen, delay=0.2)
        def wait_for_connection_close(name):
            connections = get_rabbit_connections(vhost, rabbit_manager)
            for conn in connections:
                if conn['name'] == name:
                    raise ConnectionStillOpen(name)  # pragma: no cover

        def cb(args, kwargs, res, exc_info):
            # trigger a disconnection on the second call.
            # release running workers once the connection has been closed
            count = next(counter)
            if count == 2:
                rabbit_manager.delete_connection(proxy_connection['name'])
                wait_for_connection_close(proxy_connection['name'])
                block.send(True)
                return True

        # attach a callback to `hook` so we can close the connection
        # while there are requests in-flight
        with wait_for_call(ExampleService, 'hook', callback=cb):

            # make an async call that runs for some time
            async_call = proxy.method.call_async("hello")

            # make another call that will trigger the disconnection;
            # expect the blocking proxy to raise when the service reconnects
            with pytest.raises(RpcConnectionError):
                proxy.method("hello")

            # also expect the running call to raise, since the reply may have
            # been sent while the queue was gone (deleted on disconnect, and
            # not added until re-connect)
            with pytest.raises(RpcConnectionError):
                async_call.result()

        # proxy should work again afterwards
        assert proxy.method("hello") == "hello"