From 16a821e00d15520d2f6e940e184bd289b8782620 Mon Sep 17 00:00:00 2001
From: abhishekkekane <abhishek.kekane@nttdata.com>
Date: Tue, 21 Oct 2014 04:39:59 -0700
Subject: [PATCH] Eventlet green threads not released back to pool

Presently, the wsgi server allows persist connections. Hence even after
the response is sent to the client, it doesn't close the client socket
connection. Because of this problem, the green thread is not released
back to the pool.

In order to close the client socket connection explicitly after the
response is sent and read successfully by the client, you simply have to
set keepalive to False when you create a wsgi server.

DocImpact:
Added http_keepalive option (default=True).

SecurityImpact

Closes-Bug: #1361360
Change-Id: I93aaca24935a4f3096210233097dd6b8c5440176
---
 doc/source/configuring.rst            | 11 +++++++++++
 etc/glance-api.conf                   |  7 +++++++
 etc/glance-registry.conf              |  7 +++++++
 glance/common/wsgi.py                 |  9 +++++++--
 glance/tests/unit/common/test_wsgi.py | 22 ++++++++++++++++++++++
 glance/tests/unit/test_opts.py        |  2 ++
 6 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/doc/source/configuring.rst b/doc/source/configuring.rst
index e3567cd7b5..67f8ec3382 100644
--- a/doc/source/configuring.rst
+++ b/doc/source/configuring.rst
@@ -1441,3 +1441,14 @@ return a ValueError exception with "No such digest method" error.
 * ``digest_algorithm=<algorithm>``
 
 Optional. Default: ``sha1``
+
+Configuring http_keepalive option
+----------------------------------
+
+* ``http_keepalive=<True|False>``
+
+If False, server will return the header "Connection: close", If True, server
+will return "Connection: Keep-Alive" in its responses. In order to close the
+client socket connection explicitly after the response is sent and read
+successfully by the client, you simply have to set this option to False when
+you create a wsgi server.
diff --git a/etc/glance-api.conf b/etc/glance-api.conf
index 3ab9b38569..7c9c2ebb38 100644
--- a/etc/glance-api.conf
+++ b/etc/glance-api.conf
@@ -105,6 +105,13 @@ backlog = 4096
 # represent the proxy's URL.
 #public_endpoint=<None>
 
+# http_keepalive option. If False, server will return the header
+# "Connection: close", If True, server will return "Connection: Keep-Alive"
+# in its responses. In order to close the client socket connection
+# explicitly after the response is sent and read successfully by the client,
+# you simply have to set this option to False when you create a wsgi server.
+#http_keepalive = True
+
 # ================= Syslog Options ============================
 
 # Send logs to syslog (/dev/log) instead of to file specified
diff --git a/etc/glance-registry.conf b/etc/glance-registry.conf
index 45b95871a5..247a88ed3d 100644
--- a/etc/glance-registry.conf
+++ b/etc/glance-registry.conf
@@ -54,6 +54,13 @@ limit_param_default = 25
 # Default: False
 #sqlalchemy_debug = True
 
+# http_keepalive option. If False, server will return the header
+# "Connection: close", If True, server will return "Connection: Keep-Alive"
+# in its responses. In order to close the client socket connection
+# explicitly after the response is sent and read successfully by the client,
+# you simply have to set this option to False when you create a wsgi server.
+#http_keepalive = True
+
 # ================= Syslog Options ============================
 
 # Send logs to syslog (/dev/log) instead of to file specified
diff --git a/glance/common/wsgi.py b/glance/common/wsgi.py
index 05307ab641..2b84a81b64 100644
--- a/glance/common/wsgi.py
+++ b/glance/common/wsgi.py
@@ -88,6 +88,9 @@ eventlet_opts = [
                       'max_header_line may need to be increased when using '
                       'large tokens (typically those generated by the '
                       'Keystone v3 API with big service catalogs')),
+    cfg.BoolOpt('http_keepalive', default=True,
+                help=_('If False, closes the client socket connection '
+                       'explicitly.')),
 ]
 
 profiler_opts = [
@@ -344,7 +347,8 @@ class Server(object):
                                  self.application,
                                  log=self._wsgi_logger,
                                  custom_pool=self.pool,
-                                 debug=False)
+                                 debug=False,
+                                 keepalive=CONF.http_keepalive)
         except socket.error as err:
             if err[0] != errno.EINVAL:
                 raise
@@ -355,7 +359,8 @@ class Server(object):
         LOG.info(_LI("Starting single process server"))
         eventlet.wsgi.server(sock, application, custom_pool=self.pool,
                              log=self._wsgi_logger,
-                             debug=False)
+                             debug=False,
+                             keepalive=CONF.http_keepalive)
 
 
 class Middleware(object):
diff --git a/glance/tests/unit/common/test_wsgi.py b/glance/tests/unit/common/test_wsgi.py
index f548a9045b..6908922e57 100644
--- a/glance/tests/unit/common/test_wsgi.py
+++ b/glance/tests/unit/common/test_wsgi.py
@@ -466,6 +466,28 @@ class ServerTest(test_utils.BaseTestCase):
         actual = wsgi.Server(threads=1).create_pool()
         self.assertIsInstance(actual, eventlet.greenpool.GreenPool)
 
+    @mock.patch.object(wsgi, 'get_socket')
+    def test_http_keepalive(self, mock_get_socket):
+        fake_socket = 'fake_socket'
+        mock_get_socket.return_value = 'fake_socket'
+        self.config(http_keepalive=False)
+        self.config(workers=0)
+
+        server = wsgi.Server(threads=1)
+        # mocking eventlet.wsgi server method to check it is called with
+        # configured 'http_keepalive' value.
+        with mock.patch.object(eventlet.wsgi,
+                               'server') as mock_server:
+            fake_application = "fake-application"
+            server.start(fake_application, 0)
+            server.wait()
+            mock_server.assert_called_once_with(fake_socket,
+                                                fake_application,
+                                                log=server._wsgi_logger,
+                                                debug=False,
+                                                custom_pool=server.pool,
+                                                keepalive=False)
+
 
 class TestHelpers(test_utils.BaseTestCase):
 
diff --git a/glance/tests/unit/test_opts.py b/glance/tests/unit/test_opts.py
index d7243612fd..ca3a8dff1c 100644
--- a/glance/tests/unit/test_opts.py
+++ b/glance/tests/unit/test_opts.py
@@ -146,6 +146,7 @@ class OptsTestCase(utils.BaseTestCase):
             'config_file',
             'public_endpoint',
             'digest_algorithm',
+            'http_keepalive',
         ]
 
         self._check_opt_groups(opt_list, expected_opt_groups)
@@ -207,6 +208,7 @@ class OptsTestCase(utils.BaseTestCase):
             'flavor',
             'config_file',
             'digest_algorithm',
+            'http_keepalive',
         ]
 
         self._check_opt_groups(opt_list, expected_opt_groups)