diff --git a/keystoneauth1/adapter.py b/keystoneauth1/adapter.py index a82b9bf9..4ae29448 100644 --- a/keystoneauth1/adapter.py +++ b/keystoneauth1/adapter.py @@ -45,13 +45,18 @@ class Adapter(object): :type logger: logging.Logger :param dict allow: Extra filters to pass when discovering API versions. (optional) + :param dict additional_headers: Additional headers that should be attached + to every request passing through the + adapter. Headers of the same name specified + per request will take priority. """ @positional() def __init__(self, session, service_type=None, service_name=None, interface=None, region_name=None, endpoint_override=None, version=None, auth=None, user_agent=None, - connect_retries=None, logger=None, allow={}): + connect_retries=None, logger=None, allow={}, + additional_headers=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 @@ -69,6 +74,7 @@ class Adapter(object): self.connect_retries = connect_retries self.logger = logger self.allow = allow + self.additional_headers = additional_headers or {} def _set_endpoint_filter_kwargs(self, kwargs): if self.service_type: @@ -100,6 +106,9 @@ class Adapter(object): if self.allow: kwargs.setdefault('allow', self.allow) + for k, v in self.additional_headers.items(): + kwargs.setdefault('headers', {}).setdefault(k, v) + return self.session.request(url, method, **kwargs) def get_token(self, auth=None): diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py index e9c739b1..ee7596f0 100644 --- a/keystoneauth1/session.py +++ b/keystoneauth1/session.py @@ -200,6 +200,10 @@ class Session(object): can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional, default to 30) + :param dict additional_headers: Additional headers that should be attached + to every request passing through the + session. Headers of the same name specified + per request will take priority. """ user_agent = None @@ -211,7 +215,7 @@ 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): + redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None): self.auth = auth self.session = _construct_session(session) @@ -220,6 +224,7 @@ class Session(object): self.cert = cert self.timeout = None self.redirect = redirect + self.additional_headers = additional_headers or {} if timeout is not None: self.timeout = float(timeout) @@ -501,6 +506,9 @@ class Session(object): headers['Content-Type'] = 'application/json' kwargs['data'] = self._json.encode(json) + for k, v in self.additional_headers.items(): + headers.setdefault(k, v) + kwargs.setdefault('verify', self.verify) if requests_auth: diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py index 1ec7d26c..512abf08 100644 --- a/keystoneauth1/tests/unit/test_session.py +++ b/keystoneauth1/tests/unit/test_session.py @@ -930,6 +930,56 @@ class AdapterTest(utils.TestCase): self.TEST_URL, 'GET') + def test_additional_headers(self): + session_key = uuid.uuid4().hex + session_val = uuid.uuid4().hex + adapter_key = uuid.uuid4().hex + adapter_val = uuid.uuid4().hex + request_key = uuid.uuid4().hex + request_val = uuid.uuid4().hex + text = uuid.uuid4().hex + + url = 'http://keystone.test.com' + self.requests_mock.get(url, text=text) + + sess = client_session.Session( + additional_headers={session_key: session_val}) + adap = adapter.Adapter(session=sess, + additional_headers={adapter_key: adapter_val}) + resp = adap.get(url, headers={request_key: request_val}) + + request = self.requests_mock.last_request + + self.assertEqual(resp.text, text) + self.assertEqual(session_val, request.headers[session_key]) + self.assertEqual(adapter_val, request.headers[adapter_key]) + self.assertEqual(request_val, request.headers[request_key]) + + def test_additional_headers_overrides(self): + header = uuid.uuid4().hex + session_val = uuid.uuid4().hex + adapter_val = uuid.uuid4().hex + request_val = uuid.uuid4().hex + + url = 'http://keystone.test.com' + self.requests_mock.get(url) + + sess = client_session.Session(additional_headers={header: session_val}) + adap = adapter.Adapter(session=sess) + + adap.get(url) + self.assertEqual(session_val, + self.requests_mock.last_request.headers[header]) + + adap.additional_headers[header] = adapter_val + adap.get(url) + self.assertEqual(adapter_val, + self.requests_mock.last_request.headers[header]) + + adap.get(url, headers={header: request_val}) + self.assertEqual(request_val, + self.requests_mock.last_request.headers[header]) + class TCPKeepAliveAdapterTest(utils.TestCase): diff --git a/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml b/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml new file mode 100644 index 00000000..f18fa31e --- /dev/null +++ b/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml @@ -0,0 +1,10 @@ +--- +prelude: > + Allow specifying additional_headers to the session and the adapter to add + headers to all requests that pass through these objects. +features: + - Add the ability to provide additional_headers to the session and adapter + object. This will allow clients particularly to provide additional ways to + identify their requests. It will also hopefully provide an intermediate way + to handle setting microversions until we support them directly with + keystoneauth.