Merge "Add Response class to return request-id to caller"

This commit is contained in:
Zuul 2018-10-19 20:09:20 +00:00 committed by Gerrit Code Review
commit 06e36dde26
4 changed files with 343 additions and 30 deletions

View File

@ -102,6 +102,31 @@ For more information on Sessions refer to: `Using Sessions`_.
.. _`Using Sessions`: using-sessions.html
Getting Metadata Responses
==========================
Instantiating :py:class:`keystoneclient.v3.client.Client` using
`include_metadata=True` will cause manager response to return
:py:class:`keystoneclient.base.Response` instead of just the data.
The metadata property will be available directly to the
:py:class:`keystoneclient.base.Response` and the response data will
be available as property `data` to it.
>>> from keystoneauth1.identity import v3
>>> from keystoneauth1 import session
>>> from keystoneclient.v3 import client
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
... user_id='myuserid',
... password='mypassword',
... project_id='myprojectid')
>>> sess = session.Session(auth=auth)
>>> keystone = client.Client(session=sess, include_metadata=True)
>>> resp = keystone.projects.list()
>>> resp.request_ids[0]
req-1234-5678-...
>>> resp.data
[<Project ...>, <Project ...>, ...]
Non-Session Authentication (deprecated)
=======================================

View File

@ -32,6 +32,23 @@ from keystoneclient import exceptions as ksc_exceptions
from keystoneclient.i18n import _
class Response(object):
def __init__(self, http_response, data):
self.request_ids = []
if isinstance(http_response, list):
# http_response is a list of <requests.Response> in case
# of pagination
for resp_obj in http_response:
# Extract 'x-openstack-request-id' from headers
self.request_ids.append(resp_obj.headers.get(
'x-openstack-request-id'))
else:
self.request_ids.append(http_response.headers.get(
'x-openstack-request-id'))
self.data = data
def getid(obj):
"""Return id if argument is a Resource.
@ -101,6 +118,11 @@ class Manager(object):
'may be removed in the 2.0.0 release', DeprecationWarning)
return self.client
def _prepare_return_value(self, http_response, data):
if self.client.include_metadata:
return Response(http_response, data)
return data
def _list(self, url, response_key, obj_class=None, body=None, **kwargs):
"""List the collection.
@ -131,7 +153,8 @@ class Manager(object):
# are already returned in a list (so simply utilize that list)
pass
return [obj_class(self, res, loaded=True) for res in data if res]
return self._prepare_return_value(
resp, [obj_class(self, res, loaded=True) for res in data if res])
def _get(self, url, response_key, **kwargs):
"""Get an object from collection.
@ -142,7 +165,8 @@ class Manager(object):
:param kwargs: Additional arguments will be passed to the request.
"""
resp, body = self.client.get(url, **kwargs)
return self.resource_class(self, body[response_key], loaded=True)
return self._prepare_return_value(
resp, self.resource_class(self, body[response_key], loaded=True))
def _head(self, url, **kwargs):
"""Retrieve request headers for an object.
@ -151,7 +175,7 @@ class Manager(object):
:param kwargs: Additional arguments will be passed to the request.
"""
resp, body = self.client.head(url, **kwargs)
return resp.status_code == 204
return self._prepare_return_value(resp, resp.status_code == 204)
def _post(self, url, body, response_key, return_raw=False, **kwargs):
"""Create an object.
@ -168,7 +192,8 @@ class Manager(object):
resp, body = self.client.post(url, body=body, **kwargs)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
return self._prepare_return_value(
resp, self.resource_class(self, body[response_key]))
def _put(self, url, body=None, response_key=None, **kwargs):
"""Update an object with PUT method.
@ -184,9 +209,11 @@ class Manager(object):
# PUT requests may not return a body
if body is not None:
if response_key is not None:
return self.resource_class(self, body[response_key])
return self._prepare_return_value(
resp, self.resource_class(self, body[response_key]))
else:
return self.resource_class(self, body)
return self._prepare_return_value(
resp, self.resource_class(self, body))
def _patch(self, url, body=None, response_key=None, **kwargs):
"""Update an object with PATCH method.
@ -200,9 +227,11 @@ class Manager(object):
"""
resp, body = self.client.patch(url, body=body, **kwargs)
if response_key is not None:
return self.resource_class(self, body[response_key])
return self._prepare_return_value(
resp, self.resource_class(self, body[response_key]))
else:
return self.resource_class(self, body)
return self._prepare_return_value(
resp, self.resource_class(self, body))
def _delete(self, url, **kwargs):
"""Delete an object.
@ -210,7 +239,8 @@ class Manager(object):
:param url: a partial URL, e.g., '/servers/my-server'
:param kwargs: Additional arguments will be passed to the request.
"""
return self.client.delete(url, **kwargs)
resp, body = self.client.delete(url, **kwargs)
return resp, self._prepare_return_value(resp, body)
def _update(self, url, body=None, response_key=None, method="PUT",
**kwargs):
@ -225,7 +255,10 @@ class Manager(object):
% method)
# PUT requests may not return a body
if body:
return self.resource_class(self, body[response_key])
return self._prepare_return_value(
resp, self.resource_class(self, body[response_key]))
else:
return self._prepare_return_value(resp, body)
@six.add_metaclass(abc.ABCMeta)
@ -243,16 +276,20 @@ class ManagerWithFind(Manager):
the Python side.
"""
rl = self.findall(**kwargs)
num = len(rl)
if num == 0:
if self.client.include_metadata:
base_response = rl
rl = rl.data
base_response.data = rl[0]
if len(rl) == 0:
msg = _("No %(name)s matching %(kwargs)s.") % {
'name': self.resource_class.__name__, 'kwargs': kwargs}
raise ksa_exceptions.NotFound(404, msg)
elif num > 1:
elif len(rl) > 1:
raise ksc_exceptions.NoUniqueMatch
else:
return rl[0]
return base_response if self.client.include_metadata else rl[0]
def findall(self, **kwargs):
"""Find all items with attributes matching ``**kwargs``.
@ -263,15 +300,23 @@ class ManagerWithFind(Manager):
found = []
searches = kwargs.items()
for obj in self.list():
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
def _extract_data(objs, response_data):
for obj in objs:
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
response_data.append(obj)
except AttributeError:
continue
return response_data
return found
objs = self.list()
if self.client.include_metadata:
# 'objs' is the object of 'Response' class.
objs.data = _extract_data(objs.data, found)
return objs
return _extract_data(objs, found)
class CrudManager(Manager):
@ -370,6 +415,16 @@ class CrudManager(Manager):
@filter_kwargs
def list(self, fallback_to_auth=False, **kwargs):
def return_resp(resp, include_metadata=False):
base_response = None
list_data = resp
if include_metadata:
base_response = resp
list_data = resp.data
base_response.data = list_data
return base_response if include_metadata else list_data
if 'id' in kwargs.keys():
# Ensure that users are not trying to call things like
# ``domains.list(id='default')`` when they should have used
@ -386,15 +441,16 @@ class CrudManager(Manager):
try:
query = self._build_query(kwargs)
url_query = '%(url)s%(query)s' % {'url': url, 'query': query}
return self._list(
url_query,
self.collection_key)
list_resp = self._list(url_query, self.collection_key)
return return_resp(list_resp,
include_metadata=self.client.include_metadata)
except ksa_exceptions.EmptyCatalog:
if fallback_to_auth:
return self._list(
url_query,
self.collection_key,
endpoint_filter={'interface': plugin.AUTH_INTERFACE})
list_resp = self._list(url_query, self.collection_key,
endpoint_filter={
'interface': plugin.AUTH_INTERFACE})
return return_resp(
list_resp, include_metadata=self.client.include_metadata)
else:
raise
@ -433,6 +489,11 @@ class CrudManager(Manager):
url_query,
self.collection_key)
if self.client.include_metadata:
base_response = elements
elements = elements.data
base_response.data = elements[0]
if not elements:
msg = _("No %(name)s matching %(kwargs)s.") % {
'name': self.resource_class.__name__, 'kwargs': kwargs}
@ -440,7 +501,8 @@ class CrudManager(Manager):
elif len(elements) > 1:
raise ksc_exceptions.NoUniqueMatch
else:
return elements[0]
return (base_response if self.client.include_metadata
else elements[0])
class Resource(object):

