diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py index 60f712e6..d5938066 100644 --- a/keystoneauth1/session.py +++ b/keystoneauth1/session.py @@ -52,6 +52,10 @@ _LOG_CONTENT_TYPES = set(['application/json']) _MAX_RETRY_INTERVAL = 60.0 +# NOTE(efried): This is defined in oslo_middleware.request_id.INBOUND_HEADER, +# but it didn't seem worth adding oslo_middleware to requirements just for that +_REQUEST_ID_HEADER = 'X-Openstack-Request-Id' + def _construct_session(session_obj=None): # NOTE(morganfainberg): if the logic in this function changes be sure to @@ -579,7 +583,7 @@ class Session(object): allow=None, client_name=None, client_version=None, microversion=None, microversion_service_type=None, status_code_retries=0, retriable_status_codes=None, - rate_semaphore=None, **kwargs): + rate_semaphore=None, global_request_id=None, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as @@ -668,6 +672,7 @@ class Session(object): :param rate_semaphore: Semaphore to be used to control concurrency and rate limiting of requests. (optional, defaults to no concurrency or rate control) + :param global_request_id: Value for the X-Openstack-Request-Id header. :param kwargs: any other parameter that can be passed to :meth:`requests.Session.request` (such as `headers`). Except: @@ -785,6 +790,12 @@ class Session(object): headers.setdefault('Content-Type', 'application/json') kwargs['data'] = self._json.encode(json) + if global_request_id is not None: + # NOTE(efried): This does *not* setdefault. If a global_request_id + # kwarg was explicitly specified, it should override any value + # previously configured (e.g. in Adapter.global_request_id). + headers[_REQUEST_ID_HEADER] = global_request_id + for k, v in self.additional_headers.items(): headers.setdefault(k, v) diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py index d67f5ab2..59846943 100644 --- a/keystoneauth1/tests/unit/test_session.py +++ b/keystoneauth1/tests/unit/test_session.py @@ -1296,19 +1296,25 @@ class AdapterTest(utils.TestCase): self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT) def test_setting_global_id_on_request(self): - global_id = "req-%s" % uuid.uuid4() + global_id_adpt = "req-%s" % uuid.uuid4() + global_id_req = "req-%s" % uuid.uuid4() response = uuid.uuid4().hex self.stub_url('GET', text=response) - adpt = adapter.Adapter(client_session.Session(), - auth=CalledAuthPlugin(), - service_type=self.SERVICE_TYPE, - service_name=self.SERVICE_NAME, - interface=self.INTERFACE, - region_name=self.REGION_NAME, - user_agent=self.USER_AGENT, - version=self.VERSION, - allow=self.ALLOW, - global_request_id=global_id) + + def mk_adpt(**kwargs): + return adapter.Adapter(client_session.Session(), + auth=CalledAuthPlugin(), + service_type=self.SERVICE_TYPE, + service_name=self.SERVICE_NAME, + interface=self.INTERFACE, + region_name=self.REGION_NAME, + user_agent=self.USER_AGENT, + version=self.VERSION, + allow=self.ALLOW, + **kwargs) + + # No global_request_id + adpt = mk_adpt() resp = adpt.get('/') self.assertEqual(resp.text, response) @@ -1316,7 +1322,21 @@ class AdapterTest(utils.TestCase): self.assertEqual(self.ALLOW, adpt.auth.endpoint_arguments['allow']) self.assertTrue(adpt.auth.get_token_called) - self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id) + self.assertRequestHeaderEqual('X-OpenStack-Request-ID', None) + + # global_request_id only on the request + adpt.get('/', global_request_id=global_id_req) + self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id_req) + + # global_request_id only on the adapter + adpt = mk_adpt(global_request_id=global_id_adpt) + adpt.get('/') + self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id_adpt) + + # global_request_id on the adapter *and* the request (the request takes + # precedence) + adpt.get('/', global_request_id=global_id_req) + self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id_req) def test_setting_variables_on_get_endpoint(self): adpt = self._create_loaded_adapter()