Add a per-request global_request_id

Adapter.__init__ takes a global_request_id which causes the
X-Openstack-Request-Id header to be set on each request. This is fine if
the Adapter is used for only one "request" (in the sense of e.g. "a
server create" -- see [1]), but is too broad if the Adapter is reused
for multiple requests. For example, Nova's SchedulerReportClient (used
to communicate with Placement) creates a single instance of Adapter for
the life of the process [2][3][4]. Openstack SDK's Proxy objects [5]
endure for the life of a Connection.

So what is needed is a way to manage the X-Openstack-Request-Id header
on a per-request basis.

This commit adds a global_request_id kwarg to
keystoneauth1.session.Session.request, which is the funnel point for all
requests coming through Adapter as well as Session itself. (All the
methods feeding into that one already accept and pass through arbitrary
**kwargs.) If present, the value in the X-Openstack-Request-Id header is
set accordingly. Note that this will *override*
Adapter.global_request_id, which is exactly what we want, as described
above.

[1] http://specs.openstack.org/openstack/oslo-specs/specs/pike/global-req-id.html
[2] bea9058f02/nova/scheduler/client/report.py (L200)
[3] bea9058f02/nova/scheduler/client/report.py (L243)
[4] bea9058f02/nova/utils.py (L1219-L1221)
[5] bf6651f149/openstack/proxy.py (L114)

Change-Id: Ied73320fcd813ae796e40cbdb30717900486b92c
This commit is contained in:
Eric Fried 2019-07-11 10:35:44 -05:00
parent 92ec14c66b
commit df57e0ec3b
2 changed files with 44 additions and 13 deletions

View File

@ -52,6 +52,10 @@ _LOG_CONTENT_TYPES = set(['application/json'])
_MAX_RETRY_INTERVAL = 60.0 _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): def _construct_session(session_obj=None):
# NOTE(morganfainberg): if the logic in this function changes be sure to # 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, allow=None, client_name=None, client_version=None,
microversion=None, microversion_service_type=None, microversion=None, microversion_service_type=None,
status_code_retries=0, retriable_status_codes=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. """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
@ -668,6 +672,7 @@ class Session(object):
:param rate_semaphore: Semaphore to be used to control concurrency :param rate_semaphore: Semaphore to be used to control concurrency
and rate limiting of requests. (optional, and rate limiting of requests. (optional,
defaults to no concurrency or rate control) 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 :param kwargs: any other parameter that can be passed to
:meth:`requests.Session.request` (such as `headers`). :meth:`requests.Session.request` (such as `headers`).
Except: Except:
@ -785,6 +790,12 @@ class Session(object):
headers.setdefault('Content-Type', 'application/json') headers.setdefault('Content-Type', 'application/json')
kwargs['data'] = self._json.encode(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(): for k, v in self.additional_headers.items():
headers.setdefault(k, v) headers.setdefault(k, v)

View File

@ -1296,10 +1296,13 @@ class AdapterTest(utils.TestCase):
self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT) self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT)
def test_setting_global_id_on_request(self): 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 response = uuid.uuid4().hex
self.stub_url('GET', text=response) self.stub_url('GET', text=response)
adpt = adapter.Adapter(client_session.Session(),
def mk_adpt(**kwargs):
return adapter.Adapter(client_session.Session(),
auth=CalledAuthPlugin(), auth=CalledAuthPlugin(),
service_type=self.SERVICE_TYPE, service_type=self.SERVICE_TYPE,
service_name=self.SERVICE_NAME, service_name=self.SERVICE_NAME,
@ -1308,7 +1311,10 @@ class AdapterTest(utils.TestCase):
user_agent=self.USER_AGENT, user_agent=self.USER_AGENT,
version=self.VERSION, version=self.VERSION,
allow=self.ALLOW, allow=self.ALLOW,
global_request_id=global_id) **kwargs)
# No global_request_id
adpt = mk_adpt()
resp = adpt.get('/') resp = adpt.get('/')
self.assertEqual(resp.text, response) self.assertEqual(resp.text, response)
@ -1316,7 +1322,21 @@ class AdapterTest(utils.TestCase):
self.assertEqual(self.ALLOW, self.assertEqual(self.ALLOW,
adpt.auth.endpoint_arguments['allow']) adpt.auth.endpoint_arguments['allow'])
self.assertTrue(adpt.auth.get_token_called) 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): def test_setting_variables_on_get_endpoint(self):
adpt = self._create_loaded_adapter() adpt = self._create_loaded_adapter()