Add wrapper classes for return-request-id-to-caller
Added wrapper classes which are inherited from base data types tuple, dict and str. Each of these wrapper classes contain a 'request_ids' attribute which is populated with a 'x-openstack-request-id' received in a header from a response body. This change is required to return 'request_id' from client to log request_id mappings of cross projects[1]. [1]: http://specs.openstack.org/openstack/openstack-specs/specs/return-request-id.html Change-Id: I55fcba61c4efb308f575e95e154aba23e5dd5245 Implements: blueprint return-request-id-to-caller
This commit is contained in:
		@@ -59,3 +59,13 @@ and a service endpoint URL directly.
 | 
			
		||||
    >>> from neutronclient.v2_0 import client
 | 
			
		||||
    >>> neutron = client.Client(endpoint_url='http://192.168.206.130:9696/',
 | 
			
		||||
    ...                         token='d3f9226f27774f338019aa2611112ef6')
 | 
			
		||||
 | 
			
		||||
You can get ``X-Openstack-Request-Id`` as ``request_ids`` from the result.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    >>> network = {'name': 'mynetwork', 'admin_state_up': True}
 | 
			
		||||
    >>> neutron.create_network({'network':network})
 | 
			
		||||
    >>> networks = neutron.list_networks(name='mynetwork')
 | 
			
		||||
    >>> print networks.request_ids
 | 
			
		||||
    ['req-978a0160-7ab0-44f0-8a93-08e9a4e785fa']
 | 
			
		||||
 
 | 
			
		||||
@@ -60,10 +60,19 @@ class NeutronClientException(NeutronException):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    status_code = 0
 | 
			
		||||
    req_ids_msg = _("Neutron server returns request_ids: %s")
 | 
			
		||||
    request_ids = []
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None, **kwargs):
 | 
			
		||||
        self.request_ids = kwargs.get('request_ids')
 | 
			
		||||
        if 'status_code' in kwargs:
 | 
			
		||||
            self.status_code = kwargs['status_code']
 | 
			
		||||
        if self.request_ids:
 | 
			
		||||
            req_ids_msg = self.req_ids_msg % self.request_ids
 | 
			
		||||
            if message:
 | 
			
		||||
                message += '\n' + req_ids_msg
 | 
			
		||||
            else:
 | 
			
		||||
                message = req_ids_msg
 | 
			
		||||
        super(NeutronClientException, self).__init__(message, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ API_VERSION = "2.0"
 | 
			
		||||
FORMAT = 'json'
 | 
			
		||||
TOKEN = 'testtoken'
 | 
			
		||||
ENDURL = 'localurl'
 | 
			
		||||
REQUEST_ID = 'test_request_id'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextlib.contextmanager
 | 
			
		||||
@@ -65,7 +66,7 @@ class FakeStdout(object):
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyResp(object):
 | 
			
		||||
class MyResp(requests.Response):
 | 
			
		||||
    def __init__(self, status_code, headers=None, reason=None):
 | 
			
		||||
        self.status_code = status_code
 | 
			
		||||
        self.headers = headers or {}
 | 
			
		||||
@@ -648,41 +649,46 @@ class ClientV2TestJson(CLITestV20Base):
 | 
			
		||||
        self.client.httpclient.auth_token = encodeutils.safe_encode(
 | 
			
		||||
            unicode_text)
 | 
			
		||||
        expected_auth_token = encodeutils.safe_encode(unicode_text)
 | 
			
		||||
        resp_headers = {'x-openstack-request-id': REQUEST_ID}
 | 
			
		||||
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            end_url(expected_action, query=expect_query, format=self.format),
 | 
			
		||||
            'PUT', body=expect_body,
 | 
			
		||||
            headers=mox.ContainsKeyValue(
 | 
			
		||||
                'X-Auth-Token',
 | 
			
		||||
                expected_auth_token)).AndReturn((MyResp(200), expect_body))
 | 
			
		||||
                expected_auth_token)).AndReturn((MyResp(200, resp_headers),
 | 
			
		||||
                                                 expect_body))
 | 
			
		||||
 | 
			
		||||
        self.mox.ReplayAll()
 | 
			
		||||
        res_body = self.client.do_request('PUT', action, body=body,
 | 
			
		||||
        result = self.client.do_request('PUT', action, body=body,
 | 
			
		||||
                                        params=params)
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
        self.mox.UnsetStubs()
 | 
			
		||||
 | 
			
		||||
        # test response with unicode
 | 
			
		||||
        self.assertEqual(body, res_body)
 | 
			
		||||
        self.assertEqual(body, result)
 | 
			
		||||
 | 
			
		||||
    def test_do_request_error_without_response_body(self):
 | 
			
		||||
        self.mox.StubOutWithMock(self.client.httpclient, "request")
 | 
			
		||||
        params = {'test': 'value'}
 | 
			
		||||
        expect_query = six.moves.urllib.parse.urlencode(params)
 | 
			
		||||
        self.client.httpclient.auth_token = 'token'
 | 
			
		||||
        resp_headers = {'x-openstack-request-id': REQUEST_ID}
 | 
			
		||||
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            MyUrlComparator(end_url(
 | 
			
		||||
                '/test', query=expect_query, format=self.format), self.client),
 | 
			
		||||
            'PUT', body='',
 | 
			
		||||
            headers=mox.ContainsKeyValue('X-Auth-Token', 'token')
 | 
			
		||||
        ).AndReturn((MyResp(400, reason='An error'), ''))
 | 
			
		||||
        ).AndReturn((MyResp(400, headers=resp_headers, reason='An error'), ''))
 | 
			
		||||
 | 
			
		||||
        self.mox.ReplayAll()
 | 
			
		||||
        error = self.assertRaises(exceptions.NeutronClientException,
 | 
			
		||||
                                  self.client.do_request, 'PUT', '/test',
 | 
			
		||||
                                  body='', params=params)
 | 
			
		||||
        self.assertEqual("An error", str(error))
 | 
			
		||||
        expected_error = "An error\nNeutron server returns " \
 | 
			
		||||
                         "request_ids: %s" % [REQUEST_ID]
 | 
			
		||||
        self.assertEqual(expected_error, str(error))
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
        self.mox.UnsetStubs()
 | 
			
		||||
 | 
			
		||||
