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 abc
import copy import copy
from requests import Response
import six import six
from six.moves.urllib import parse from six.moves.urllib import parse
@ -409,7 +410,43 @@ class Extension(HookableMixin):
return "<Extension '%s'>" % self.name 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.). """Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes. This is pretty much just a bag for attributes.
@ -418,22 +455,26 @@ class Resource(object):
HUMAN_ID = False HUMAN_ID = False
NAME_ATTR = 'name' 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. """Populate and bind to a manager.
:param manager: BaseManager object :param manager: BaseManager object
:param info: dictionary representing resource attributes :param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True :param loaded: prevent lazy-loading if set to True
:param resp: Response or list of Response objects
""" """
self.manager = manager self.manager = manager
self._info = info self._info = info
self._add_details(info) self._add_details(info)
self._loaded = loaded self._loaded = loaded
self.setup()
self.append_request_ids(resp)
def __repr__(self): def __repr__(self):
reprkeys = sorted(k reprkeys = sorted(k
for k in self.__dict__.keys() 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) info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info) return "<%s %s>" % (self.__class__.__name__, info)
@ -493,3 +534,26 @@ class Resource(object):
def to_dict(self): def to_dict(self):
return copy.deepcopy(self._info) 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from requests import Response
from cinderclient import base from cinderclient import base
from cinderclient import exceptions from cinderclient import exceptions
from cinderclient.openstack.common.apiclient import base as common_base
from cinderclient.v1 import volumes from cinderclient.v1 import volumes
from cinderclient.tests.unit import utils from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes from cinderclient.tests.unit.v1 import fakes
@ -21,11 +24,21 @@ from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient() 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): class BaseTest(utils.TestCase):
def test_resource_repr(self): def test_resource_repr(self):
r = base.Resource(None, dict(foo="bar", baz="spam")) r = base.Resource(None, dict(foo="bar", baz="spam"))
self.assertEqual("<Resource baz=spam, foo=bar>", repr(r)) self.assertEqual("<Resource baz=spam, foo=bar>", repr(r))
self.assertNotIn("x_openstack_request_ids", repr(r))
def test_getid(self): def test_getid(self):
self.assertEqual(4, base.getid(4)) self.assertEqual(4, base.getid(4))
@ -63,3 +76,38 @@ class BaseTest(utils.TestCase):
def test_to_dict(self): def test_to_dict(self):
r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
self.assertEqual({'id': 1, 'name': 'hi'}, r1.to_dict()) 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)