diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index 7b2af55a..622d5bf9 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -149,6 +149,9 @@ reseller_prefix = AUTH_ # Keystone server details auth_uri = http://keystonehost:35357/ +# Connect/read timeout to use when communicating with Keystone +http_timeout = 10.0 + # SSL-related options #insecure = False #certfile = diff --git a/swift3/s3_token_middleware.py b/swift3/s3_token_middleware.py index 3ec0233d..973671e4 100644 --- a/swift3/s3_token_middleware.py +++ b/swift3/s3_token_middleware.py @@ -59,6 +59,9 @@ class S3Token(object): self._app = app self._logger = logging.getLogger(conf.get('log_name', __name__)) self._logger.debug('Starting the %s component', PROTOCOL_NAME) + self._timeout = float(conf.get('http_timeout', '10.0')) + if not (0 < self._timeout <= 60): + raise ValueError('http_timeout must be between 0 and 60 seconds') self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_') # where to find the auth service (we use this to validate tokens) @@ -119,7 +122,8 @@ class S3Token(object): try: response = requests.post('%s/v2.0/s3tokens' % self._request_uri, headers=headers, data=creds_json, - verify=self._verify) + verify=self._verify, + timeout=self._timeout) except requests.exceptions.RequestException as e: self._logger.info('HTTP connection exception: %s', e) resp = self._deny_request('InvalidURI') diff --git a/swift3/test/unit/test_s3_token_middleware.py b/swift3/test/unit/test_s3_token_middleware.py index 70a49072..9b161883 100644 --- a/swift3/test/unit/test_s3_token_middleware.py +++ b/swift3/test/unit/test_s3_token_middleware.py @@ -255,6 +255,53 @@ class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase): self.assertEqual('Either auth_uri or auth_host required', cm.exception.message) + @mock.patch.object(requests, 'post') + def test_http_timeout(self, MOCK_REQUEST): + self.middleware = s3_token.filter_factory({ + 'http_timeout': '2', + 'auth_uri': 'http://example.com', + })(FakeApp()) + + MOCK_REQUEST.return_value = TestResponse({ + 'status_code': 201, + 'text': json.dumps(GOOD_RESPONSE)}) + + req = Request.blank('/v1/AUTH_cfa/c/o') + req.headers['Authorization'] = 'AWS access:signature' + req.headers['X-Storage-Token'] = 'token' + req.get_response(self.middleware) + + self.assertTrue(MOCK_REQUEST.called) + mock_args, mock_kwargs = MOCK_REQUEST.call_args + self.assertEqual(mock_kwargs['timeout'], 2) + + def test_http_timeout_option(self): + good_values = ['1', '5.3', '10', '.001'] + for val in good_values: + middleware = s3_token.filter_factory({ + 'http_timeout': val, + 'auth_uri': 'http://example.com', + })(FakeApp()) + self.assertEqual(float(val), middleware._timeout) + + bad_values = ['1, 4', '-3', '100', 'foo', '0'] + for val in bad_values: + with self.assertRaises(ValueError) as ctx: + s3_token.filter_factory({ + 'http_timeout': val, + 'auth_uri': 'http://example.com', + })(FakeApp()) + self.assertTrue(ctx.exception.args[0].startswith(( + 'invalid literal for float():', + 'could not convert string to float:', + 'http_timeout must be between 0 and 60 seconds', + )), 'Unexpected error message: %s' % ctx.exception) + + # default is 10 seconds + middleware = s3_token.filter_factory({ + 'auth_uri': 'http://example.com'})(FakeApp()) + self.assertEqual(10, middleware._timeout) + def test_unicode_path(self): url = u'/v1/AUTH_cfa/c/euro\u20ac'.encode('utf8') req = Request.blank(urllib.parse.quote(url))