@@ -697,21 +703,126 @@ class ClientV2TestJson(CLITestV20Base):
 | 
			
		||||
        else:
 | 
			
		||||
            self.fail('Expected exception NOT raised')
 | 
			
		||||
 | 
			
		||||
    def test_do_request_request_ids(self):
 | 
			
		||||
        self.mox.StubOutWithMock(self.client.httpclient, "request")
 | 
			
		||||
        params = {'test': 'value'}
 | 
			
		||||
        expect_query = six.moves.urllib.parse.urlencode(params)
 | 
			
		||||
        self.client.httpclient.auth_token = 'token'
 | 
			
		||||
        body = params
 | 
			
		||||
        expect_body = self.client.serialize(body)
 | 
			
		||||
        resp_headers = {'x-openstack-request-id': REQUEST_ID}
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            MyUrlComparator(end_url(
 | 
			
		||||
                '/test', query=expect_query,
 | 
			
		||||
                format=self.format), self.client),
 | 
			
		||||
            'PUT', body=expect_body,
 | 
			
		||||
            headers=mox.ContainsKeyValue('X-Auth-Token', 'token')
 | 
			
		||||
        ).AndReturn((MyResp(200, resp_headers), expect_body))
 | 
			
		||||
 | 
			
		||||
        self.mox.ReplayAll()
 | 
			
		||||
        result = self.client.do_request('PUT', '/test', body=body,
 | 
			
		||||
                                        params=params)
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
        self.mox.UnsetStubs()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(body, result)
 | 
			
		||||
        self.assertEqual([REQUEST_ID], result.request_ids)
 | 
			
		||||
 | 
			
		||||
    def test_list_request_ids_with_retrieve_all_true(self):
 | 
			
		||||
        self.mox.StubOutWithMock(self.client.httpclient, "request")
 | 
			
		||||
 | 
			
		||||
        path = '/test'
 | 
			
		||||
        resources = 'tests'
 | 
			
		||||
        fake_query = "marker=myid2&limit=2"
 | 
			
		||||
        reses1 = {resources: [{'id': 'myid1', },
 | 
			
		||||
                              {'id': 'myid2', }],
 | 
			
		||||
                  '%s_links' % resources: [{'href': end_url(path, fake_query),
 | 
			
		||||
                                            'rel': 'next'}]}
 | 
			
		||||
        reses2 = {resources: [{'id': 'myid3', },
 | 
			
		||||
                              {'id': 'myid4', }]}
 | 
			
		||||
        resstr1 = self.client.serialize(reses1)
 | 
			
		||||
        resstr2 = self.client.serialize(reses2)
 | 
			
		||||
        resp_headers = {'x-openstack-request-id': REQUEST_ID}
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            end_url(path, "", format=self.format), 'GET',
 | 
			
		||||
            body=None,
 | 
			
		||||
            headers=mox.ContainsKeyValue(
 | 
			
		||||
                'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
 | 
			
		||||
                                                   resstr1))
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            MyUrlComparator(end_url(path, fake_query, format=self.format),
 | 
			
		||||
                            self.client), 'GET',
 | 
			
		||||
            body=None,
 | 
			
		||||
            headers=mox.ContainsKeyValue(
 | 
			
		||||
                'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
 | 
			
		||||
                                                   resstr2))
 | 
			
		||||
        self.mox.ReplayAll()
 | 
			
		||||
        result = self.client.list(resources, path)
 | 
			
		||||
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
        self.mox.UnsetStubs()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual([REQUEST_ID, REQUEST_ID], result.request_ids)
 | 
			
		||||
 | 
			
		||||
    def test_list_request_ids_with_retrieve_all_false(self):
 | 
			
		||||
        self.mox.StubOutWithMock(self.client.httpclient, "request")
 | 
			
		||||
 | 
			
		||||
        path = '/test'
 | 
			
		||||
        resources = 'tests'
 | 
			
		||||
        fake_query = "marker=myid2&limit=2"
 | 
			
		||||
        reses1 = {resources: [{'id': 'myid1', },
 | 
			
		||||
                              {'id': 'myid2', }],
 | 
			
		||||
                  '%s_links' % resources: [{'href': end_url(path, fake_query),
 | 
			
		||||
                                            'rel': 'next'}]}
 | 
			
		||||
        reses2 = {resources: [{'id': 'myid3', },
 | 
			
		||||
                              {'id': 'myid4', }]}
 | 
			
		||||
        resstr1 = self.client.serialize(reses1)
 | 
			
		||||
        resstr2 = self.client.serialize(reses2)
 | 
			
		||||
        resp_headers = {'x-openstack-request-id': REQUEST_ID}
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            end_url(path, "", format=self.format), 'GET',
 | 
			
		||||
            body=None,
 | 
			
		||||
            headers=mox.ContainsKeyValue(
 | 
			
		||||
                'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
 | 
			
		||||
                                                   resstr1))
 | 
			
		||||
        self.client.httpclient.request(
 | 
			
		||||
            MyUrlComparator(end_url(path, fake_query, format=self.format),
 | 
			
		||||
                            self.client), 'GET',
 | 
			
		||||
            body=None,
 | 
			
		||||
            headers=mox.ContainsKeyValue(
 | 
			
		||||
                'X-Auth-Token', TOKEN)).AndReturn((MyResp(200, resp_headers),
 | 
			
		||||
                                                   resstr2))
 | 
			
		||||
        self.mox.ReplayAll()
 | 
			
		||||
        result = self.client.list(resources, path, retrieve_all=False)
 | 
			
		||||
        next(result)
 | 
			
		||||
        self.assertEqual([REQUEST_ID], result.request_ids)
 | 
			
		||||
        next(result)
 | 
			
		||||
        self.assertEqual([REQUEST_ID, REQUEST_ID], result.request_ids)
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
        self.mox.UnsetStubs()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
 | 
			
		||||
    def _test_exception_handler_v20(
 | 
			
		||||
            self, expected_exception, status_code, expected_msg,
 | 
			
		||||
            error_type=None, error_msg=None, error_detail=None,
 | 
			
		||||
            error_content=None):
 | 
			
		||||
            request_id=None, error_content=None):
 | 
			
		||||
 | 
			
		||||
        resp = MyResp(status_code, {'x-openstack-request-id': request_id})
 | 
			
		||||
        if request_id is not None:
 | 
			
		||||
            expected_msg += "\nNeutron server returns " \
 | 
			
		||||
                            "request_ids: %s" % [request_id]
 | 
			
		||||
        if error_content is None:
 | 
			
		||||
            error_content = {'NeutronError': {'type': error_type,
 | 
			
		||||
                                              'message': error_msg,
 | 
			
		||||
                                              'detail': error_detail}}
 | 
			
		||||
        expected_content = self.client._convert_into_with_meta(error_content,
 | 
			
		||||
                                                               resp)
 | 
			
		||||
 | 
			
		||||
        e = self.assertRaises(expected_exception,
 | 
			
		||||
                              client.exception_handler_v20,
 | 
			
		||||
                              status_code, error_content)
 | 
			
		||||
                              status_code, expected_content)
 | 
			
		||||
        self.assertEqual(status_code, e.status_code)
 | 
			
		||||
        self.assertEqual(expected_exception.__name__,
 | 
			
		||||
                         e.__class__.__name__)
 | 
			
		||||
