Allow specifying client and service info to user_agent
Allow specifying a service name and version to the session and a client name and version to the adapter. The way this will work is that libraries such as keystoneclient will pass client_name and client_version when creating their adapter. Then when nova or another service creates a session it will provide the service name and version. The combination of these will be used to provide a meaningful user agent. Change-Id: Ibe516d9b248513579d5e8ca94015c4ae9c00f3f9 Closes-Bug: #1614846
This commit is contained in:
parent
811cd1f3e1
commit
eb5571a6ca
@ -49,6 +49,11 @@ class Adapter(object):
|
||||
to every request passing through the
|
||||
adapter. Headers of the same name specified
|
||||
per request will take priority.
|
||||
:param str client_name: The name of the client that created the adapter.
|
||||
This will be used to create the user_agent.
|
||||
:param str client_version: The version of the client that created the
|
||||
adapter. This will be used to create the
|
||||
user_agent.
|
||||
"""
|
||||
|
||||
@positional()
|
||||
@ -56,7 +61,8 @@ class Adapter(object):
|
||||
interface=None, region_name=None, endpoint_override=None,
|
||||
version=None, auth=None, user_agent=None,
|
||||
connect_retries=None, logger=None, allow={},
|
||||
additional_headers=None):
|
||||
additional_headers=None, client_name=None,
|
||||
client_version=None):
|
||||
# NOTE(jamielennox): when adding new parameters to adapter please also
|
||||
# add them to the adapter call in httpclient.HTTPClient.__init__ as
|
||||
# well as to load_adapter_from_argparse below if the argument is
|
||||
@ -74,6 +80,8 @@ class Adapter(object):
|
||||
self.connect_retries = connect_retries
|
||||
self.logger = logger
|
||||
self.allow = allow
|
||||
self.client_name = client_name
|
||||
self.client_version = client_version
|
||||
self.additional_headers = additional_headers or {}
|
||||
|
||||
def _set_endpoint_filter_kwargs(self, kwargs):
|
||||
@ -105,6 +113,10 @@ class Adapter(object):
|
||||
kwargs.setdefault('logger', self.logger)
|
||||
if self.allow:
|
||||
kwargs.setdefault('allow', self.allow)
|
||||
if self.client_name:
|
||||
kwargs.setdefault('client_name', self.client_name)
|
||||
if self.client_version:
|
||||
kwargs.setdefault('client_version', self.client_version)
|
||||
|
||||
for k, v in self.additional_headers.items():
|
||||
kwargs.setdefault('headers', {}).setdefault(k, v)
|
||||
|
@ -134,7 +134,7 @@ def _determine_calling_package():
|
||||
# hit the bottom of the frame stack
|
||||
break
|
||||
|
||||
return None
|
||||
return ''
|
||||
|
||||
|
||||
def _determine_user_agent():
|
||||
@ -153,10 +153,10 @@ def _determine_user_agent():
|
||||
name = sys.argv[0]
|
||||
except IndexError:
|
||||
# sys.argv is empty, usually the Python interpreter prevents this.
|
||||
return None
|
||||
return ''
|
||||
|
||||
if not name:
|
||||
return None
|
||||
return ''
|
||||
|
||||
name = os.path.basename(name)
|
||||
if name in ignored:
|
||||
@ -207,6 +207,15 @@ class Session(object):
|
||||
to every request passing through the
|
||||
session. Headers of the same name specified
|
||||
per request will take priority.
|
||||
:param str app_name: The name of the application that is creating the
|
||||
session. This will be used to create the user_agent.
|
||||
:param str app_version: The version of the application creating the
|
||||
session. This will be used to create the
|
||||
user_agent.
|
||||
:param list additional_user_agent: A list of tuple of name, version that
|
||||
will be added to the user agent. This
|
||||
can be used by libraries that are part
|
||||
of the communication process.
|
||||
"""
|
||||
|
||||
user_agent = None
|
||||
@ -218,7 +227,8 @@ class Session(object):
|
||||
@positional(2)
|
||||
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
|
||||
cert=None, timeout=None, user_agent=None,
|
||||
redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None):
|
||||
redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None,
|
||||
app_name=None, app_version=None, additional_user_agent=None):
|
||||
|
||||
self.auth = auth
|
||||
self.session = _construct_session(session)
|
||||
@ -228,19 +238,14 @@ class Session(object):
|
||||
self.timeout = None
|
||||
self.redirect = redirect
|
||||
self.additional_headers = additional_headers or {}
|
||||
self.app_name = app_name
|
||||
self.app_version = app_version
|
||||
self.additional_user_agent = additional_user_agent or []
|
||||
self._determined_user_agent = None
|
||||
|
||||
if timeout is not None:
|
||||
self.timeout = float(timeout)
|
||||
|
||||
# Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be
|
||||
# ordered by decreasing significance. If a user sets their product
|
||||
# that value will be used. Otherwise we attempt to derive a useful
|
||||
# product value. The value will be prepended it to the KSA version,
|
||||
# requests version, and then the Python version.
|
||||
|
||||
if user_agent is None:
|
||||
user_agent = _determine_user_agent()
|
||||
|
||||
if user_agent is not None:
|
||||
self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT)
|
||||
|
||||
@ -371,7 +376,7 @@ class Session(object):
|
||||
endpoint_filter=None, auth=None, requests_auth=None,
|
||||
raise_exc=True, allow_reauth=True, log=True,
|
||||
endpoint_override=None, connect_retries=0, logger=_logger,
|
||||
allow={}, **kwargs):
|
||||
allow={}, client_name=None, client_version=None, **kwargs):
|
||||
"""Send an HTTP request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
@ -499,7 +504,39 @@ class Session(object):
|
||||
elif self.user_agent:
|
||||
user_agent = headers.setdefault('User-Agent', self.user_agent)
|
||||
else:
|
||||
user_agent = headers.setdefault('User-Agent', DEFAULT_USER_AGENT)
|
||||
# Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be
|
||||
# ordered by decreasing significance. If a user sets their product
|
||||
# that value will be used. Otherwise we attempt to derive a useful
|
||||
# product value. The value will be prepended it to the KSA version,
|
||||
# requests version, and then the Python version.
|
||||
|
||||
agent = []
|
||||
|
||||
if self.app_name and self.app_version:
|
||||
agent.append('%s/%s' % (self.app_name, self.app_version))
|
||||
elif self.app_name:
|
||||
agent.append(self.app_name)
|
||||
|
||||
for additional in self.additional_user_agent:
|
||||
agent.append('%s/%s' % additional)
|
||||
|
||||
if client_name and client_version:
|
||||
agent.append('%s/%s' % (client_name, client_version))
|
||||
elif client_name:
|
||||
agent.append(client_name)
|
||||
|
||||
if not agent:
|
||||
# NOTE(jamielennox): determine_user_agent will return an empty
|
||||
# string on failure so checking for None will ensure it is only
|
||||
# called once even on failure.
|
||||
if self._determined_user_agent is None:
|
||||
self._determined_user_agent = _determine_user_agent()
|
||||
|
||||
if self._determined_user_agent:
|
||||
agent.append(self._determined_user_agent)
|
||||
|
||||
agent.append(DEFAULT_USER_AGENT)
|
||||
user_agent = headers.setdefault('User-Agent', ' '.join(agent))
|
||||
|
||||
if self.original_ip:
|
||||
headers.setdefault('Forwarded',
|
||||
|
@ -1005,6 +1005,113 @@ class AdapterTest(utils.TestCase):
|
||||
self.assertEqual(request_val,
|
||||
self.requests_mock.last_request.headers[header])
|
||||
|
||||
def test_adapter_user_agent_session_adapter(self):
|
||||
sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
|
||||
adap = adapter.Adapter(client_name='testclient',
|
||||
client_version='4.5.6',
|
||||
session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
adap.get(url)
|
||||
|
||||
agent = 'ksatest/1.2.3 testclient/4.5.6'
|
||||
self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
def test_adapter_user_agent_session_adapter_no_app_version(self):
|
||||
sess = client_session.Session(app_name='ksatest')
|
||||
adap = adapter.Adapter(client_name='testclient',
|
||||
client_version='4.5.6',
|
||||
session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
adap.get(url)
|
||||
|
||||
agent = 'ksatest testclient/4.5.6'
|
||||
self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
def test_adapter_user_agent_session_adapter_no_client_version(self):
|
||||
sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
|
||||
adap = adapter.Adapter(client_name='testclient', session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
adap.get(url)
|
||||
|
||||
agent = 'ksatest/1.2.3 testclient'
|
||||
self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
def test_adapter_user_agent_session_adapter_additional(self):
|
||||
sess = client_session.Session(app_name='ksatest',
|
||||
app_version='1.2.3',
|
||||
additional_user_agent=[('one', '1.1.1'),
|
||||
('two', '2.2.2')])
|
||||
adap = adapter.Adapter(client_name='testclient',
|
||||
client_version='4.5.6',
|
||||
session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
adap.get(url)
|
||||
|
||||
agent = 'ksatest/1.2.3 one/1.1.1 two/2.2.2 testclient/4.5.6'
|
||||
self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
def test_adapter_user_agent_session(self):
|
||||
sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
|
||||
adap = adapter.Adapter(session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
adap.get(url)
|
||||
|
||||
agent = 'ksatest/1.2.3'
|
||||
self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
def test_adapter_user_agent_adapter(self):
|
||||
sess = client_session.Session()
|
||||
adap = adapter.Adapter(client_name='testclient',
|
||||
client_version='4.5.6',
|
||||
session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
adap.get(url)
|
||||
|
||||
agent = 'testclient/4.5.6'
|
||||
self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
def test_adapter_user_agent_session_override(self):
|
||||
sess = client_session.Session(app_name='ksatest',
|
||||
app_version='1.2.3',
|
||||
additional_user_agent=[('one', '1.1.1'),
|
||||
('two', '2.2.2')])
|
||||
adap = adapter.Adapter(client_name='testclient',
|
||||
client_version='4.5.6',
|
||||
session=sess)
|
||||
|
||||
url = 'http://keystone.test.com'
|
||||
self.requests_mock.get(url)
|
||||
|
||||
override_user_agent = '%s/%s' % (uuid.uuid4().hex, uuid.uuid4().hex)
|
||||
adap.get(url, user_agent=override_user_agent)
|
||||
|
||||
self.assertEqual(override_user_agent,
|
||||
self.requests_mock.last_request.headers['User-Agent'])
|
||||
|
||||
|
||||
class TCPKeepAliveAdapterTest(utils.TestCase):
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
prelude: >
|
||||
Allow adding client and application name and version to the session and
|
||||
adapter that will generate a userful user agent string.
|
||||
features:
|
||||
- You can specify a ``app_name`` and ``app_version`` when creating a
|
||||
session. This information will be encoded into the user agent.
|
||||
- You can specify a ``client_name`` and ``client_version`` when creating an
|
||||
adapter. This will be handled by client libraries and incluced into the user
|
||||
agent.
|
||||
- Libraries like shade that modify the way requests are made can add
|
||||
themselves to additional_user_agent and have their version reflected in the
|
||||
user agent string.
|
||||
deprecations:
|
||||
- We suggest you fill the name and version for the application and client
|
||||
instead of specifying a custom ``user_agent``. This will then generate a
|
||||
standard user agent string.
|
Loading…
Reference in New Issue
Block a user