Add Wrapper classes for list, dict, tuple

Added wrapper classes which are inherited from base data types list,
tuple and dict. Each of these wrapper classes contains a 'request_ids'
attribute which will be 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.

Partial-Implements: blueprint return-request-id-to-caller
Change-Id: I3aadb4d8bf675e20f2094b66a23ac20f455a99eb
This commit is contained in:
Ankit Agrawal 2015-12-02 00:51:17 -08:00
parent faf8808162
commit 1619f11b9c
2 changed files with 115 additions and 3 deletions
cinderclient
openstack/common/apiclient
tests/unit

@ -26,6 +26,7 @@ Base utilities to build API operation managers and objects on top of.
import abc
import copy
from requests import Response
import six
from six.moves.urllib import parse
@ -409,7 +410,43 @@ class Extension(HookableMixin):
return "<Extension '%s'>" % self.name
class Resource(object):
class RequestIdMixin(object):
"""Wrapper class to expose x-openstack-request-id to the caller."""
def setup(self):
self.x_openstack_request_ids = []
@property
def request_ids(self):
return self.x_openstack_request_ids
def append_request_ids(self, resp):
"""Add request_ids as an attribute to the object
:param resp: list, Response object or string
"""
if resp is None:
return
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)
else:
# Add request_ids if response contains single object.
self._append_request_id(resp)
def _append_request_id(self, resp):
if isinstance(resp, Response):
# Extract 'x-openstack-request-id' from headers if
# response is a Response object.
request_id = resp.headers.get('x-openstack-request-id')
self.x_openstack_request_ids.append(request_id)
else:
# If resp is of type string (in case of encryption type list)
self.x_openstack_request_ids.append(resp)
class Resource(RequestIdMixin):
"""Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes.
@ -418,22 +455,26 @@ class Resource(object):
HUMAN_ID = False
NAME_ATTR = 'name'
def __init__(self, manager, info, loaded=False):
def __init__(self, manager, info, loaded=False, resp=None):
"""Populate and bind to a manager.
:param manager: BaseManager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
:param resp: Response or list of Response objects
"""
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
self.setup()
self.append_request_ids(resp)
def __repr__(self):
reprkeys = sorted(k
for k in self.__dict__.keys()
if k[0] != '_' and k != 'manager')
if k[0] != '_' and
k not in ['manager', 'x_openstack_request_ids'])
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
@ -493,3 +534,26 @@ class Resource(object):
def to_dict(self):
return copy.deepcopy(self._info)
class ListWithMeta(list, RequestIdMixin):
def __init__(self, values, resp):
super(ListWithMeta, self).__init__(values)
self.setup()
self.append_request_ids(resp)
class DictWithMeta(dict, RequestIdMixin):
def __init__(self, values, resp):
super(DictWithMeta, self).__init__(values)
self.setup()
self.append_request_ids(resp)
class TupleWithMeta(tuple, RequestIdMixin):
def __new__(cls, resp, values):
return super(TupleWithMeta, cls).__new__(cls, (resp, values))
def __init__(self, resp, values):
self.setup()
self.append_request_ids(resp)

@ -11,8 +11,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from requests import Response
from cinderclient import base
from cinderclient import exceptions
from cinderclient.openstack.common.apiclient import base as common_base
from cinderclient.v1 import volumes
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
@ -21,11 +24,21 @@ from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
REQUEST_ID = 'req-test-request-id'
def create_response_obj_with_header():
resp = Response()
resp.headers['x-openstack-request-id'] = REQUEST_ID
return resp
class BaseTest(utils.TestCase):
def test_resource_repr(self):
r = base.Resource(None, dict(foo="bar", baz="spam"))
self.assertEqual("<Resource baz=spam, foo=bar>", repr(r))
self.assertNotIn("x_openstack_request_ids", repr(r))
def test_getid(self):
self.assertEqual(4, base.getid(4))
@ -63,3 +76,38 @@ class BaseTest(utils.TestCase):
def test_to_dict(self):
r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
self.assertEqual({'id': 1, 'name': 'hi'}, r1.to_dict())
def test_resource_object_with_request_ids(self):
resp_obj = create_response_obj_with_header()
r = base.Resource(None, {"name": "1"}, resp=resp_obj)
self.assertEqual([REQUEST_ID], r.request_ids)
class ListWithMetaTest(utils.TestCase):
def test_list_with_meta(self):
resp = create_response_obj_with_header()
obj = common_base.ListWithMeta([], resp)
self.assertEqual([], obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class DictWithMetaTest(utils.TestCase):
def test_dict_with_meta(self):
resp = create_response_obj_with_header()
obj = common_base.DictWithMeta([], resp)
self.assertEqual({}, obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class TupleWithMetaTest(utils.TestCase):
def test_tuple_with_meta(self):
resp = create_response_obj_with_header()
obj = common_base.TupleWithMeta(resp, None)
self.assertIsInstance(obj, tuple)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)