diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py index 5e1e1e619..9604e89c1 100644 --- a/keystoneclient/middleware/auth_token.py +++ b/keystoneclient/middleware/auth_token.py @@ -318,6 +318,10 @@ class ConfigurationError(Exception): pass +class NetworkError(Exception): + pass + + class MiniResp(object): def __init__(self, error_message, env, headers=[]): # The HEAD method is unique: it must never return a body, even if @@ -424,6 +428,7 @@ class AuthProtocol(object): self.http_connect_timeout = (http_connect_timeout_cfg and int(http_connect_timeout_cfg)) self.auth_version = None + self.http_request_max_retries = 3 def _assert_valid_memcache_protection_config(self): if self._memcache_security_strategy: @@ -655,9 +660,8 @@ class AuthProtocol(object): """ conn = self._get_http_connection() - RETRIES = 3 + RETRIES = self.http_request_max_retries retry = 0 - while True: try: conn.request(method, path, **kwargs) @@ -667,7 +671,7 @@ class AuthProtocol(object): except Exception as e: if retry == RETRIES: self.LOG.error('HTTP connection exception: %s' % e) - raise ServiceError('Unable to communicate with keystone') + raise NetworkError('Unable to communicate with keystone') # NOTE(vish): sleep 0.5, 1, 2 self.LOG.warn('Retrying on HTTP connection exception: %s' % e) time.sleep(2.0 ** retry / 2) @@ -778,6 +782,10 @@ class AuthProtocol(object): expires = self._confirm_token_not_expired(data) self._cache_put(token_id, data, expires) return data + except NetworkError as e: + self.LOG.debug('Token validation failure.', exc_info=True) + self.LOG.warn("Authorization failed for token %s", user_token) + raise InvalidUserToken('Token authorization failed') except Exception as e: self.LOG.debug('Token validation failure.', exc_info=True) self._cache_store_invalid(user_token) diff --git a/tests/test_auth_token_middleware.py b/tests/test_auth_token_middleware.py index 10d45b085..439522599 100644 --- a/tests/test_auth_token_middleware.py +++ b/tests/test_auth_token_middleware.py @@ -558,6 +558,13 @@ class RaisingHTTPConnection(FakeHTTPConnection): raise AssertionError("HTTP request was called.") +class RaisingHTTPNetworkError(FakeHTTPConnection): + """An HTTPConnection that always raises network error.""" + + def request(self, method, path, **kwargs): + raise auth_token.NetworkError("Network connection error.") + + class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" def __init__(self, expected_env=None): @@ -1236,6 +1243,20 @@ class AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest): self.assertEquals(middleware.token_revocation_list_cache_timeout, datetime.timedelta(seconds=24)) + def test_http_error_not_cached_token(self): + """Test to don't cache token as invalid on network errors. + + We use UUID tokens since they are the easiest one to reach + get_http_connection. + """ + req = webob.Request.blank('/') + token = self.token_dict['uuid_token_default'] + req.headers['X-Auth-Token'] = token + self.set_fake_http(RaisingHTTPNetworkError) + self.middleware.http_request_max_retries = 0 + self.middleware(req.environ, self.start_fake_response) + self.assertEqual(self._get_cached_token(token), None) + class CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest): def setUp(self):