@@ -728,7 +839,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
                   'fake-network-uuid. The IP address fake-ip is in use.')
 | 
			
		||||
        self._test_exception_handler_v20(
 | 
			
		||||
            exceptions.IpAddressInUseClient, 409, err_msg,
 | 
			
		||||
            'IpAddressInUse', err_msg, '')
 | 
			
		||||
            'IpAddressInUse', err_msg, '', REQUEST_ID)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_neutron_known_error(self):
 | 
			
		||||
        known_error_map = [
 | 
			
		||||
@@ -754,7 +865,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
            self._test_exception_handler_v20(
 | 
			
		||||
                client_exc, status_code,
 | 
			
		||||
                error_msg + '\n' + error_detail,
 | 
			
		||||
                server_exc, error_msg, error_detail)
 | 
			
		||||
                server_exc, error_msg, error_detail, REQUEST_ID)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_neutron_known_error_without_detail(self):
 | 
			
		||||
        error_msg = 'Network not found'
 | 
			
		||||
@@ -762,7 +873,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
        self._test_exception_handler_v20(
 | 
			
		||||
            exceptions.NetworkNotFoundClient, 404,
 | 
			
		||||
            error_msg,
 | 
			
		||||
            'NetworkNotFound', error_msg, error_detail)
 | 
			
		||||
            'NetworkNotFound', error_msg, error_detail, REQUEST_ID)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_unknown_error_to_per_code_exception(self):
 | 
			
		||||
        for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items():
 | 
			
		||||
