def test_can_have_a_different_should_retry_function(self): """ The ``should_retry`` function does not have to be a :obj:`ShouldDelayAndRetry`. """ expected = Retry(effect=Effect(1), should_retry=ANY) actual = Retry(effect=Effect(1), should_retry=lambda _: False) seq = [ retry_sequence(expected, [lambda _: raise_(Exception())]) ] self.assertRaises(Exception, perform_sequence, seq, Effect(actual))
def lb_req(url, json_response, response): """ Return a SequenceDispatcher two-tuple that matches a service request to a particular load balancer endpoint (using GET), and returns the given ``response`` as the content in an HTTP 200 ``StubResponse``. """ if isinstance(response, Exception): def handler(i): raise response log_seq = [] else: def handler(i): return (StubResponse(200, {}), response) log_seq = [(Log(mock.ANY, mock.ANY), lambda i: None)] return ( Retry( effect=mock.ANY, should_retry=ShouldDelayAndRetry( can_retry=retry_times(5), next_interval=exponential_backoff_interval(2)) ), nested_sequence([ (service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'GET', url, json_response=json_response).intent, handler) ] + log_seq) )
def perform_retry_without_delay(actual_retry_intent): should_retry = actual_retry_intent.should_retry if isinstance(should_retry, ShouldDelayAndRetry): def should_retry(exc_info): exc_type, exc_value, exc_traceback = exc_info failure = Failure(exc_value, exc_type, exc_traceback) return Effect( Constant( actual_retry_intent.should_retry.can_retry(failure))) new_retry_effect = Effect( Retry(effect=actual_retry_intent.effect, should_retry=should_retry)) _dispatchers = [ TypeDispatcher({Retry: perform_retry}), base_dispatcher ] if fallback_dispatcher is not None: _dispatchers.append(fallback_dispatcher) seq = [(expected_retry_intent.effect.intent, performer) for performer in performers] return perform_sequence(seq, new_retry_effect, ComposedDispatcher(_dispatchers))
def test_do_not_have_to_expect_an_exact_can_retry(self): """ The expected retry intent does not actually have to specify the exact ``can_retry`` function, since it might just be a lambda, which is hard to compare or hash. """ expected = Retry(effect=Effect(1), should_retry=ANY) actual = Retry(effect=Effect(1), should_retry=ShouldDelayAndRetry( can_retry=lambda _: False, next_interval=repeating_interval(10))) seq = [ retry_sequence(expected, [lambda _: raise_(Exception())]) ] self.assertRaises(Exception, perform_sequence, seq, Effect(actual))
def node_feed_req(lb_id, node_id, response): """ Return (intent, performer) sequence for getting clb node's feed that wrapped with retry intent. :param lb_id: Lodbalancer ID :param node_id: LB node ID :param response: The response returned when getting CLB node feed. It is either string containing feed or Exception object that will be raised when getting the feed :return: (intent, performer) tuple """ if isinstance(response, Exception): def handler(i): raise response else: def handler(i): return response return (Retry(effect=mock.ANY, should_retry=ShouldDelayAndRetry( can_retry=retry_times(5), next_interval=exponential_backoff_interval(2))), nested_sequence([(("gcnf", lb_id, node_id), handler)]))
def _perform_add_event(self, response_sequence): """ Given a sequence of functions that take an intent and returns a response (or raises an exception), perform :func:`add_event` and return the result. """ log = object() eff = add_event(self.event, 'tid', 'ord', log) uid = '00000000-0000-0000-0000-000000000000' svrq = service_request( ServiceType.CLOUD_FEEDS, 'POST', 'autoscale/events', headers={'content-type': ['application/vnd.rackspace.atom+json']}, data=self._get_request('INFO', uid, 'tid'), log=log, success_pred=has_code(201), json_response=False) seq = [ (TenantScope(mock.ANY, 'tid'), nested_sequence([ retry_sequence( Retry(effect=svrq, should_retry=ShouldDelayAndRetry( can_retry=mock.ANY, next_interval=exponential_backoff_interval(2))), response_sequence) ])) ] return perform_sequence(seq, eff)
def test_perform_retry(self): """ When the specified effect is successful, its result is propagated. """ retry = Retry(effect=Effect(Constant('foo')), should_retry=lambda e: 1 / 0) result = sync_perform(self.dispatcher, Effect(retry)) self.assertEqual(result, 'foo')
def simple_intents(): return [ Authenticate(None, None, None), InvalidateToken(None, None), Request(method='GET', url='http://example.com/'), Retry(effect=Effect(Constant(None)), should_retry=lambda e: False), Delay(0), Constant(None), ReadReference(ref=Reference(None)), ]
def test_retry_effect(self): """ :func:`retry_effect` takes an effect and returns an :obj:`Effect` of :obj:`Retry`, with a :obj:`ShouldDelayAndRetry` as the should_retry callable. """ can_retry = lambda f: True next_interval = lambda f: 1 eff = retry_effect(STUB, can_retry, next_interval) self.assertEqual( eff, Effect( Retry(effect=STUB, should_retry=ShouldDelayAndRetry( can_retry=can_retry, next_interval=next_interval))))
def test_retry_sequence_fails_if_mismatch_sequence(self): """ Fail if the wrong number of performers are given. """ r = Retry( effect=Effect(1), should_retry=ShouldDelayAndRetry( can_retry=retry_times(5), next_interval=repeating_interval(10))) seq = [ retry_sequence(r, [lambda _: raise_(Exception()), lambda _: raise_(Exception())]) ] self.assertRaises(AssertionError, perform_sequence, seq, Effect(r))
def test_retry_sequence_retries_without_delays(self): """ Perform the wrapped effect with the performers given, without any delay even if the original intent had a delay. """ r = Retry( effect=Effect(1), should_retry=ShouldDelayAndRetry( can_retry=retry_times(5), next_interval=repeating_interval(10))) seq = [ retry_sequence(r, [lambda _: raise_(Exception()), lambda _: raise_(Exception()), lambda _: "yay done"]) ] self.assertEqual(perform_sequence(seq, Effect(r)), "yay done")
def test_perform_retry_retries_on_error(self): """ When the specified effect raises, it is retried when should_retry returns an Effect of True. """ func = _repeated_effect_func(lambda: _raise(RuntimeError("foo")), lambda: "final") def should_retry(exc_info): if (exc_info[0] is RuntimeError and exc_info[1].message == "foo"): return Effect(Constant(True)) else: return Effect(Constant(False)) retry = Retry(effect=Effect(Func(func)), should_retry=should_retry) result = sync_perform(self.dispatcher, Effect(retry)) self.assertEqual(result, "final")
def test_fallback(self): """ Accept a ``fallback`` dispatcher that will be used if a performer returns an effect for an intent that is not covered by the base dispatcher. """ def dispatch_2(intent): if intent == 2: return sync_performer(lambda d, i: "yay done") r = Retry( effect=Effect(1), should_retry=ShouldDelayAndRetry( can_retry=retry_times(5), next_interval=repeating_interval(10))) seq = [ retry_sequence(r, [lambda _: Effect(2)], fallback_dispatcher=ComposedDispatcher( [dispatch_2, base_dispatcher])) ] self.assertEqual(perform_sequence(seq, Effect(r)), "yay done")