From fe9692ea6be848b4f2d99daffd598a9fbfe79f42 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Thu, 15 Jan 2015 18:21:17 -0600 Subject: [PATCH] Configure TCP Keep-Alive for certain Sessions If the user creates a keystoneclient.session.Session without passing a custom session, we will enable TCP Keep-Alive for the requests session used by keystoneclient's Session. novaclient and other clients can experience hung TCP connections. Most clients use keystoneclient's session and will need this merged here before they can make use of it in their projects. Change-Id: Ib70a8b3270d2492596b9fb8981b8584b85567a9c Closes-bug: 1323862 --- keystoneclient/session.py | 15 +++++++++++++++ keystoneclient/tests/test_session.py | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/keystoneclient/session.py b/keystoneclient/session.py index 1f606e43e..ae8736b3c 100644 --- a/keystoneclient/session.py +++ b/keystoneclient/session.py @@ -15,6 +15,7 @@ import functools import hashlib import logging import os +import socket import time from oslo.config import cfg @@ -123,6 +124,9 @@ class Session(object): redirect=_DEFAULT_REDIRECT_LIMIT): if not session: session = requests.Session() + # Use TCPKeepAliveAdapter to fix bug 1323862 + for scheme in session.adapters.keys(): + session.mount(scheme, TCPKeepAliveAdapter()) self.auth = auth self.session = session @@ -778,3 +782,14 @@ class Session(object): kwargs['timeout'] = args.timeout return cls._make(**kwargs) + + +class TCPKeepAliveAdapter(requests.adapters.HTTPAdapter): + """The custom adapter used to set TCP Keep-Alive on all connections.""" + def init_poolmanager(self, *args, **kwargs): + if requests.__version__ >= '2.4.1': + kwargs.setdefault('socket_options', [ + (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ]) + super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs) diff --git a/keystoneclient/tests/test_session.py b/keystoneclient/tests/test_session.py index 3cd7ae5f6..b6015f069 100644 --- a/keystoneclient/tests/test_session.py +++ b/keystoneclient/tests/test_session.py @@ -205,6 +205,19 @@ class SessionTests(utils.TestCase): self.assertThat(self.requests.request_history, matchers.HasLength(retries + 1)) + def test_uses_tcp_keepalive_by_default(self): + session = client_session.Session() + requests_session = session.session + self.assertIsInstance(requests_session.adapters['http://'], + client_session.TCPKeepAliveAdapter) + self.assertIsInstance(requests_session.adapters['https://'], + client_session.TCPKeepAliveAdapter) + + def test_does_not_set_tcp_keepalive_on_custom_sessions(self): + mock_session = mock.Mock() + client_session.Session(session=mock_session) + self.assertFalse(mock_session.mount.called) + class RedirectTests(utils.TestCase):