@@ -771,7 +882,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
            self._test_exception_handler_v20(
 | 
			
		||||
                client_exc, status_code,
 | 
			
		||||
                error_msg + '\n' + error_detail,
 | 
			
		||||
                'UnknownError', error_msg, error_detail)
 | 
			
		||||
                'UnknownError', error_msg, error_detail, [REQUEST_ID])
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_neutron_unknown_status_code(self):
 | 
			
		||||
        error_msg = 'Unknown error'
 | 
			
		||||
@@ -779,7 +890,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
        self._test_exception_handler_v20(
 | 
			
		||||
            exceptions.NeutronClientException, 501,
 | 
			
		||||
            error_msg + '\n' + error_detail,
 | 
			
		||||
            'UnknownError', error_msg, error_detail)
 | 
			
		||||
            'UnknownError', error_msg, error_detail, REQUEST_ID)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_bad_neutron_error(self):
 | 
			
		||||
        for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items():
 | 
			
		||||
@@ -787,7 +898,8 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
            self._test_exception_handler_v20(
 | 
			
		||||
                client_exc, status_code,
 | 
			
		||||
                expected_msg="{'unknown_key': 'UNKNOWN'}",
 | 
			
		||||
                error_content=error_content)
 | 
			
		||||
                error_content=error_content,
 | 
			
		||||
                request_id=REQUEST_ID)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_error_dict_contains_message(self):
 | 
			
		||||
        error_content = {'message': 'This is an error message'}
 | 
			
		||||
@@ -795,7 +907,8 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
            self._test_exception_handler_v20(
 | 
			
		||||
                client_exc, status_code,
 | 
			
		||||
                expected_msg='This is an error message',
 | 
			
		||||
                error_content=error_content)
 | 
			
		||||
                error_content=error_content,
 | 
			
		||||
                request_id=REQUEST_ID)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_error_dict_not_contain_message(self):
 | 
			
		||||
        # 599 is not contained in HTTP_EXCEPTION_MAP.
 | 
			
		||||
