def test_host_fallback(self): ably = AblyRest(token="foo") self.assertIn('http_max_retry_count', ably.http.CONNECTION_RETRY_DEFAULTS) def make_url(host): base_url = "%s://%s:%d" % (ably.http.preferred_scheme, host, ably.http.preferred_port) return urljoin(base_url, '/') with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch('requests.sessions.Session.send', side_effect=requests.exceptions.RequestException) as send_mock: with self.assertRaises(requests.exceptions.RequestException): ably.http.make_request('GET', '/', skip_auth=True) self.assertEqual( send_mock.call_count, ably.http.CONNECTION_RETRY_DEFAULTS['http_max_retry_count']) expected_urls_set = set([ make_url(host) for host in ([ably.http.preferred_host] + Defaults.get_fallback_rest_hosts(Options())) ]) for ((__, url), ___) in request_mock.call_args_list: self.assertIn(url, expected_urls_set) expected_urls_set.remove(url)
def test_no_retry_if_not_500_to_599_http_code(self): default_host = Defaults.get_rest_host(Options()) ably = AblyRest(token="foo") self.assertIn('http_max_retry_count', ably.http.CONNECTION_RETRY_DEFAULTS) default_url = "%s://%s:%d/" % ( ably.http.preferred_scheme, default_host, ably.http.preferred_port) def raise_ably_exception(*args, **kwagrs): raise AblyException(message="", status_code=600, code=50500) with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch('ably.util.exceptions.AblyException.raise_for_response', side_effect=raise_ably_exception) as send_mock: with self.assertRaises(AblyException): ably.http.make_request('GET', '/', skip_auth=True) self.assertEqual(send_mock.call_count, 1) self.assertEqual( request_mock.call_args, mock.call(mock.ANY, default_url, data=mock.ANY, headers=mock.ANY))
def test_tls_can_be_disabled(self): ably = AblyRest(token='foo', tls=False) self.assertFalse(ably.options.tls, msg="Expected encryption to be False") self.assertEqual(Defaults.port, Defaults.get_port(ably.options), msg="Unexpected port mismatch")
def test_no_retry_if_not_500_to_599_http_code(self): default_host = Defaults.get_rest_host(Options()) ably = AblyRest(token="foo") self.assertIn('http_max_retry_count', ably.http.CONNECTION_RETRY_DEFAULTS) default_url = "%s://%s:%d/" % (ably.http.preferred_scheme, default_host, ably.http.preferred_port) def raise_ably_exception(*args, **kwagrs): raise AblyException(message="", status_code=600, code=50500) with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch( 'ably.util.exceptions.AblyException.raise_for_response', side_effect=raise_ably_exception) as send_mock: with self.assertRaises(AblyException): ably.http.make_request('GET', '/', skip_auth=True) self.assertEqual(send_mock.call_count, 1) self.assertEqual( request_mock.call_args, mock.call(mock.ANY, default_url, data=mock.ANY, headers=mock.ANY))
def test_tls_defaults_to_true(self): ably = AblyRest(token='foo') self.assertTrue(ably.options.tls, msg="Expected encryption to default to true") self.assertEqual(Defaults.tls_port, Defaults.get_port(ably.options), msg="Unexpected port mismatch")
def test_host_fallback(self): ably = AblyRest(token="foo") self.assertIn('http_max_retry_count', ably.http.CONNECTION_RETRY_DEFAULTS) def make_url(host): base_url = "%s://%s:%d" % (ably.http.preferred_scheme, host, ably.http.preferred_port) return urljoin(base_url, '/') with mock.patch('requests.Request', wraps=requests.Request) as request_mock: with mock.patch('requests.sessions.Session.send', side_effect=requests.exceptions.RequestException ) as send_mock: with self.assertRaises(requests.exceptions.RequestException): ably.http.make_request('GET', '/', skip_auth=True) self.assertEqual( send_mock.call_count, ably.http. CONNECTION_RETRY_DEFAULTS['http_max_retry_count']) expected_urls_set = set([ make_url(host) for host in ([ably.http.preferred_host] + Defaults.get_fallback_rest_hosts(Options())) ]) for ((__, url), ___) in request_mock.call_args_list: self.assertIn(url, expected_urls_set) expected_urls_set.remove(url)
def test_specified_tls_port(self): ably = AblyRest(token='foo', tls_port=9999, tls=True) self.assertEqual( 9999, Defaults.get_port(ably.options), msg="Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port)
def __get_rest_hosts(self): """ Return the list of hosts as they should be tried. First comes the main host. Then the fallback hosts in random order. The returned list will have a length of up to http_max_retry_count. """ # Defaults host = self.rest_host if host is None: host = Defaults.rest_host environment = self.environment if environment is None: environment = Defaults.environment http_max_retry_count = self.http_max_retry_count if http_max_retry_count is None: http_max_retry_count = Defaults.http_max_retry_count # Prepend environment if environment != 'production': host = '%s-%s' % (environment, host) # Fallback hosts fallback_hosts = self.fallback_hosts if fallback_hosts is None: if host == Defaults.rest_host or self.fallback_hosts_use_default: fallback_hosts = Defaults.fallback_hosts elif environment != 'production': fallback_hosts = Defaults.get_environment_fallback_hosts(environment) else: fallback_hosts = [] # Explicit warning about deprecating the option if self.fallback_hosts_use_default: if environment != Defaults.environment: warnings.warn( "It is no longer required to set 'fallback_hosts_use_default', the correct fallback hosts " "are now inferred from the environment, 'fallback_hosts': {}" .format(','.join(fallback_hosts)), DeprecationWarning ) else: warnings.warn( "It is no longer required to set 'fallback_hosts_use_default': 'fallback_hosts': {}" .format(','.join(fallback_hosts)), DeprecationWarning ) # Shuffle fallback_hosts = list(fallback_hosts) random.shuffle(fallback_hosts) # First main host hosts = [host] + fallback_hosts hosts = hosts[:http_max_retry_count] return hosts
def test_fallback_hosts(self): # Specify the fallback_hosts (RSC15a) fallback_hosts = [ ['fallback1.com', 'fallback2.com'], [], ] # Fallback hosts specified (RSC15g1) for aux in fallback_hosts: ably = AblyRest(token='foo', fallback_hosts=aux) assert sorted(aux) == sorted( ably.options.get_fallback_rest_hosts()) # Specify environment (RSC15g2) ably = AblyRest(token='foo', environment='sandbox', http_max_retry_count=10) assert sorted( Defaults.get_environment_fallback_hosts('sandbox')) == sorted( ably.options.get_fallback_rest_hosts()) # Fallback hosts and environment not specified (RSC15g3) ably = AblyRest(token='foo', http_max_retry_count=10) assert sorted(Defaults.fallback_hosts) == sorted( ably.options.get_fallback_rest_hosts()) # Specify environment and fallback_hosts_use_default, no fallback hosts (RSC15g4) # We specify http_max_retry_count=10 so all the fallback hosts get in the list ably = AblyRest(token='foo', environment='not_considered', fallback_hosts_use_default=True, http_max_retry_count=10) assert sorted(Defaults.fallback_hosts) == sorted( ably.options.get_fallback_rest_hosts()) # RSC15f ably = AblyRest(token='foo') assert 600000 == ably.options.fallback_retry_timeout ably = AblyRest(token='foo', fallback_retry_timeout=1000) assert 1000 == ably.options.fallback_retry_timeout with warnings.catch_warnings(record=True) as ws: # Cause all warnings to always be triggered warnings.simplefilter("always") AblyRest(token='foo', fallback_hosts_use_default=True) # Verify warning is raised for fallback_hosts_use_default ws = [w for w in ws if issubclass(w.category, DeprecationWarning)] assert len(ws) == 1
def wrapper(http, *args, **kwargs): try: return func(http, *args, **kwargs) except requests.exceptions.ConnectionError as e: # if we cannot attempt a fallback, re-raise # TODO: See if we can determine why this failed fallback_hosts = Defaults.get_fallback_hosts(http.options) if kwargs.get("host") or not fallback_hosts: raise last_exception = None for host in fallback_hosts: try: kwargs["host"] = host return func(rest, *args, **kwargs) except requests.exceptions.ConnectionError as e: # TODO: as above last_exception = e raise last_exception
def preferred_host(self): return Defaults.get_host(self.options)
def test_specified_non_tls_port(self): ably = AblyRest(token='foo', port=9998, tls=False) self.assertEqual(9998, Defaults.get_port(ably.options), msg="Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port)
def preferred_scheme(self): return Defaults.get_scheme(self.options)
def test_tls_defaults_to_true(self): ably = AblyRest(token='foo') assert ably.options.tls, "Expected encryption to default to true" assert Defaults.tls_port == Defaults.get_port( ably.options), "Unexpected port mismatch"
def preferred_host(self): return Defaults.get_rest_host(self.options)
def preferred_port(self): return Defaults.get_port(self.options)
def test_tls_can_be_disabled(self): ably = AblyRest(Options(tls=False)) self.assertFalse(ably.options.tls, msg="Expected encryption to be False") self.assertEqual(Defaults.port, Defaults.get_port(ably.options), msg="Unexpected port mismatch")
def make_request(self, method, path, headers=None, body=None, native_data=None, skip_auth=False, timeout=None): fallback_hosts = Defaults.get_fallback_rest_hosts(self.__options) if fallback_hosts: fallback_hosts.insert(0, self.preferred_host) fallback_hosts = itertools.cycle(fallback_hosts) if native_data is not None and body is not None: raise ValueError("make_request takes either body or native_data") elif native_data is not None: body = self.dump_body(native_data) if body: all_headers = HttpUtils.default_post_headers( self.options.use_binary_protocol) else: all_headers = HttpUtils.default_get_headers( self.options.use_binary_protocol) if not skip_auth: if self.auth.auth_mechanism == Auth.Method.BASIC and self.preferred_scheme.lower() == 'http': raise AblyException( "Cannot use Basic Auth over non-TLS connections", 401, 40103) all_headers.update(self.auth._get_auth_headers()) if headers: all_headers.update(headers) http_open_timeout = self.http_open_timeout http_request_timeout = self.http_request_timeout if fallback_hosts: http_max_retry_count = self.http_max_retry_count else: http_max_retry_count = 1 http_max_retry_duration = self.http_max_retry_duration requested_at = time.time() for retry_count in range(http_max_retry_count): host = next(fallback_hosts) if fallback_hosts else self.preferred_host if self.options.environment: host = self.options.environment + '-' + host base_url = "%s://%s:%d" % (self.preferred_scheme, host, self.preferred_port) url = urljoin(base_url, path) request = requests.Request(method, url, data=body, headers=all_headers) prepped = self.__session.prepare_request(request) try: response = self.__session.send( prepped, timeout=(http_open_timeout, http_request_timeout)) except Exception as e: # Need to catch `Exception`, see: # https://github.com/kennethreitz/requests/issues/1236#issuecomment-133312626 # if last try or cumulative timeout is done, throw exception up time_passed = time.time() - requested_at if retry_count == http_max_retry_count - 1 or \ time_passed > http_max_retry_duration: raise e else: try: AblyException.raise_for_response(response) return Response(response) except AblyException as e: if not e.is_server_error: raise e
def test_specified_non_tls_port(self): ably = AblyRest(token='foo', port=9998, tls=False) assert 9998 == Defaults.get_port( ably.options ), "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port
def test_tls_defaults_to_true(self): ably = AblyRest() self.assertTrue(ably.options.tls, msg="Expected encryption to default to true") self.assertEqual(Defaults.tls_port, Defaults.get_port(ably.options), msg="Unexpected port mismatch")
def test_tls_can_be_disabled(self): ably = AblyRest(token='foo', tls=False) assert not ably.options.tls, "Expected encryption to be False" assert Defaults.port == Defaults.get_port( ably.options), "Unexpected port mismatch"
def test_specified_tls_port(self): ably = AblyRest(token='foo', tls_port=9999, tls=True) assert 9999 == Defaults.get_port(ably.options), "Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port
def test_tls_defaults_to_true(self): ably = AblyRest(token='foo') assert ably.options.tls, "Expected encryption to default to true" assert Defaults.tls_port == Defaults.get_port(ably.options), "Unexpected port mismatch"
def test_tls_can_be_disabled(self): ably = AblyRest(token='foo', tls=False) assert not ably.options.tls, "Expected encryption to be False" assert Defaults.port == Defaults.get_port(ably.options), "Unexpected port mismatch"
def test_specified_port(self): ably = AblyRest(Options(port=9998, tls_port=9999)) self.assertEqual(9999, Defaults.get_port(ably.options), msg="Unexpected port mismatch. Expected: 9999. Actual: %d" % ably.options.tls_port)