From 19bba346ba3a52ebd5a3f83d7ed259d7e8025489 Mon Sep 17 00:00:00 2001 From: Stuart McLaren Date: Thu, 4 Sep 2014 16:00:31 +0000 Subject: [PATCH] Add client_socket_timeout option Add a parameter to take advantage of the new(ish) eventlet socket timeout behaviour. Allows closing idle client connections after a period of time, eg: $ time nc localhost 9292 real 1m0.063s Setting 'client_socket_timeout = 0' means do not timeout. DocImpact Closes-bug: 1371022 Change-Id: I9e7edcbf25ece61dc16b8cd5a8bef5ed9a14e3d6 --- doc/source/configuring.rst | 9 ++++ etc/glance-api.conf | 5 ++ etc/glance-registry.conf | 5 ++ glance/common/wsgi.py | 12 ++++- glance/tests/functional/test_wsgi.py | 66 +++++++++++++++++++++++++++ glance/tests/unit/common/test_wsgi.py | 3 +- glance/tests/unit/test_opts.py | 2 + 7 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 glance/tests/functional/test_wsgi.py diff --git a/doc/source/configuring.rst b/doc/source/configuring.rst index 5984929f6c..9eb3a17111 100644 --- a/doc/source/configuring.rst +++ b/doc/source/configuring.rst @@ -173,6 +173,15 @@ Not supported on OS X. Optional. Default: ``600`` +* ``client_socket_timeout=SECONDS`` + +Timeout for client connections' socket operations. If an incoming +connection is idle for this period it will be closed. A value of `0` +means wait forever. + +Optional. Default: ``900`` + + * ``workers=PROCESSES`` Number of Glance API or Registry worker processes to start. Each worker diff --git a/etc/glance-api.conf b/etc/glance-api.conf index 63df3f9d73..c9cafd061f 100644 --- a/etc/glance-api.conf +++ b/etc/glance-api.conf @@ -31,6 +31,11 @@ backlog = 4096 # Not supported on OS X. #tcp_keepidle = 600 +# Timeout (in seconds) for client connections' socket operations. If an incoming +# connection is idle for this period it will be closed. A value of "0" +# means wait forever. +#client_socket_timeout = 900 + # API to use for accessing data. Default value points to sqlalchemy # package, it is also possible to use: glance.db.registry.api # data_api = glance.db.sqlalchemy.api diff --git a/etc/glance-registry.conf b/etc/glance-registry.conf index f7ce79562a..3e03b0b93a 100644 --- a/etc/glance-registry.conf +++ b/etc/glance-registry.conf @@ -25,6 +25,11 @@ backlog = 4096 # Not supported on OS X. #tcp_keepidle = 600 +# Timeout (in seconds) for client connections' socket operations. If an incoming +# connection is idle for this period it will be closed. A value of "0" +# means wait forever. +#client_socket_timeout = 900 + # API to use for accessing data. Default value points to sqlalchemy # package. #data_api = glance.db.sqlalchemy.api diff --git a/glance/common/wsgi.py b/glance/common/wsgi.py index 0379d5bba3..8c1df37a37 100644 --- a/glance/common/wsgi.py +++ b/glance/common/wsgi.py @@ -99,6 +99,11 @@ eventlet_opts = [ 'read successfully by the client, you simply have to ' 'set this option to False when you create a wsgi ' 'server.')), + cfg.IntOpt('client_socket_timeout', default=900, + help=_('Timeout for client connections\' socket operations. ' + 'If an incoming connection is idle for this number of ' + 'seconds it will be closed. A value of \'0\' means ' + 'wait forever.')), ] profiler_opts = [ @@ -367,6 +372,7 @@ class Server(object): :param has changed: callable to determine if a parameter has changed """ eventlet.wsgi.MAX_HEADER_LINE = CONF.max_header_line + self.client_socket_timeout = CONF.client_socket_timeout or None self.configure_socket(old_conf, has_changed) if self.initialize_glance_store: initialize_glance_store() @@ -447,7 +453,8 @@ class Server(object): log=self._wsgi_logger, custom_pool=self.pool, debug=False, - keepalive=CONF.http_keepalive) + keepalive=CONF.http_keepalive, + socket_timeout=self.client_socket_timeout) except socket.error as err: if err[0] != errno.EINVAL: raise @@ -463,7 +470,8 @@ class Server(object): eventlet.wsgi.server(sock, application, custom_pool=self.pool, log=self._wsgi_logger, debug=False, - keepalive=CONF.http_keepalive) + keepalive=CONF.http_keepalive, + socket_timeout=self.client_socket_timeout) def configure_socket(self, old_conf=None, has_changed=None): """ diff --git a/glance/tests/functional/test_wsgi.py b/glance/tests/functional/test_wsgi.py new file mode 100644 index 0000000000..1dcd8f5639 --- /dev/null +++ b/glance/tests/functional/test_wsgi.py @@ -0,0 +1,66 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for `glance.wsgi`.""" + +import re +import socket +import time + +from oslo.config import cfg +import testtools + +from glance.common import wsgi + +CONF = cfg.CONF + + +class TestWSGIServer(testtools.TestCase): + """WSGI server tests.""" + def test_client_socket_timeout(self): + CONF.set_default("workers", 0) + CONF.set_default("client_socket_timeout", 0.1) + """Verify connections are timed out as per 'client_socket_timeout'""" + greetings = 'Hello, World!!!' + + def hello_world(env, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + return [greetings] + + server = wsgi.Server() + server.start(hello_world, 0) + port = server.sock.getsockname()[1] + sock1 = socket.socket() + sock1.connect(("127.0.0.1", port)) + + fd = sock1.makefile('rw') + fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() + + buf = fd.read() + # Should succeed - no timeout + self.assertTrue(re.search(greetings, buf)) + + sock2 = socket.socket() + sock2.connect(("127.0.0.1", port)) + time.sleep(0.2) + + fd = sock2.makefile('rw') + fd.write(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + fd.flush() + + buf = fd.read() + # Should fail - connection timed out so we get nothing from the server + self.assertFalse(buf) diff --git a/glance/tests/unit/common/test_wsgi.py b/glance/tests/unit/common/test_wsgi.py index bc1d885241..632b5ae480 100644 --- a/glance/tests/unit/common/test_wsgi.py +++ b/glance/tests/unit/common/test_wsgi.py @@ -508,7 +508,8 @@ class ServerTest(test_utils.BaseTestCase): log=server._wsgi_logger, debug=False, custom_pool=server.pool, - keepalive=False) + keepalive=False, + socket_timeout=900) class TestHelpers(test_utils.BaseTestCase): diff --git a/glance/tests/unit/test_opts.py b/glance/tests/unit/test_opts.py index b6c74b527e..1421fbe02a 100644 --- a/glance/tests/unit/test_opts.py +++ b/glance/tests/unit/test_opts.py @@ -135,6 +135,7 @@ class OptsTestCase(utils.BaseTestCase): 'digest_algorithm', 'http_keepalive', 'disabled_notifications', + 'client_socket_timeout' ] self._check_opt_groups(opt_list, expected_opt_groups) @@ -184,6 +185,7 @@ class OptsTestCase(utils.BaseTestCase): 'config_file', 'digest_algorithm', 'http_keepalive', + 'client_socket_timeout' ] self._check_opt_groups(opt_list, expected_opt_groups)