@@ -804,6 +917,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
        self._test_exception_handler_v20(
 | 
			
		||||
            exceptions.NeutronClientException, 599,
 | 
			
		||||
            expected_msg=expected_msg,
 | 
			
		||||
            request_id=None,
 | 
			
		||||
            error_content=error_content)
 | 
			
		||||
 | 
			
		||||
    def test_exception_handler_v20_default_fallback(self):
 | 
			
		||||
@@ -813,6 +927,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
        self._test_exception_handler_v20(
 | 
			
		||||
            exceptions.NeutronClientException, 599,
 | 
			
		||||
            expected_msg=expected_msg,
 | 
			
		||||
            request_id=None,
 | 
			
		||||
            error_content=error_content)
 | 
			
		||||
 | 
			
		||||
    def test_exception_status(self):
 | 
			
		||||
@@ -848,3 +963,60 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
 | 
			
		||||
        self.assertIsNotNone(error.status_code)
 | 
			
		||||
        self.mox.VerifyAll()
 | 
			
		||||
        self.mox.UnsetStubs()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DictWithMetaTest(base.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_dict_with_meta(self):
 | 
			
		||||
        body = {'test': 'value'}
 | 
			
		||||
        resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
 | 
			
		||||
        obj = client._DictWithMeta(body, resp)
 | 
			
		||||
        self.assertEqual(body, obj)
 | 
			
		||||
 | 
			
		||||
        # Check request_ids attribute is added to obj
 | 
			
		||||
        self.assertTrue(hasattr(obj, 'request_ids'))
 | 
			
		||||
        self.assertEqual([REQUEST_ID], obj.request_ids)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TupleWithMetaTest(base.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_tuple_with_meta(self):
 | 
			
		||||
        body = ('test', 'value')
 | 
			
		||||
        resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
 | 
			
		||||
        obj = client._TupleWithMeta(body, resp)
 | 
			
		||||
        self.assertEqual(body, obj)
 | 
			
		||||
 | 
			
		||||
        # Check request_ids attribute is added to obj
 | 
			
		||||
        self.assertTrue(hasattr(obj, 'request_ids'))
 | 
			
		||||
        self.assertEqual([REQUEST_ID], obj.request_ids)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StrWithMetaTest(base.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_str_with_meta(self):
 | 
			
		||||
        body = "test_string"
 | 
			
		||||
        resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
 | 
			
		||||
        obj = client._StrWithMeta(body, resp)
 | 
			
		||||
        self.assertEqual(body, obj)
 | 
			
		||||
 | 
			
		||||
        # Check request_ids attribute is added to obj
 | 
			
		||||
        self.assertTrue(hasattr(obj, 'request_ids'))
 | 
			
		||||
        self.assertEqual([REQUEST_ID], obj.request_ids)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GeneratorWithMetaTest(base.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    body = {'test': 'value'}
 | 
			
		||||
    resp = MyResp(200, {'x-openstack-request-id': REQUEST_ID})
 | 
			
		||||
 | 
			
		||||
    def _pagination(self, collection, path, **params):
 | 
			
		||||
        obj = client._DictWithMeta(self.body, self.resp)
 | 
			
		||||
        yield obj
 | 
			
		||||
 | 
			
		||||
    def test_generator(self):
 | 
			
		||||
        obj = client._GeneratorWithMeta(self._pagination, 'test_collection',
 | 
			
		||||
                                        'test_path', test_args='test_args')
 | 
			
		||||
        self.assertEqual(self.body, next(obj))
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(hasattr(obj, 'request_ids'))
 | 
			
		||||
        self.assertEqual([REQUEST_ID], obj.request_ids)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ import time
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
from six import string_types
 | 
			
		||||
 | 
			
		||||
from neutronclient._i18n import _
 | 
			
		||||
from neutronclient import client
 | 
			
		||||
@@ -44,6 +45,7 @@ def exception_handler_v20(status_code, error_content):
 | 
			
		||||
    :param error_content: deserialized body of error response
 | 
			
		||||
    """
 | 
			
		||||
    error_dict = None
 | 
			
		||||
    request_ids = error_content.request_ids
 | 
			
		||||
    if isinstance(error_content, dict):
 | 
			
		||||
        error_dict = error_content.get('NeutronError')
 | 
			
		||||
    # Find real error type
 | 
			
		||||
@@ -78,7 +80,8 @@ def exception_handler_v20(status_code, error_content):
 | 
			
		||||
        client_exc = exceptions.NeutronClientException
 | 
			
		||||
 | 
			
		||||
    raise client_exc(message=error_message,
 | 
			
		||||
                     status_code=status_code)
 | 
			
		||||
                     status_code=status_code,
 | 
			
		||||
                     request_ids=request_ids)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIParamsCall(object):
 | 
			
		||||
@@ -97,6 +100,99 @@ class APIParamsCall(object):
 | 
			
		||||
        return with_params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _RequestIdMixin(object):
 | 
			
		||||
    """Wrapper class to expose x-openstack-request-id to the caller."""
 | 
			
		||||
    def _request_ids_setup(self):
 | 
			
		||||
        self._request_ids = []
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def request_ids(self):
 | 
			
		||||
        return self._request_ids
 | 
			
		||||
 | 
			
		||||
    def _append_request_ids(self, resp):
 | 
			
		||||
        """Add request_ids as an attribute to the object
 | 
			
		||||
 | 
			
		||||
        :param resp: Response object or list of Response objects
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(resp, list):
 | 
			
		||||
            # Add list of request_ids if response is of type list.
 | 
			
		||||
            for resp_obj in resp:
 | 
			
		||||
                self._append_request_id(resp_obj)
 | 
			
		||||
        elif resp is not None:
 | 
			
		||||
            # Add request_ids if response contains single object.
 | 
			
		||||
            self._append_request_id(resp)
 | 
			
		||||
 | 
			
		||||
    def _append_request_id(self, resp):
 | 
			
		||||
        if isinstance(resp, requests.Response):
 | 
			
		||||
            # Extract 'x-openstack-request-id' from headers if
 | 
			
		||||
            # response is a Response object.
 | 
			
		||||
            request_id = resp.headers.get('x-openstack-request-id')
 | 
			
		||||
        else:
 | 
			
		||||
            # If resp is of type string.
 | 
			
		||||
            request_id = resp
 | 
			
		||||
        if request_id:
 | 
			
		||||
            self._request_ids.append(request_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _DictWithMeta(dict, _RequestIdMixin):
 | 
			
		||||
    def __init__(self, values, resp):
 | 
			
		||||
        super(_DictWithMeta, self).__init__(values)
 | 
			
		||||
        self._request_ids_setup()
 | 
			
		||||
        self._append_request_ids(resp)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _TupleWithMeta(tuple, _RequestIdMixin):
 | 
			
		||||
    def __new__(cls, values, resp):
 | 
			
		||||
        return super(_TupleWithMeta, cls).__new__(cls, values)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, values, resp):
 | 
			
		||||
        self._request_ids_setup()
 | 
			
		||||
        self._append_request_ids(resp)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _StrWithMeta(str, _RequestIdMixin):
 | 
			
		||||
    def __new__(cls, value, resp):
 | 
			
		||||
        return super(_StrWithMeta, cls).__new__(cls, value)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, values, resp):
 | 
			
		||||
        self._request_ids_setup()
 | 
			
		||||
        self._append_request_ids(resp)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _GeneratorWithMeta(_RequestIdMixin):
 | 
			
		||||
    def __init__(self, paginate_func, collection, path, **params):
 | 
			
		||||
        self.paginate_func = paginate_func
 | 
			
		||||
        self.collection = collection
 | 
			
		||||
        self.path = path
 | 
			
		||||
        self.params = params
 | 
			
		||||
        self.generator = None
 | 
			
		||||
        self._request_ids_setup()
 | 
			
		||||
 | 
			
		||||
    def _paginate(self):
 | 
			
		||||
        for r in self.paginate_func(
 | 
			
		||||
                self.collection, self.path, **self.params):
 | 
			
		||||
            yield r, r.request_ids
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    # Python 3 compatibility
 | 
			
		||||
    def __next__(self):
 | 
			
		||||
        return self.next()
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        if not self.generator:
 | 
			
		||||
            self.generator = self._paginate()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            obj, req_id = next(self.generator)
 | 
			
		||||
            self._append_request_ids(req_id)
 | 
			
		||||
        except StopIteration:
 | 
			
		||||
            raise StopIteration()
 | 
			
		||||
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientBase(object):
 | 
			
		||||
    """Client for the OpenStack Neutron v2.0 API.
 | 
			
		||||
 | 
			
		||||
@@ -162,7 +258,7 @@ class ClientBase(object):
 | 
			
		||||
        self.action_prefix = "/v%s" % (self.version)
 | 
			
		||||
        self.retry_interval = 1
 | 
			
		||||
 | 
			
		||||
    def _handle_fault_response(self, status_code, response_body):
 | 
			
		||||
    def _handle_fault_response(self, status_code, response_body, resp):
 | 
			
		||||
        # Create exception with HTTP status code and message
 | 
			
		||||
        _logger.debug("Error message: %s", response_body)
 | 
			
		||||
        # Add deserialized error message to exception arguments
 | 
			
		||||
@@ -172,8 +268,9 @@ class ClientBase(object):
 | 
			
		||||
            # If unable to deserialized body it is probably not a
 | 
			
		||||
            # Neutron error
 | 
			
		||||
            des_error_body = {'message': response_body}
 | 
			
		||||
        error_body = self._convert_into_with_meta(des_error_body, resp)
 | 
			
		||||
        # Raise the appropriate exception
 | 
			
		||||
        exception_handler_v20(status_code, des_error_body)
 | 
			
		||||
        exception_handler_v20(status_code, error_body)
 | 
			
		||||
 | 
			
		||||
    def do_request(self, method, action, body=None, headers=None, params=None):
 | 
			
		||||
        # Add format and tenant_id
 | 
			
		||||
@@ -193,11 +290,12 @@ class ClientBase(object):
 | 
			
		||||
                           requests.codes.created,
 | 
			
		||||
                           requests.codes.accepted,
 | 
			
		||||
                           requests.codes.no_content):
 | 
			
		||||
            return self.deserialize(replybody, status_code)
 | 
			
		||||
            data = self.deserialize(replybody, status_code)
 | 
			
		||||
            return self._convert_into_with_meta(data, resp)
 | 
			
		||||
        else:
 | 
			
		||||
            if not replybody:
 | 
			
		||||
                replybody = resp.reason
 | 
			
		||||
            self._handle_fault_response(status_code, replybody)
 | 
			
		||||
            self._handle_fault_response(status_code, replybody, resp)
 | 
			
		||||
 | 
			
		||||
    def get_auth_info(self):
 | 
			
		||||
        return self.httpclient.get_auth_info()
 | 
			
		||||
@@ -271,11 +369,14 @@ class ClientBase(object):
 | 
			
		||||
    def list(self, collection, path, retrieve_all=True, **params):
 | 
			
		||||
        if retrieve_all:
 | 
			
		||||
            res = []
 | 
			
		||||
            request_ids = []
 | 
			
		||||
            for r in self._pagination(collection, path, **params):
 | 
			
		||||
                res.extend(r[collection])
 | 
			
		||||
            return {collection: res}
 | 
			
		||||
                request_ids.extend(r.request_ids)
 | 
			
		||||
            return _DictWithMeta({collection: res}, request_ids)
 | 
			
		||||
        else:
 | 
			
		||||
            return self._pagination(collection, path, **params)
 | 
			
		||||
            return _GeneratorWithMeta(self._pagination, collection,
 | 
			
		||||
                                      path, **params)
 | 
			
		||||
 | 
			
		||||
    def _pagination(self, collection, path, **params):
 | 
			
		||||
        if params.get('page_reverse', False):
 | 
			
		||||
@@ -297,6 +398,15 @@ class ClientBase(object):
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    def _convert_into_with_meta(self, item, resp):
 | 
			
		||||
        if item:
 | 
			
		||||
            if isinstance(item, dict):
 | 
			
		||||
                return _DictWithMeta(item, resp)
 | 
			
		||||
            elif isinstance(item, string_types):
 | 
			
		||||
                return _StrWithMeta(item, resp)
 | 
			
		||||
        else:
 | 
			
		||||
            return _TupleWithMeta((), resp)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Client(ClientBase):
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
---
 | 
			
		||||
features:
 | 
			
		||||
  - Neutron client returns 'x-openstack-request-id'.
 | 
			
		||||
		Reference in New Issue
	
	Block a user