예제 #1
0
 def test_emtpy_remote_service_list(self):
     """
     A list without services should throw an AllServicesUnavailable exception
     """
     services = RemoteServiceList()
     with self.assertRaises(AllServicesUnavailable):
         services.call_first_available()
예제 #2
0
    def test_service_is_marked_as_unavailable(self):
        """
        Verify that a function that is failing `failure_threshold` times is
        marked as broken and skipped
        """

        # create a list of service where the first function yields an exception
        services = RemoteServiceList(expected_exception=CustomException)
        func = generate_failing_func(exception=CustomException)
        services.append(func)
        services.append(lambda: 42)

        # initially all services should be marked as functional
        self.assertEqual(services[0].state, State.FUNCTIONAL)
        self.assertEqual(services[1].state, State.FUNCTIONAL)

        # after calling for `failure_threshold` times the failing service
        # should be marked as UNAVAILABLE
        for _ in range(0, services.failure_threshold):
            self.assertEqual(services.call_first_available(), 42)

        self.assertGreater(func.call_count, 0)
        self.assertEqual(func.call_count, services.failure_threshold)
        self.assertEqual(services[0].state, State.UNAVAILABLE)

        # the second function must still be FUNCTIONAL
        self.assertEqual(services[1].state, State.FUNCTIONAL)

        # and return the expected value
        self.assertEqual(services.call_first_available(), 42)
예제 #3
0
    def test_failing_service_list_should_throw(self):
        """
        A list of failing services should throw an AllServicesUnavailable exception
        """
        services = RemoteServiceList()
        services.append(generate_failing_func())
        services.append(generate_failing_func())

        with self.assertRaises(AllServicesUnavailable):
            services.call_first_available()
예제 #4
0
    def test_recovery(self, dt_now):
        """
        Ensure that a failing function recovers after the configured timeout.
        Before the timeout expires other good functions should be used.
        """

        # build a service list with a variadic function & a lambda that tells
        # us that we are in failover mode
        services = RemoteServiceList(expected_exception=CustomException)
        func = generate_variadic_func(exception=CustomException)
        services.append(func)
        services.append(lambda: "failover")

        # record the current time for our mocking
        start_time = datetime.now()

        # initially return the current time
        dt_now.return_value = start_time

        # calling the function (before it is marked as failing) returns the
        # input parameters (as expected)
        assert services.call_first_available(1) == ((1, ), {})

        # mark the first function in the list as failing & move into the future
        func.fail = True
        dt_now.return_value += timedelta(seconds=1)

        # call n times until the function is marked as failed
        for _ in range(0, services.failure_threshold):
            assert services.call_first_available() == "failover"

        assert services[0].state == State.UNAVAILABLE

        # Every second until recovery call the first available service
        # The return value should always be 'failover'
        while dt_now.return_value <= start_time + timedelta(
                seconds=services.recovery_timeout):
            assert services.call_first_available() == "failover"
            dt_now.return_value += timedelta(seconds=1)

        # the state of the primary function should still be UNAVAILABLE
        assert services[0].state == State.UNAVAILABLE
        assert services[1].state == State.FUNCTIONAL

        # tell function to return again
        func.fail = False

        # after the recovery timeout the first service should start returing
        # again
        dt_now.return_value += timedelta(seconds=1)
        assert services.call_first_available(1) == ((1, ), {})
        assert services[0].state == State.FUNCTIONAL
예제 #5
0
    def test_recovery(self, dt_now):
        """
        Ensure that a failing function recovers after the configured timeout.
        Before the timeout expires other good functions should be used.
        """

        # build a service list with a variadic function & a lambda that tells
        # us that we are in failover mode
        services = RemoteServiceList(expected_exception=CustomException)
        func = generate_variadic_func(exception=CustomException)
        services.append(func)
        services.append(lambda: 'failover')

        # record the current time for our mocking
        start_time = datetime.now()

        # initially return the current time
        dt_now.return_value = start_time

        # calling the function (before it is marked as failing) returns the
        # input parameters (as expected)
        self.assertEqual(services.call_first_available(1), ((1,), {}))

        # mark the first function in the list as failing & move into the future
        func.fail = True
        dt_now.return_value += timedelta(seconds=1)

        # call n times until the function is marked as failed
        for _ in range(0, services.failure_threshold):
            self.assertEqual(services.call_first_available(), 'failover')

        self.assertEqual(services[0].state, State.UNAVAILABLE)

        # Every second until recovery call the first available service
        # The return value should always be 'failover'
        while dt_now.return_value <= start_time + timedelta(seconds=services.recovery_timeout):
            self.assertEqual(services.call_first_available(), 'failover')
            dt_now.return_value += timedelta(seconds=1)

        # the state of the primary function should still be UNAVAILABLE
        self.assertEqual(services[0].state, State.UNAVAILABLE)
        self.assertEqual(services[1].state, State.FUNCTIONAL)

        # tell function to return again
        func.fail = False

        # after the recovery timeout the first service should start returing again
        dt_now.return_value += timedelta(seconds=1)
        self.assertEqual(services.call_first_available(1), ((1,), {}))
        self.assertEqual(services[0].state, State.FUNCTIONAL)
예제 #6
0
 def test_passes_arguments(self):
     """
     A service should get arguments passed on
     """
     services = RemoteServiceList()
     services.append(lambda arg: arg)
     self.assertEqual(services.call_first_available(42), 42)
예제 #7
0
    def test_service_failover(self):
        """
        Services that fail should cause a failover to another service on first failure
        """
        services = RemoteServiceList()
        func = generate_failing_func()
        services.append(func)
        services.append(lambda: 42)

        self.assertEqual(services.call_first_available(), 42)
        self.assertEqual(func.call_count, 1)
예제 #8
0
    def test_custom_exception_handling(self):
        """
        A custom exception should be caught successfully
        """
        services = RemoteServiceList(expected_exception=CustomException)
        func = generate_failing_func(exception=CustomException)
        services.append(func)
        services.append(lambda: 23)

        self.assertEqual(services.call_first_available(), 23)
        self.assertEqual(func.call_count, 1)
예제 #9
0
    def test_passes_kwargs(self):
        """
        A function should get keyword-arguments passed on
        """
        services = RemoteServiceList()
        services.append(generate_passthru_func())

        # the arguments we pass into the service should be returned for investigation
        args, kwargs = services.call_first_available(1, 2, 3, one=1, two=2, three=3)

        self.assertEqual(args, (1,2,3))
        self.assertEqual(kwargs, dict(one=1, two=2, three=3))