summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTihomir Trifonov <t.trifonov@gmail.com>2014-02-20 23:11:34 +0200
committerTihomir Trifonov <t.trifonov@gmail.com>2014-02-27 16:21:25 +0200
commit36db3b95f556d5f57a2bf49303b24a0b25b4b7e8 (patch)
treefed832962cb00fec3209331c6a7a3bc8145e9adc
parent4a2c9b2621b2abb65f03b4003f648deebd4baebc (diff)
downloadpython-novaclient-36db3b95f556d5f57a2bf49303b24a0b25b4b7e8.tar.gz
Fix in in novaclient, to avoid excessive conns
The current client creates new .Session() on each request, but since Horizon is a stateless app, each Session creates new HttpAdapter, which itself has its own connection pool, and each connection there is used (almost) once and then is being kept in the pool(with Keep-Alive) for a certain amount of time(waiting for inactivity timeout). The problem is that the connection cannot be used anymore from next Django calls - they create new connection pool with new connections, etc. This keeps lots of open connections on the server. Now the client will store an HTTPAdapter for each URL into a singleton object, and will reuse its connections between Django calls, but still taking advantage of Sessions during a single page load(although we do not fully use this). Note: the default pool behavior is non-blocking, which means that if the max_pool_size is reached, a new connection will still be opened, and when released - will be discarded. It could be useful to add max_pool_size param into settings, for performance fine-tuning. The default max_pool_size is 10. Since python-novaclient is also used from non-Django projects, I'd expect feedback from more people on the impact this change could have over other projects. Patch Set 3: Removed explicit connection closing, leaving connections open in the pool. Change-Id: Icc9dc2fa2863d0e0e26a86c8180f2e0fbcd1fcff Closes-Bug: #1247056
-rw-r--r--novaclient/client.py38
1 files changed, 33 insertions, 5 deletions
diff --git a/novaclient/client.py b/novaclient/client.py
index 7a7c06fe..0b9aeeec 100644
--- a/novaclient/client.py
+++ b/novaclient/client.py
@@ -24,6 +24,7 @@ import logging
import time
import requests
+from requests import adapters
try:
import json
@@ -38,8 +39,20 @@ from novaclient import service_catalog
from novaclient import utils
-class HTTPClient(object):
+_ADAPTERS = {}
+
+
+def _adapter_pool(url):
+ """
+ Store and reuse HTTP adapters per Service URL.
+ """
+ if url not in _ADAPTERS:
+ _ADAPTERS[url] = adapters.HTTPAdapter()
+ return _ADAPTERS[url]
+
+
+class HTTPClient(object):
USER_AGENT = 'python-novaclient'
def __init__(self, user, password, projectid=None, auth_url=None,
@@ -105,8 +118,10 @@ class HTTPClient(object):
self.auth_system = auth_system
self.auth_plugin = auth_plugin
-
+ self._current_url = None
+ self._http = None
self._logger = logging.getLogger(__name__)
+
if self.http_log_debug and not self._logger.handlers:
# Logging level is already set on the root logger
ch = logging.StreamHandler()
@@ -119,8 +134,6 @@ class HTTPClient(object):
# have to set it up here on WARNING (its original level)
# otherwise we will get all the requests logging messages
rql.setLevel(logging.WARNING)
- # requests within the same session can reuse TCP connections from pool
- self.http = requests.Session()
def use_token_cache(self, use_it):
self.os_cache = use_it
@@ -167,6 +180,20 @@ class HTTPClient(object):
'headers': resp.headers,
'text': resp.text})
+ def http(self, url):
+ magic_tuple = parse.urlsplit(url)
+ scheme, netloc, path, query, frag = magic_tuple
+ service_url = '%s://%s' % (scheme, netloc)
+ if self._current_url != service_url:
+ # Invalidate Session object in case the url is somehow changed
+ if self._http:
+ self._http.close()
+ self._current_url = service_url
+ self._logger.debug("New session created for: (%s)" % service_url)
+ self._http = requests.Session()
+ self._http.mount(service_url, _adapter_pool(service_url))
+ return self._http
+
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
@@ -180,10 +207,11 @@ class HTTPClient(object):
kwargs['verify'] = self.verify_cert
self.http_log_req(method, url, kwargs)
- resp = self.http.request(
+ resp = self.http(url).request(
method,
url,
**kwargs)
+
self.http_log_resp(resp)
if resp.text: