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
|
to every request passing through the
|
||||||
adapter. Headers of the same name specified
|
adapter. Headers of the same name specified
|
||||||
per request will take priority.
|
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()
|
@positional()
|
||||||
@ -56,7 +61,8 @@ class Adapter(object):
|
|||||||
interface=None, region_name=None, endpoint_override=None,
|
interface=None, region_name=None, endpoint_override=None,
|
||||||
version=None, auth=None, user_agent=None,
|
version=None, auth=None, user_agent=None,
|
||||||
connect_retries=None, logger=None, allow={},
|
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
|
# NOTE(jamielennox): when adding new parameters to adapter please also
|
||||||
# add them to the adapter call in httpclient.HTTPClient.__init__ as
|
# add them to the adapter call in httpclient.HTTPClient.__init__ as
|
||||||
# well as to load_adapter_from_argparse below if the argument is
|
# 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.connect_retries = connect_retries
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.allow = allow
|
self.allow = allow
|
||||||
|
self.client_name = client_name
|
||||||
|
self.client_version = client_version
|
||||||
self.additional_headers = additional_headers or {}
|
self.additional_headers = additional_headers or {}
|
||||||
|
|
||||||
def _set_endpoint_filter_kwargs(self, kwargs):
|
def _set_endpoint_filter_kwargs(self, kwargs):
|
||||||
@ -105,6 +113,10 @@ class Adapter(object):
|
|||||||
kwargs.setdefault('logger', self.logger)
|
kwargs.setdefault('logger', self.logger)
|
||||||
if self.allow:
|
if self.allow:
|
||||||
kwargs.setdefault('allow', 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():
|
for k, v in self.additional_headers.items():
|
||||||
kwargs.setdefault('headers', {}).setdefault(k, v)
|
kwargs.setdefault('headers', {}).setdefault(k, v)
|
||||||
|
@ -134,7 +134,7 @@ def _determine_calling_package():
|
|||||||
# hit the bottom of the frame stack
|
# hit the bottom of the frame stack
|
||||||
break
|
break
|
||||||
|
|
||||||
return None
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def _determine_user_agent():
|
def _determine_user_agent():
|
||||||
@ -153,10 +153,10 @@ def _determine_user_agent():
|
|||||||
name = sys.argv[0]
|
name = sys.argv[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# sys.argv is empty, usually the Python interpreter prevents this.
|
# sys.argv is empty, usually the Python interpreter prevents this.
|
||||||
return None
|
return ''
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
return None
|
return ''
|
||||||
|
|
||||||
name = os.path.basename(name)
|
name = os.path.basename(name)
|
||||||
if name in ignored:
|
if name in ignored:
|
||||||
@ -207,6 +207,15 @@ class Session(object):
|
|||||||
to every request passing through the
|
to every request passing through the
|
||||||
session. Headers of the same name specified
|
session. Headers of the same name specified
|
||||||
per request will take priority.
|
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
|
user_agent = None
|
||||||
@ -218,7 +227,8 @@ class Session(object):
|
|||||||
@positional(2)
|
@positional(2)
|
||||||
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
|
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
|
||||||
cert=None, timeout=None, user_agent=None,
|
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.auth = auth
|
||||||
self.session = _construct_session(session)
|
self.session = _construct_session(session)
|
||||||
@ -228,19 +238,14 @@ class Session(object):
|
|||||||
self.timeout = None
|
self.timeout = None
|
||||||
self.redirect = redirect
|
self.redirect = redirect
|
||||||
self.additional_headers = additional_headers or {}
|
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:
|
if timeout is not None:
|
||||||
self.timeout = float(timeout)
|
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:
|
if user_agent is not None:
|
||||||
self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT)
|
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,
|
endpoint_filter=None, auth=None, requests_auth=None,
|
||||||
raise_exc=True, allow_reauth=True, log=True,
|
raise_exc=True, allow_reauth=True, log=True,
|
||||||
endpoint_override=None, connect_retries=0, logger=_logger,
|
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.
|
"""Send an HTTP request with the specified characteristics.
|
||||||
|
|
||||||
Wrapper around `requests.Session.request` to handle tasks such as
|
Wrapper around `requests.Session.request` to handle tasks such as
|
||||||
@ -499,7 +504,39 @@ class Session(object):
|
|||||||
elif self.user_agent:
|
elif self.user_agent:
|
||||||
user_agent = headers.setdefault('User-Agent', self.user_agent)
|
user_agent = headers.setdefault('User-Agent', self.user_agent)
|
||||||
else:
|
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:
|
if self.original_ip:
|
||||||
headers.setdefault('Forwarded',
|
headers.setdefault('Forwarded',
|
||||||
|
@ -1005,6 +1005,113 @@ class AdapterTest(utils.TestCase):
|
|||||||
self.assertEqual(request_val,
|
self.assertEqual(request_val,
|
||||||
self.requests_mock.last_request.headers[header])
|
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):
|
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