diff --git a/swiftclient/client.py b/swiftclient/client.py index b18241d3..7f4c35b2 100644 --- a/swiftclient/client.py +++ b/swiftclient/client.py @@ -140,8 +140,7 @@ def encode_meta_headers(headers): class HTTPConnection(object): def __init__(self, url, proxy=None, cacert=None, insecure=False, - ssl_compression=False, default_user_agent=None, - timeout=None): + ssl_compression=False, default_user_agent=None, timeout=None): """ Make an HTTPConnection or HTTPSConnection @@ -266,7 +265,9 @@ def http_connection(*arg, **kwarg): def get_auth_1_0(url, user, key, snet, **kwargs): cacert = kwargs.get('cacert', None) insecure = kwargs.get('insecure', False) - parsed, conn = http_connection(url, cacert=cacert, insecure=insecure) + timeout = kwargs.get('timeout', None) + parsed, conn = http_connection(url, cacert=cacert, insecure=insecure, + timeout=timeout) method = 'GET' conn.request(method, parsed.path, '', {'X-Auth-User': user, 'X-Auth-Key': key}) @@ -326,6 +327,7 @@ def get_auth_keystone(auth_url, user, key, os_options, **kwargs): """ insecure = kwargs.get('insecure', False) + timeout = kwargs.get('timeout', None) auth_version = kwargs.get('auth_version', '2.0') debug = logger.isEnabledFor(logging.DEBUG) and True or False @@ -346,7 +348,7 @@ def get_auth_keystone(auth_url, user, key, os_options, **kwargs): project_domain_id=os_options.get('project_domain_id'), debug=debug, cacert=kwargs.get('cacert'), - auth_url=auth_url, insecure=insecure) + auth_url=auth_url, insecure=insecure, timeout=timeout) except exceptions.Unauthorized: msg = 'Unauthorized. Check username, password and tenant name/id.' if auth_version in AUTH_VERSIONS_V3: @@ -394,13 +396,15 @@ def get_auth(auth_url, user, key, **kwargs): storage_url, token = None, None cacert = kwargs.get('cacert', None) insecure = kwargs.get('insecure', False) + timeout = kwargs.get('timeout', False) if auth_version in AUTH_VERSIONS_V1: storage_url, token = get_auth_1_0(auth_url, user, key, kwargs.get('snet'), cacert=cacert, - insecure=insecure) + insecure=insecure, + timeout=timeout) elif auth_version in AUTH_VERSIONS_V2 + AUTH_VERSIONS_V3: # We are handling a special use case here where the user argument # specifies both the user name and tenant name in the form tenant:user @@ -423,6 +427,7 @@ def get_auth(auth_url, user, key, **kwargs): key, os_options, cacert=cacert, insecure=insecure, + timeout=timeout, auth_version=auth_version) else: raise ClientException('Unknown auth_version %s specified.' @@ -1190,6 +1195,7 @@ class Connection(object): raise an exception to the caller. Setting this parameter to True will cause a retry after a backoff. + :param timeout: The connect timeout for the HTTP connection. """ self.authurl = authurl self.user = user @@ -1231,7 +1237,8 @@ class Connection(object): auth_version=self.auth_version, os_options=self.os_options, cacert=self.cacert, - insecure=self.insecure) + insecure=self.insecure, + timeout=self.timeout) def http_connection(self, url=None): return http_connection(url if url else self.url, diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py index 0c2a669d..397ecafa 100644 --- a/tests/unit/test_swiftclient.py +++ b/tests/unit/test_swiftclient.py @@ -108,18 +108,28 @@ class MockHttpResponse(object): self.headers.update(headers) class Raw(object): - def read(self): - pass - self.raw = Raw() + def __init__(self, headers): + self.headers = headers + + def read(self, **kw): + return "" + + def getheader(self, name, default): + return self.headers.get(name, default) + + self.raw = Raw(headers) def read(self): return "" + def close(self): + pass + def getheader(self, name, default): return self.headers.get(name, default) def getheaders(self): - return {"key1": "value1", "key2": "value2"} + return dict(self.headers) def fake_response(self): return self @@ -1404,6 +1414,45 @@ class TestConnection(MockHttpTest): ('HEAD', '/v1/AUTH_pre_url', '', {'x-auth-token': 'post_token'}), ]) + def test_timeout_passed_down(self): + # We want to avoid mocking http_connection(), and most especially + # avoid passing it down in argument. However, we cannot simply + # instantiate C=Connection(), then shim C.http_conn. Doing so would + # avoid some of the code under test (where _retry() invokes + # http_connection()), and would miss get_auth() completely. + # So, with regret, we do mock http_connection(), but with a very + # light shim that swaps out _request() as originally intended. + + orig_http_connection = c.http_connection + + timeouts = [] + + def my_request_handler(*a, **kw): + if 'timeout' in kw: + timeouts.append(kw['timeout']) + else: + timeouts.append(None) + return MockHttpResponse( + status=200, + headers={ + 'x-auth-token': 'a_token', + 'x-storage-url': 'http://files.example.com/v1/AUTH_user'}) + + def shim_connection(*a, **kw): + url, conn = orig_http_connection(*a, **kw) + conn._request = my_request_handler + return url, conn + + conn = c.Connection( + 'http://auth.example.com', 'user', 'password', timeout=33.0) + with mock.patch.multiple('swiftclient.client', + http_connection=shim_connection, + sleep=mock.DEFAULT): + conn.head_account() + + # 1 call is through get_auth, 1 call is HEAD for account + self.assertEqual(timeouts, [33.0, 33.0]) + def test_reset_stream(self): class LocalContents(object):