View File

@ -389,6 +389,11 @@ class HTTPClient(baseclient.Client, base.BaseAuthPlugin):
user_agent=user_agent,
connect_retries=connect_retries)
# NOTE(dstanek): This allows me to not have to change keystoneauth or
# to write an adapter to the adapter here. Splitting thing into
# multiple project isn't always all sunshine and roses.
self._adapter.include_metadata = kwargs.pop('include_metadata', False)
# keyring setup
if use_keyring and keyring is None:
_logger.warning('Failed to load keyring modules.')

View File

@ -11,14 +11,29 @@
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import fixtures
from keystoneauth1.identity import v2
from keystoneauth1 import session
import requests
from keystoneclient import base
from keystoneclient import exceptions
from keystoneclient.tests.unit import utils
from keystoneclient import utils as base_utils
from keystoneclient.v2_0 import client
from keystoneclient.v2_0 import roles
from keystoneclient.v3 import users
TEST_REQUEST_ID = uuid.uuid4().hex
TEST_REQUEST_ID_1 = uuid.uuid4().hex
def create_response_with_request_id_header():
resp = requests.Response()
resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
return resp
class HumanReadable(base.Resource):
@ -202,3 +217,209 @@ class ManagerTest(utils.TestCase):
management=True)
put_mock.assert_called_once_with(self.url, management=True, body=None)
self.assertEqual(rsrc.hi, 1)
class ManagerRequestIdTest(utils.TestCase):
url = "/test-url"
resp = create_response_with_request_id_header()
def setUp(self):
super(ManagerRequestIdTest, self).setUp()
auth = v2.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
self.mgr = base.Manager(self.client)
self.mgr.resource_class = base.Resource
def mock_request_method(self, request_method, body):
return self.useFixture(fixtures.MockPatchObject(
self.client, request_method, autospec=True,
return_value=(self.resp, body))
).mock
def test_get(self):
body = {"hello": {"hi": 1}}
get_mock = self.mock_request_method('get', body)
rsrc = self.mgr._get(self.url, "hello")
get_mock.assert_called_once_with(self.url)
self.assertEqual(rsrc.data.hi, 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_list(self):
body = {"hello": [{"name": "admin"}, {"name": "admin"}]}
get_mock = self.mock_request_method('get', body)
returned_list = self.mgr._list(self.url, "hello")
self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID)
get_mock.assert_called_once_with(self.url)
def test_list_with_multiple_response_objects(self):
body = {"hello": [{"name": "admin"}, {"name": "admin"}]}
resp_1 = requests.Response()
resp_1.headers['x-openstack-request-id'] = TEST_REQUEST_ID
resp_2 = requests.Response()
resp_2.headers['x-openstack-request-id'] = TEST_REQUEST_ID_1
resp_result = [resp_1, resp_2]
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
return_value=(resp_result, body))
).mock
returned_list = self.mgr._list(self.url, "hello")
self.assertIn(returned_list.request_ids[0], [
TEST_REQUEST_ID, TEST_REQUEST_ID_1])
self.assertIn(returned_list.request_ids[1], [
TEST_REQUEST_ID, TEST_REQUEST_ID_1])
get_mock.assert_called_once_with(self.url)
def test_post(self):
body = {"hello": {"hi": 1}}
post_mock = self.mock_request_method('post', body)
rsrc = self.mgr._post(self.url, body, "hello")
post_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hi, 1)
post_mock.reset_mock()
rsrc = self.mgr._post(self.url, body, "hello", return_raw=True)
post_mock.assert_called_once_with(self.url, body=body)
self.assertNotIsInstance(rsrc, base.Response)
self.assertEqual(rsrc["hi"], 1)
def test_put(self):
body = {"hello": {"hi": 1}}
put_mock = self.mock_request_method('put', body)
rsrc = self.mgr._put(self.url, body, "hello")
put_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hi, 1)
put_mock.reset_mock()
rsrc = self.mgr._put(self.url, body)
put_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hello["hi"], 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_head(self):
get_mock = self.mock_request_method('head', None)
rsrc = self.mgr._head(self.url)
get_mock.assert_called_once_with(self.url)
self.assertFalse(rsrc.data)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_delete(self):
delete_mock = self.mock_request_method('delete', None)
resp, base_resp = self.mgr._delete(self.url, name="hello")
delete_mock.assert_called_once_with('/test-url', name='hello')
self.assertEqual(base_resp.request_ids[0], TEST_REQUEST_ID)
self.assertEqual(base_resp.data, None)
self.assertTrue(isinstance(resp, requests.Response))
def test_patch(self):
body = {"hello": {"hi": 1}}
patch_mock = self.mock_request_method('patch', body)
rsrc = self.mgr._patch(self.url, body, "hello")
patch_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hi, 1)
patch_mock.reset_mock()
rsrc = self.mgr._patch(self.url, body)
patch_mock.assert_called_once_with(self.url, body=body)
self.assertEqual(rsrc.data.hello["hi"], 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
def test_update(self):
body = {"hello": {"hi": 1}}
patch_mock = self.mock_request_method('patch', body)
put_mock = self.mock_request_method('put', body)
rsrc = self.mgr._update(
self.url, body=body, response_key="hello", method="PATCH",
management=False)
patch_mock.assert_called_once_with(
self.url, management=False, body=body)
self.assertEqual(rsrc.data.hi, 1)
rsrc = self.mgr._update(
self.url, body=None, response_key="hello", method="PUT",
management=True)
put_mock.assert_called_once_with(self.url, management=True, body=None)
self.assertEqual(rsrc.data.hi, 1)
self.assertEqual(rsrc.request_ids[0], TEST_REQUEST_ID)
class ManagerWithFindRequestIdTest(utils.TestCase):
url = "/fakes"
resp = create_response_with_request_id_header()
def setUp(self):
super(ManagerWithFindRequestIdTest, self).setUp()
auth = v2.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
def test_find_resource(self):
body = {"roles": [{"name": 'entity_one'}, {"name": 'entity_one_1'}]}
request_resp = requests.Response()
request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
side_effect=[exceptions.NotFound, (request_resp, body)])
).mock
mgr = roles.RoleManager(self.client)
mgr.resource_class = roles.Role
response = base_utils.find_resource(mgr, 'entity_one')
get_mock.assert_called_with('/OS-KSADM/roles')
self.assertEqual(response.request_ids[0], TEST_REQUEST_ID)
class CrudManagerRequestIdTest(utils.TestCase):
resp = create_response_with_request_id_header()
request_resp = requests.Response()
request_resp.headers['x-openstack-request-id'] = TEST_REQUEST_ID
def setUp(self):
super(CrudManagerRequestIdTest, self).setUp()
auth = v2.Token(auth_url='http://127.0.0.1:5000',
token=self.TEST_TOKEN)
session_ = session.Session(auth=auth)
self.client = client.Client(session=session_,
include_metadata='True')._adapter
def test_find_resource(self):
body = {"users": [{"name": 'entity_one'}]}
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
side_effect=[exceptions.NotFound, (self.request_resp, body)])
).mock
mgr = users.UserManager(self.client)
mgr.resource_class = users.User
response = base_utils.find_resource(mgr, 'entity_one')
get_mock.assert_called_with('/users?name=entity_one')
self.assertEqual(response.request_ids[0], TEST_REQUEST_ID)
def test_list(self):
body = {"users": [{"name": "admin"}, {"name": "admin"}]}
get_mock = self.useFixture(fixtures.MockPatchObject(
self.client, 'get', autospec=True,
return_value=(self.request_resp, body))
).mock
mgr = users.UserManager(self.client)
mgr.resource_class = users.User
returned_list = mgr.list()
self.assertEqual(returned_list.request_ids[0], TEST_REQUEST_ID)
get_mock.assert_called_once_with('/users?')