diff --git a/ironicclient/client.py b/ironicclient/client.py index f556847a0..dbab71f1c 100644 --- a/ironicclient/client.py +++ b/ironicclient/client.py @@ -24,7 +24,8 @@ LOG = logging.getLogger(__name__) def get_client(api_version, auth_type=None, os_ironic_api_version=None, max_retries=None, retry_interval=None, session=None, valid_interfaces=None, interface=None, service_type=None, - region_name=None, **kwargs): + region_name=None, additional_headers=None, + global_request_id=None, **kwargs): """Get an authenticated client, based on the credentials. :param api_version: the API version to use. Valid value: '1'. @@ -41,7 +42,13 @@ def get_client(api_version, auth_type=None, os_ironic_api_version=None, :param service_type: Bare metal endpoint service type. :param region_name: Name of the region to use when searching the bare metal endpoint. - :param kwargs: all the other params that are passed to keystoneauth. + :param additional_headers: Additional headers that should be attached + to every request passing through the client. Headers of the same name + specified per request will take priority. + :param global_request_id: A header (in the form of ``req-$uuid``) that will + be passed on all requests. Enables cross project request id tracking. + :param kwargs: all the other params that are passed to keystoneauth for + session construction. """ # TODO(TheJulia): At some point, we should consider possibly noting # the "latest" flag for os_ironic_api_version to cause the client to @@ -95,6 +102,8 @@ def get_client(api_version, auth_type=None, os_ironic_api_version=None, ironicclient_kwargs = { 'os_ironic_api_version': os_ironic_api_version, + 'additional_headers': additional_headers, + 'global_request_id': global_request_id, 'max_retries': max_retries, 'retry_interval': retry_interval, 'session': session, diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index 136ac1fd7..16d439bad 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -362,6 +362,13 @@ class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter): kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version', self.os_ironic_api_version) + for k, v in self.additional_headers.items(): + kwargs['headers'].setdefault(k, v) + + if self.global_request_id is not None: + kwargs['headers'].setdefault( + "X-OpenStack-Request-ID", self.global_request_id) + endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', self.interface) endpoint_filter.setdefault('service_type', self.service_type) diff --git a/ironicclient/tests/unit/common/test_http.py b/ironicclient/tests/unit/common/test_http.py index 483f3c191..dd105a0e5 100644 --- a/ironicclient/tests/unit/common/test_http.py +++ b/ironicclient/tests/unit/common/test_http.py @@ -409,6 +409,32 @@ class SessionClientTest(utils.BaseTestCase): self.assertRaises(exc.EndpointNotFound, _session_client, session=utils.mockSession({})) + def test_json_request(self): + session = utils.mockSession({}, status_code=200) + req_id = "req-7b081d28-8272-45f4-9cf6-89649c1c7a1a" + client = _session_client( + session=session, additional_headers={"foo": "bar"}, + global_request_id=req_id) + client.json_request('GET', 'url') + + session.request.assert_called_once_with( + 'url', 'GET', raise_exc=False, auth=None, + headers={ + "foo": "bar", + "X-OpenStack-Request-ID": req_id, + "Content-Type": "application/json", + "Accept": "application/json", + "X-OpenStack-Ironic-API-Version": "1.6" + }, + endpoint_filter={ + 'interface': 'publicURL', + 'service_type': 'baremetal', + 'region_name': '' + }, + endpoint_override='http://localhost:1234', + user_agent=http.USER_AGENT + ) + @mock.patch.object(time, 'sleep', lambda *_: None) class RetriesTestCase(utils.BaseTestCase): diff --git a/ironicclient/tests/unit/test_client.py b/ironicclient/tests/unit/test_client.py index 0b59422bf..fb9756fcc 100644 --- a/ironicclient/tests/unit/test_client.py +++ b/ironicclient/tests/unit/test_client.py @@ -28,12 +28,15 @@ class ClientTest(utils.BaseTestCase): @mock.patch.object(config, 'get_cloud_region', autospec=True) def _test_get_client(self, mock_cloud_region, mock_retrieve_data, version=None, auth='password', - expected_interface=None, **kwargs): + expected_interface=None, additional_headers=None, + global_request_id=None, **kwargs): session = mock_cloud_region.return_value.get_session.return_value session.get_endpoint.return_value = 'http://localhost:6385/v1/f14b4123' mock_retrieve_data.return_value = version - client = iroclient.get_client('1', **kwargs) + client = iroclient.get_client( + '1', additional_headers=additional_headers, + global_request_id=global_request_id, **kwargs) expected_version = kwargs.pop('os_ironic_api_version', None) kwargs.pop('interface', None) @@ -80,6 +83,20 @@ class ClientTest(utils.BaseTestCase): self.assertEqual('http://localhost:6385', client.http_client.endpoint_override) + def test_get_client_additional_headers_and_global_request(self): + req_id = 'req-7b081d28-8272-45f4-9cf6-89649c1c7a1a' + kwargs = { + 'endpoint': 'http://localhost:6385/v1', + 'additional_headers': {'foo': 'bar'}, + 'global_request_id': req_id + } + client = self._test_get_client(auth='none', **kwargs) + self.assertIsInstance(client.http_client, http.SessionClient) + self.assertEqual('http://localhost:6385', + client.http_client.endpoint_override) + self.assertEqual(req_id, client.http_client.global_request_id) + self.assertEqual({'foo': 'bar'}, client.http_client.additional_headers) + def test_get_client_with_auth_token_endpoint(self): kwargs = { 'endpoint': 'http://localhost:6385/v1',