Add request id to returned objects

Adding two classes RequestIdProxy and GeneratorProxy derived from
wrapt.ObjectProxy to wrap objects returned from the API.

GeneratorProxy class is used to wrap generator objects returned
by cases like images.list() etc. whereas RequestIdProxy class is
used to wrap non-generator object cases like images.create() etc.

In all cases the returned object will have the same behavior as
the wrapped(original) object. However now the returned objects
will have an extra property 'request_ids' which is a list of
exactly one request id.

For generator cases the request_ids property will be an empty list
until the underlying generator is invoked at-least once.

Co-Authored-By: Abhishek Kekane <abhishek.kekane@nttdata.com>

Closes-Bug: #1525259
Blueprint: return-request-id-to-caller
Change-Id: If8c0e0843270ff718a37ca2697afeb8da22aa3b1
This commit is contained in:
Ravi Jethani 2016-04-22 13:54:49 +00:00 committed by Abhishek Kekane
parent 54e6faadf2
commit 610177a779
20 changed files with 578 additions and 211 deletions

View File

@ -36,6 +36,7 @@ else:
from oslo_utils import encodeutils
from oslo_utils import strutils
import prettytable
import wrapt
from glanceclient._i18n import _
from glanceclient import exc
@ -472,3 +473,76 @@ class IterableWithLength(object):
def __len__(self):
return self.length
class RequestIdProxy(wrapt.ObjectProxy):
def __init__(self, wrapped):
# `wrapped` is a tuple: (original_obj, response_obj)
super(RequestIdProxy, self).__init__(wrapped[0])
self._self_wrapped = wrapped[0]
req_id = _extract_request_id(wrapped[1])
self._self_request_ids = [req_id]
@property
def request_ids(self):
return self._self_request_ids
@property
def wrapped(self):
return self._self_wrapped
class GeneratorProxy(wrapt.ObjectProxy):
def __init__(self, wrapped):
super(GeneratorProxy, self).__init__(wrapped)
self._self_wrapped = wrapped
self._self_request_ids = []
def _set_request_ids(self, resp):
if self._self_request_ids == []:
req_id = _extract_request_id(resp)
self._self_request_ids = [req_id]
def _next(self):
obj, resp = next(self._self_wrapped)
self._set_request_ids(resp)
return obj
# Override generator's next method to add
# request id on each iteration
def next(self):
return self._next()
# For Python 3 compatibility
def __next__(self):
return self._next()
def __iter__(self):
return self
@property
def request_ids(self):
return self._self_request_ids
@property
def wrapped(self):
return self._self_wrapped
def add_req_id_to_object():
@wrapt.decorator
def inner(wrapped, instance, args, kwargs):
return RequestIdProxy(wrapped(*args, **kwargs))
return inner
def add_req_id_to_generator():
@wrapt.decorator
def inner(wrapped, instance, args, kwargs):
return GeneratorProxy(wrapped(*args, **kwargs))
return inner
def _extract_request_id(resp):
# TODO(rsjethani): Do we need more checks here?
return resp.headers.get('x-openstack-request-id')

View File

@ -17,6 +17,7 @@ import sys
import mock
from oslo_utils import encodeutils
from requests import Response
import six
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
@ -25,6 +26,15 @@ import testtools
from glanceclient.common import utils
REQUEST_ID = 'req-1234'
def create_response_obj_with_req_id(req_id):
resp = Response()
resp.headers['x-openstack-request-id'] = req_id
return resp
class TestUtils(testtools.TestCase):
def test_make_size_human_readable(self):
@ -178,3 +188,43 @@ class TestUtils(testtools.TestCase):
(name, value) = utils.safe_header(sensitive_header, None)
self.assertEqual(sensitive_header, name)
self.assertIsNone(value)
def test_generator_proxy(self):
def _test_decorator():
i = 1
resp = create_response_obj_with_req_id(REQUEST_ID)
while True:
yield i, resp
i += 1
gen_obj = _test_decorator()
proxy = utils.GeneratorProxy(gen_obj)
# Proxy object should succeed in behaving as the
# wrapped object
self.assertTrue(isinstance(proxy, type(gen_obj)))
# Initially request_ids should be empty
self.assertEqual([], proxy.request_ids)
# Only after we have started iterating we should
# see non-empty `request_ids` property
proxy.next()
self.assertEqual([REQUEST_ID], proxy.request_ids)
# Even after multiple iterations `request_ids` property
# should only contain one request id
proxy.next()
proxy.next()
self.assertEqual(1, len(proxy.request_ids))
def test_request_id_proxy(self):
def test_data(val):
resp = create_response_obj_with_req_id(REQUEST_ID)
return val, resp
# Object of any type except decorator can be passed to test_data
proxy = utils.RequestIdProxy(test_data(11))
# Verify that proxy object has a property `request_ids` and it is
# a list of one request id
self.assertEqual([REQUEST_ID], proxy.request_ids)

View File

@ -0,0 +1,119 @@
# Copyright 2016 NTT DATA
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
class BaseController(testtools.TestCase):
def __init__(self, api, schema_api, controller_class):
self.controller = controller_class(api, schema_api)
def _assertRequestId(self, obj):
self.assertIsNotNone(getattr(obj, 'request_ids', None))
self.assertEqual(['req-1234'], obj.request_ids)
def list(self, *args, **kwargs):
gen_obj = self.controller.list(*args, **kwargs)
# For generator cases the request_ids property will be an empty list
# until the underlying generator is invoked at-least once.
resources = list(gen_obj)
if len(resources) > 0:
self._assertRequestId(gen_obj)
else:
# If list is empty that means geneator object has raised
# StopIteration for first iteration and will not contain the
# request_id in it.
self.assertEqual([], gen_obj.request_ids)
return resources
def get(self, *args, **kwargs):
resource = self.controller.get(*args, **kwargs)
self._assertRequestId(resource)
return resource
def create(self, *args, **kwargs):
resource = self.controller.create(*args, **kwargs)
self._assertRequestId(resource)
return resource
def create_multiple(self, *args, **kwargs):
tags = self.controller.create_multiple(*args, **kwargs)
actual = [tag.name for tag in tags]
self._assertRequestId(tags)
return actual
def update(self, *args, **properties):
resource = self.controller.update(*args, **properties)
self._assertRequestId(resource)
return resource
def delete(self, *args):
resp = self.controller.delete(*args)
self._assertRequestId(resp)
def delete_all(self, *args):
resp = self.controller.delete_all(*args)
self._assertRequestId(resp)
def deactivate(self, *args):
resp = self.controller.deactivate(*args)
self._assertRequestId(resp)
def reactivate(self, *args):
resp = self.controller.reactivate(*args)
self._assertRequestId(resp)
def upload(self, *args, **kwargs):
resp = self.controller.upload(*args, **kwargs)
self._assertRequestId(resp)
def data(self, *args, **kwargs):
body = self.controller.data(*args, **kwargs)
self._assertRequestId(body)
return body
def delete_locations(self, *args):
resp = self.controller.delete_locations(*args)
self._assertRequestId(resp)
def add_location(self, *args, **kwargs):
resp = self.controller.add_location(*args, **kwargs)
self._assertRequestId(resp)
def update_location(self, *args, **kwargs):
resp = self.controller.update_location(*args, **kwargs)
self._assertRequestId(resp)
def associate(self, *args, **kwargs):
resource_types = self.controller.associate(*args, **kwargs)
self._assertRequestId(resource_types)
return resource_types
def deassociate(self, *args):
resp = self.controller.deassociate(*args)
self._assertRequestId(resp)
class BaseResourceTypeController(BaseController):
def __init__(self, api, schema_api, controller_class):
super(BaseResourceTypeController, self).__init__(api, schema_api,
controller_class)
def get(self, *args, **kwargs):
resource_types = self.controller.get(*args)
names = [rt.name for rt in resource_types]
self._assertRequestId(resource_types)
return names

View File

@ -18,6 +18,7 @@ import mock
import testtools
from glanceclient import exc
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import images
@ -532,27 +533,25 @@ class TestController(testtools.TestCase):
super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = images.Controller(self.api, self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
images.Controller)
def test_list_images(self):
# NOTE(bcwaldon):cast to list since the controller returns a generator
images = list(self.controller.list())
images = self.controller.list()
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
self.assertEqual('image-1', images[0].name)
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
self.assertEqual('image-2', images[1].name)
def test_list_images_paginated(self):
# NOTE(bcwaldon):cast to list since the controller returns a generator
images = list(self.controller.list(page_size=1))
images = self.controller.list(page_size=1)
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
self.assertEqual('image-1', images[0].name)
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
self.assertEqual('image-2', images[1].name)
def test_list_images_paginated_with_limit(self):
# NOTE(bcwaldon):cast to list since the controller returns a generator
images = list(self.controller.list(limit=3, page_size=2))
images = self.controller.list(limit=3, page_size=2)
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
self.assertEqual('image-1', images[0].name)
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
@ -562,40 +561,40 @@ class TestController(testtools.TestCase):
self.assertEqual(3, len(images))
def test_list_images_with_marker(self):
images = list(self.controller.list(limit=1,
marker='3a4560a1-e585-443e-9b39-553b46ec92d1'))
images = self.controller.list(
limit=1, marker='3a4560a1-e585-443e-9b39-553b46ec92d1')
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[0].id)
self.assertEqual('image-2', images[0].name)
def test_list_images_visibility_public(self):
filters = {'filters': {'visibility': 'public'}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(_PUBLIC_ID, images[0].id)
def test_list_images_visibility_private(self):
filters = {'filters': {'visibility': 'private'}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(_PRIVATE_ID, images[0].id)
def test_list_images_visibility_shared(self):
filters = {'filters': {'visibility': 'shared'}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(_SHARED_ID, images[0].id)
def test_list_images_member_status_rejected(self):
filters = {'filters': {'member_status': 'rejected'}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(_STATUS_REJECTED_ID, images[0].id)
def test_list_images_for_owner(self):
filters = {'filters': {'owner': _OWNER_ID}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(_OWNED_IMAGE_ID, images[0].id)
def test_list_images_for_checksum_single_image(self):
fake_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
filters = {'filters': {'checksum': _CHKSUM}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(1, len(images))
self.assertEqual('%s' % fake_id, images[0].id)
@ -603,32 +602,32 @@ class TestController(testtools.TestCase):
fake_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
fake_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'
filters = {'filters': {'checksum': _CHKSUM1}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(2, len(images))
self.assertEqual('%s' % fake_id1, images[0].id)
self.assertEqual('%s' % fake_id2, images[1].id)
def test_list_images_for_wrong_checksum(self):
filters = {'filters': {'checksum': 'wrong'}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(0, len(images))
def test_list_images_for_bogus_owner(self):
filters = {'filters': {'owner': _BOGUS_ID}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual([], images)
def test_list_images_for_bunch_of_filters(self):
filters = {'filters': {'owner': _BOGUS_ID,
'visibility': 'shared',
'member_status': 'pending'}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(_EVERYTHING_ID, images[0].id)
def test_list_images_filters_encoding(self):
filters = {"owner": u"ni\xf1o"}
try:
list(self.controller.list(filters=filters))
self.controller.list(filters=filters)
except KeyError:
# NOTE(flaper87): It raises KeyError because there's
# no fixture supporting this query:
@ -640,7 +639,7 @@ class TestController(testtools.TestCase):
def test_list_images_for_tag_single_image(self):
img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
filters = {'filters': {'tag': [_TAG1]}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(1, len(images))
self.assertEqual('%s' % img_id, images[0].id)
@ -648,7 +647,7 @@ class TestController(testtools.TestCase):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
img_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'
filters = {'filters': {'tag': [_TAG2]}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[0].id)
self.assertEqual('%s' % img_id2, images[1].id)
@ -656,33 +655,32 @@ class TestController(testtools.TestCase):
def test_list_images_for_multi_tags(self):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
filters = {'filters': {'tag': [_TAG1, _TAG2]}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(1, len(images))
self.assertEqual('%s' % img_id1, images[0].id)
def test_list_images_for_non_existent_tag(self):
filters = {'filters': {'tag': ['fake']}}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(0, len(images))
def test_list_images_for_invalid_tag(self):
filters = {'filters': {'tag': [[]]}}
self.assertRaises(exc.HTTPBadRequest,
list,
self.controller.list(**filters))
self.controller.list, **filters)
def test_list_images_with_single_sort_key(self):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
sort_key = 'name'
images = list(self.controller.list(sort_key=sort_key))
images = self.controller.list(sort_key=sort_key)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[0].id)
def test_list_with_multiple_sort_keys(self):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
sort_key = ['name', 'id']
images = list(self.controller.list(sort_key=sort_key))
images = self.controller.list(sort_key=sort_key)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[0].id)
@ -690,8 +688,7 @@ class TestController(testtools.TestCase):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
sort_key = 'id'
sort_dir = 'desc'
images = list(self.controller.list(sort_key=sort_key,
sort_dir=sort_dir))
images = self.controller.list(sort_key=sort_key, sort_dir=sort_dir)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[1].id)
@ -699,8 +696,7 @@ class TestController(testtools.TestCase):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
sort_key = ['name', 'id']
sort_dir = 'desc'
images = list(self.controller.list(sort_key=sort_key,
sort_dir=sort_dir))
images = self.controller.list(sort_key=sort_key, sort_dir=sort_dir)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[1].id)
@ -708,61 +704,50 @@ class TestController(testtools.TestCase):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
sort_key = ['name', 'id']
sort_dir = ['desc', 'asc']
images = list(self.controller.list(sort_key=sort_key,
sort_dir=sort_dir))
images = self.controller.list(sort_key=sort_key, sort_dir=sort_dir)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[1].id)
def test_list_images_with_new_sorting_syntax(self):
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
sort = 'name:desc,size:asc'
images = list(self.controller.list(sort=sort))
images = self.controller.list(sort=sort)
self.assertEqual(2, len(images))
self.assertEqual('%s' % img_id1, images[1].id)
def test_list_images_sort_dirs_fewer_than_keys(self):
sort_key = ['name', 'id', 'created_at']
sort_dir = ['desc', 'asc']
self.assertRaises(exc.HTTPBadRequest,
list,
self.controller.list(
sort_key=sort_key,
sort_dir=sort_dir))
self.assertRaises(exc.HTTPBadRequest, self.controller.list,
sort_key=sort_key, sort_dir=sort_dir)
def test_list_images_combined_syntax(self):
sort_key = ['name', 'id']
sort_dir = ['desc', 'asc']
sort = 'name:asc'
self.assertRaises(exc.HTTPBadRequest,
list,
self.controller.list(
sort=sort,
sort_key=sort_key,
sort_dir=sort_dir))
self.controller.list, sort=sort, sort_key=sort_key,
sort_dir=sort_dir)
def test_list_images_new_sorting_syntax_invalid_key(self):
sort = 'INVALID:asc'
self.assertRaises(exc.HTTPBadRequest,
list,
self.controller.list(
sort=sort))
self.assertRaises(exc.HTTPBadRequest, self.controller.list,
sort=sort)
def test_list_images_new_sorting_syntax_invalid_direction(self):
sort = 'name:INVALID'
self.assertRaises(exc.HTTPBadRequest,
list,
self.controller.list(
sort=sort))
self.assertRaises(exc.HTTPBadRequest, self.controller.list,
sort=sort)
def test_list_images_for_property(self):
filters = {'filters': dict([('os_distro', 'NixOS')])}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(1, len(images))
def test_list_images_for_non_existent_property(self):
filters = {'filters': dict([('my_little_property',
'cant_be_this_cute')])}
images = list(self.controller.list(**filters))
images = self.controller.list(**filters)
self.assertEqual(0, len(images))
def test_get_image(self):
@ -804,7 +789,7 @@ class TestController(testtools.TestCase):
None)]
self.assertEqual(expect, self.api.calls)
def reactivate_image(self):
def test_reactivate_image(self):
id_image = '87b634c1-f893-33c9-28a9-e5673c99239a'
self.controller.reactivate(id_image)
expect = [('POST',
@ -868,12 +853,12 @@ class TestController(testtools.TestCase):
def test_download_no_data(self):
resp = utils.FakeResponse(headers={}, status_code=204)
self.controller.http_client.get = mock.Mock(return_value=(resp, None))
body = self.controller.data('image_id')
self.assertIsNone(body)
self.controller.controller.http_client.get = mock.Mock(
return_value=(resp, None))
self.controller.data('image_id')
def test_download_forbidden(self):
self.controller.http_client.get = mock.Mock(
self.controller.controller.http_client.get = mock.Mock(
side_effect=exc.HTTPForbidden())
try:
self.controller.data('image_id')
@ -893,7 +878,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -912,7 +898,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -931,7 +918,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -953,7 +941,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -974,7 +963,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -999,7 +989,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -1016,7 +1007,8 @@ class TestController(testtools.TestCase):
expect = [
('GET', '/v2/images/%s' % image_id, {}, None),
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
('GET', '/v2/images/%s' % image_id, {}, None),
('GET', '/v2/images/%s' % image_id,
{'x-openstack-request-id': 'req-1234'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(image_id, image.id)
@ -1040,8 +1032,9 @@ class TestController(testtools.TestCase):
image_id, url, meta)
self.assertIn(estr, str(e))
def _empty_get(self, image_id):
return ('GET', '/v2/images/%s' % image_id, {}, None)
def _empty_get(self, image_id, headers=None):
return ('GET', '/v2/images/%s' % image_id,
headers or {}, None)
def _patch_req(self, image_id, patch_body):
c_type = 'application/openstack-images-v2.1-json-patch'
@ -1055,9 +1048,11 @@ class TestController(testtools.TestCase):
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
new_loc = {'url': 'http://spam.com/', 'metadata': {'spam': 'ham'}}
add_patch = {'path': '/locations/-', 'value': new_loc, 'op': 'add'}
headers = {'x-openstack-request-id': 'req-1234'}
self.controller.add_location(image_id, **new_loc)
self.assertEqual([self._patch_req(image_id, [add_patch]),
self._empty_get(image_id)], self.api.calls)
self._empty_get(image_id, headers=headers)],
self.api.calls)
@mock.patch.object(images.Controller, '_send_image_update_request',
side_effect=exc.HTTPBadRequest)
@ -1092,6 +1087,7 @@ class TestController(testtools.TestCase):
def test_update_location(self):
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
new_loc = {'url': 'http://foo.com/', 'metadata': {'spam': 'ham'}}
headers = {'x-openstack-request-id': 'req-1234'}
fixture_idx = '/v2/images/%s' % (image_id)
orig_locations = data_fixtures[fixture_idx]['GET'][1]['locations']
loc_map = dict([(l['url'], l) for l in orig_locations])
@ -1101,12 +1097,13 @@ class TestController(testtools.TestCase):
self.controller.update_location(image_id, **new_loc)
self.assertEqual([self._empty_get(image_id),
self._patch_req(image_id, mod_patch),
self._empty_get(image_id)],
self._empty_get(image_id, headers=headers)],
self.api.calls)
def test_update_tags(self):
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
tag_map = {'tags': ['tag01', 'tag02', 'tag03']}
headers = {'x-openstack-request-id': 'req-1234'}
image = self.controller.update(image_id, **tag_map)
@ -1115,7 +1112,7 @@ class TestController(testtools.TestCase):
expected = [
self._empty_get(image_id),
self._patch_req(image_id, expected_body),
self._empty_get(image_id)
self._empty_get(image_id, headers=headers)
]
self.assertEqual(expected, self.api.calls)
self.assertEqual(image_id, image.id)

View File

@ -15,6 +15,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import image_members
@ -80,12 +81,12 @@ class TestController(testtools.TestCase):
super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = image_members.Controller(self.api, self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
image_members.Controller)
def test_list_image_members(self):
image_id = IMAGE
# NOTE(iccha): cast to list since the controller returns a generator
image_members = list(self.controller.list(image_id))
image_members = self.controller.list(image_id)
self.assertEqual(IMAGE, image_members[0].image_id)
self.assertEqual(MEMBER, image_members[0].member_id)

View File

@ -15,6 +15,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
@ -542,83 +543,79 @@ class TestNamespaceController(testtools.TestCase):
super(TestNamespaceController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.NamespaceController(self.api,
self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
metadefs.NamespaceController)
def test_list_namespaces(self):
namespaces = list(self.controller.list())
namespaces = self.controller.list()
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE1, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE2, namespaces[1]['namespace'])
def test_list_namespaces_paginate(self):
namespaces = list(self.controller.list(page_size=1))
namespaces = self.controller.list(page_size=1)
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE7, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE8, namespaces[1]['namespace'])
def test_list_with_limit_greater_than_page_size(self):
namespaces = list(self.controller.list(page_size=1, limit=2))
namespaces = self.controller.list(page_size=1, limit=2)
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE7, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE8, namespaces[1]['namespace'])
def test_list_with_marker(self):
namespaces = list(self.controller.list(marker=NAMESPACE6, page_size=2))
namespaces = self.controller.list(marker=NAMESPACE6, page_size=2)
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE7, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE8, namespaces[1]['namespace'])
def test_list_with_sort_dir(self):
namespaces = list(self.controller.list(sort_dir='asc', limit=1))
namespaces = self.controller.list(sort_dir='asc', limit=1)
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE1, namespaces[0]['namespace'])
def test_list_with_sort_dir_invalid(self):
# NOTE(TravT): The clients work by returning an iterator.
# Invoking the iterator is what actually executes the logic.
ns_iterator = self.controller.list(sort_dir='foo')
self.assertRaises(ValueError, next, ns_iterator)
self.assertRaises(ValueError, self.controller.list, sort_dir='foo')
def test_list_with_sort_key(self):
namespaces = list(self.controller.list(sort_key='created_at', limit=1))
namespaces = self.controller.list(sort_key='created_at', limit=1)
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE1, namespaces[0]['namespace'])
def test_list_with_sort_key_invalid(self):
# NOTE(TravT): The clients work by returning an iterator.
# Invoking the iterator is what actually executes the logic.
ns_iterator = self.controller.list(sort_key='foo')
self.assertRaises(ValueError, next, ns_iterator)
self.assertRaises(ValueError, self.controller.list, sort_key='foo')
def test_list_namespaces_with_one_resource_type_filter(self):
namespaces = list(self.controller.list(
namespaces = self.controller.list(
filters={
'resource_types': [RESOURCE_TYPE1]
}
))
)
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE3, namespaces[0]['namespace'])
def test_list_namespaces_with_multiple_resource_types_filter(self):
namespaces = list(self.controller.list(
namespaces = self.controller.list(
filters={
'resource_types': [RESOURCE_TYPE1, RESOURCE_TYPE2]
}
))
)
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE4, namespaces[0]['namespace'])
def test_list_namespaces_with_visibility_filter(self):
namespaces = list(self.controller.list(
namespaces = self.controller.list(
filters={
'visibility': 'private'
}
))
)
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE5, namespaces[0]['namespace'])

View File

@ -16,6 +16,7 @@
import six
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
@ -263,11 +264,11 @@ class TestObjectController(testtools.TestCase):
super(TestObjectController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.ObjectController(self.api, self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
metadefs.ObjectController)
def test_list_object(self):
objects = list(self.controller.list(NAMESPACE1))
objects = self.controller.list(NAMESPACE1)
actual = [obj.name for obj in objects]
self.assertEqual([OBJECT1, OBJECT2], actual)

View File

@ -15,6 +15,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
@ -238,12 +239,11 @@ class TestPropertyController(testtools.TestCase):
super(TestPropertyController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.PropertyController(self.api,
self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
metadefs.PropertyController)
def test_list_property(self):
properties = list(self.controller.list(NAMESPACE1))
properties = self.controller.list(NAMESPACE1)
actual = [prop.name for prop in properties]
self.assertEqual(sorted([PROPERTY1, PROPERTY2]), sorted(actual))

View File

@ -15,6 +15,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
@ -151,18 +152,17 @@ class TestResoureTypeController(testtools.TestCase):
super(TestResoureTypeController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.ResourceTypeController(self.api,
self.schema_api)
self.controller = base.BaseResourceTypeController(
self.api, self.schema_api, metadefs.ResourceTypeController)
def test_list_resource_types(self):
resource_types = list(self.controller.list())
resource_types = self.controller.list()
names = [rt.name for rt in resource_types]
self.assertEqual([RESOURCE_TYPE1, RESOURCE_TYPE2], names)
def test_get_resource_types(self):
resource_types = list(self.controller.get(NAMESPACE1))
names = [rt.name for rt in resource_types]
self.assertEqual([RESOURCE_TYPE3, RESOURCE_TYPE4], names)
resource_types = self.controller.get(NAMESPACE1)
self.assertEqual([RESOURCE_TYPE3, RESOURCE_TYPE4], resource_types)
def test_associate_resource_types(self):
resource_types = self.controller.associate(NAMESPACE1,

View File

@ -15,6 +15,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
@ -134,11 +135,11 @@ class TestTagController(testtools.TestCase):
super(TestTagController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.TagController(self.api, self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
metadefs.TagController)
def test_list_tag(self):
tags = list(self.controller.list(NAMESPACE1))
tags = self.controller.list(NAMESPACE1)
actual = [tag.name for tag in tags]
self.assertEqual([TAG1, TAG2], actual)
@ -155,8 +156,7 @@ class TestTagController(testtools.TestCase):
'tags': [TAGNEW2, TAGNEW3]
}
tags = self.controller.create_multiple(NAMESPACE1, **properties)
actual = [tag.name for tag in tags]
self.assertEqual([TAGNEW2, TAGNEW3], actual)
self.assertEqual([TAGNEW2, TAGNEW3], tags)
def test_update_tag(self):
properties = {

View File

@ -15,6 +15,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import image_tags
@ -54,7 +55,8 @@ class TestController(testtools.TestCase):
super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = image_tags.Controller(self.api, self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
image_tags.Controller)
def test_update_image_tag(self):
image_id = IMAGE

View File

@ -16,6 +16,7 @@
import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
from glanceclient.v2 import tasks
@ -252,11 +253,11 @@ class TestController(testtools.TestCase):
super(TestController, self).setUp()
self.api = utils.FakeAPI(fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = tasks.Controller(self.api, self.schema_api)
self.controller = base.BaseController(self.api, self.schema_api,
tasks.Controller)
def test_list_tasks(self):
# NOTE(flwang): cast to list since the controller returns a generator
tasks = list(self.controller.list())
tasks = self.controller.list()
self.assertEqual(_PENDING_ID, tasks[0].id)
self.assertEqual('import', tasks[0].type)
self.assertEqual('pending', tasks[0].status)
@ -265,8 +266,7 @@ class TestController(testtools.TestCase):
self.assertEqual('processing', tasks[1].status)
def test_list_tasks_paginated(self):
# NOTE(flwang): cast to list since the controller returns a generator
tasks = list(self.controller.list(page_size=1))
tasks = self.controller.list(page_size=1)
self.assertEqual(_PENDING_ID, tasks[0].id)
self.assertEqual('import', tasks[0].type)
self.assertEqual(_PROCESSING_ID, tasks[1].id)
@ -274,38 +274,38 @@ class TestController(testtools.TestCase):
def test_list_tasks_with_status(self):
filters = {'filters': {'status': 'processing'}}
tasks = list(self.controller.list(**filters))
tasks = self.controller.list(**filters)
self.assertEqual(_OWNED_TASK_ID, tasks[0].id)
def test_list_tasks_with_wrong_status(self):
filters = {'filters': {'status': 'fake'}}
tasks = list(self.controller.list(**filters))
tasks = self.controller.list(**filters)
self.assertEqual(0, len(tasks))
def test_list_tasks_with_type(self):
filters = {'filters': {'type': 'import'}}
tasks = list(self.controller.list(**filters))
tasks = self.controller.list(**filters)
self.assertEqual(_OWNED_TASK_ID, tasks[0].id)
def test_list_tasks_with_wrong_type(self):
filters = {'filters': {'type': 'fake'}}
tasks = list(self.controller.list(**filters))
tasks = self.controller.list(**filters)
self.assertEqual(0, len(tasks))
def test_list_tasks_for_owner(self):
filters = {'filters': {'owner': _OWNER_ID}}
tasks = list(self.controller.list(**filters))
tasks = self.controller.list(**filters)
self.assertEqual(_OWNED_TASK_ID, tasks[0].id)
def test_list_tasks_for_fake_owner(self):
filters = {'filters': {'owner': _FAKE_OWNER_ID}}
tasks = list(self.controller.list(**filters))
tasks = self.controller.list(**filters)
self.assertEqual(tasks, [])
def test_list_tasks_filters_encoding(self):
filters = {"owner": u"ni\xf1o"}
try:
list(self.controller.list(filters=filters))
self.controller.list(filters=filters)
except KeyError:
# NOTE(flaper87): It raises KeyError because there's
# no fixture supporting this query:
@ -316,34 +316,33 @@ class TestController(testtools.TestCase):
self.assertEqual(b"ni\xc3\xb1o", filters["owner"])
def test_list_tasks_with_marker(self):
tasks = list(self.controller.list(marker=_PENDING_ID, page_size=1))
tasks = self.controller.list(marker=_PENDING_ID, page_size=1)
self.assertEqual(1, len(tasks))
self.assertEqual(_PROCESSING_ID, tasks[0]['id'])
def test_list_tasks_with_single_sort_key(self):
tasks = list(self.controller.list(sort_key='type'))
tasks = self.controller.list(sort_key='type')
self.assertEqual(2, len(tasks))
self.assertEqual(_PENDING_ID, tasks[0].id)
def test_list_tasks_with_invalid_sort_key(self):
self.assertRaises(ValueError,
list,
self.controller.list(sort_key='invalid'))
self.controller.list, sort_key='invalid')
def test_list_tasks_with_desc_sort_dir(self):
tasks = list(self.controller.list(sort_key='id', sort_dir='desc'))
tasks = self.controller.list(sort_key='id', sort_dir='desc')
self.assertEqual(2, len(tasks))
self.assertEqual(_PENDING_ID, tasks[1].id)
def test_list_tasks_with_asc_sort_dir(self):
tasks = list(self.controller.list(sort_key='id', sort_dir='asc'))
tasks = self.controller.list(sort_key='id', sort_dir='asc')
self.assertEqual(2, len(tasks))
self.assertEqual(_PENDING_ID, tasks[0].id)
def test_list_tasks_with_invalid_sort_dir(self):
self.assertRaises(ValueError,
list,
self.controller.list(sort_dir='invalid'))
self.controller.list,
sort_dir='invalid')
def test_get_task(self):
task = self.controller.get(_PENDING_ID)

View File

@ -113,6 +113,7 @@ class FakeResponse(object):
self.reason = reason
self.version = version
self.headers = headers
self.headers['x-openstack-request-id'] = 'req-1234'
self.status_code = status_code
self.raw = RawRequest(headers, body=body, reason=reason,
version=version, status=status_code)

View File

@ -32,24 +32,29 @@ class Controller(object):
schema = self.schema_client.get('member')
return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel)
@utils.add_req_id_to_generator()
def list(self, image_id):
url = '/v2/images/%s/members' % image_id
resp, body = self.http_client.get(url)
for member in body['members']:
yield self.model(member)
yield self.model(member), resp
@utils.add_req_id_to_object()
def delete(self, image_id, member_id):
self.http_client.delete('/v2/images/%s/members/%s' %
(image_id, member_id))
resp, body = self.http_client.delete('/v2/images/%s/members/%s' %
(image_id, member_id))
return (resp, body), resp
@utils.add_req_id_to_object()
def update(self, image_id, member_id, member_status):
url = '/v2/images/%s/members/%s' % (image_id, member_id)
body = {'status': member_status}
resp, updated_member = self.http_client.put(url, data=body)
return self.model(updated_member)
return self.model(updated_member), resp
@utils.add_req_id_to_object()
def create(self, image_id, member_id):
url = '/v2/images/%s/members' % image_id
body = {'member': member_id}
resp, created_member = self.http_client.post(url, data=body)
return self.model(created_member)
return self.model(created_member), resp

View File

@ -30,6 +30,7 @@ class Controller(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def update(self, image_id, tag_value):
"""Update an image with the given tag.
@ -37,8 +38,10 @@ class Controller(object):
:param tag_value: value of the tag.
"""
url = '/v2/images/%s/tags/%s' % (image_id, tag_value)
self.http_client.put(url)
resp, body = self.http_client.put(url)
return (resp, body), resp
@utils.add_req_id_to_object()
def delete(self, image_id, tag_value):
"""Delete the tag associated with the given image.
@ -46,4 +49,5 @@ class Controller(object):
:param tag_value: tag value to be deleted.
"""
url = '/v2/images/%s/tags/%s' % (image_id, tag_value)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp

View File

@ -81,6 +81,7 @@ class Controller(object):
raise exc.HTTPBadRequest(msg)
return sort
@utils.add_req_id_to_generator()
def list(self, **kwargs):
"""Retrieve a listing of Image objects.
@ -97,6 +98,7 @@ class Controller(object):
def paginate(url, page_size, limit=None):
next_url = url
req_id_hdr = {}
while True:
if limit and page_size > limit:
@ -105,7 +107,12 @@ class Controller(object):
next_url = next_url.replace("limit=%s" % page_size,
"limit=%s" % limit)
resp, body = self.http_client.get(next_url)
resp, body = self.http_client.get(next_url, headers=req_id_hdr)
# NOTE(rsjethani): Store curent request id so that it can be
# used in subsequent requests. Refer bug #1525259
req_id_hdr['x-openstack-request-id'] = \
utils._extract_request_id(resp)
for image in body['images']:
# NOTE(bcwaldon): remove 'self' for now until we have
# an elegant way to pass it into the model constructor
@ -114,7 +121,7 @@ class Controller(object):
# We do not validate the model when listing.
# This prevents side-effects of injecting invalid
# schema values via v1.
yield self.unvalidated_model(**image)
yield self.unvalidated_model(**image), resp
if limit:
limit -= 1
if limit <= 0:
@ -173,17 +180,23 @@ class Controller(object):
if isinstance(kwargs.get('marker'), six.string_types):
url = '%s&marker=%s' % (url, kwargs['marker'])
for image in paginate(url, page_size, limit):
yield image
for image, resp in paginate(url, page_size, limit):
yield image, resp
def get(self, image_id):
@utils.add_req_id_to_object()
def _get(self, image_id, header=None):
url = '/v2/images/%s' % image_id
resp, body = self.http_client.get(url)
header = header or {}
resp, body = self.http_client.get(url, headers=header)
# NOTE(bcwaldon): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict
body.pop('self', None)
return self.unvalidated_model(**body)
return self.unvalidated_model(**body), resp
def get(self, image_id):
return self._get(image_id)
@utils.add_req_id_to_object()
def data(self, image_id, do_checksum=True):
"""Retrieve data of an image.
@ -194,7 +207,7 @@ class Controller(object):
url = '/v2/images/%s/file' % image_id
resp, body = self.http_client.get(url)
if resp.status_code == codes.no_content:
return None
return None, resp
checksum = resp.headers.get('content-md5', None)
content_length = int(resp.headers.get('content-length', 0))
@ -202,8 +215,9 @@ class Controller(object):
if do_checksum and checksum is not None:
body = utils.integrity_iter(body, checksum)
return utils.IterableWithLength(body, content_length)
return utils.IterableWithLength(body, content_length), resp
@utils.add_req_id_to_object()
def upload(self, image_id, image_data, image_size=None):
"""Upload the data for an image.
@ -214,13 +228,17 @@ class Controller(object):
url = '/v2/images/%s/file' % image_id
hdrs = {'Content-Type': 'application/octet-stream'}
body = image_data
self.http_client.put(url, headers=hdrs, data=body)
resp, body = self.http_client.put(url, headers=hdrs, data=body)
return (resp, body), resp
@utils.add_req_id_to_object()
def delete(self, image_id):
"""Delete an image."""
url = '/v2/images/%s' % image_id
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
@utils.add_req_id_to_object()
def create(self, **kwargs):
"""Create an image."""
url = '/v2/images'
@ -236,17 +254,21 @@ class Controller(object):
# NOTE(esheffield): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_object()
def deactivate(self, image_id):
"""Deactivate an image."""
url = '/v2/images/%s/actions/deactivate' % image_id
return self.http_client.post(url)
resp, body = self.http_client.post(url)
return (resp, body), resp
@utils.add_req_id_to_object()
def reactivate(self, image_id):
"""Reactivate an image."""
url = '/v2/images/%s/actions/reactivate' % image_id
return self.http_client.post(url)
resp, body = self.http_client.post(url)
return (resp, body), resp
def update(self, image_id, remove_props=None, **kwargs):
"""Update attributes of an image.
@ -276,12 +298,16 @@ class Controller(object):
url = '/v2/images/%s' % image_id
hdrs = {'Content-Type': 'application/openstack-images-v2.1-json-patch'}
self.http_client.patch(url, headers=hdrs, data=image.patch)
resp, _ = self.http_client.patch(url, headers=hdrs, data=image.patch)
# Get request id from `patch` request so it can be passed to the
# following `get` call
req_id_hdr = {
'x-openstack-request-id': utils._extract_request_id(resp)}
# NOTE(bcwaldon): calling image.patch doesn't clear the changes, so
# we need to fetch the image again to get a clean history. This is
# an obvious optimization for warlock
return self.get(image_id)
return self._get(image_id, req_id_hdr)
def _get_image_with_locations_or_fail(self, image_id):
image = self.get(image_id)
@ -290,10 +316,13 @@ class Controller(object):
'API access to image locations')
return image
@utils.add_req_id_to_object()
def _send_image_update_request(self, image_id, patch_body):
url = '/v2/images/%s' % image_id
hdrs = {'Content-Type': 'application/openstack-images-v2.1-json-patch'}
self.http_client.patch(url, headers=hdrs, data=json.dumps(patch_body))
resp, body = self.http_client.patch(url, headers=hdrs,
data=json.dumps(patch_body))
return (resp, body), resp
def add_location(self, image_id, url, metadata):
"""Add a new location entry to an image's list of locations.
@ -308,8 +337,11 @@ class Controller(object):
"""
add_patch = [{'op': 'add', 'path': '/locations/-',
'value': {'url': url, 'metadata': metadata}}]
self._send_image_update_request(image_id, add_patch)
return self.get(image_id)
response = self._send_image_update_request(image_id, add_patch)
# Get request id from the above update request and pass the same to
# following get request
req_id_hdr = {'x-openstack-request-id': response.request_ids[0]}
return self._get(image_id, req_id_hdr)
def delete_locations(self, image_id, url_set):
"""Remove one or more location entries of an image.
@ -332,7 +364,7 @@ class Controller(object):
url_indices.sort(reverse=True)
patches = [{'op': 'remove', 'path': '/locations/%s' % url_idx}
for url_idx in url_indices]
self._send_image_update_request(image_id, patches)
return self._send_image_update_request(image_id, patches)
def update_location(self, image_id, url, metadata):
"""Update an existing location entry in an image's list of locations.
@ -359,6 +391,9 @@ class Controller(object):
patches = [{'op': 'replace',
'path': '/locations',
'value': list(url_map.values())}]
self._send_image_update_request(image_id, patches)
response = self._send_image_update_request(image_id, patches)
# Get request id from the above update request and pass the same to
# following get request
req_id_hdr = {'x-openstack-request-id': response.request_ids[0]}
return self.get(image_id)
return self._get(image_id, req_id_hdr)

View File

@ -37,6 +37,7 @@ class NamespaceController(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, **kwargs):
"""Create a namespace.
@ -50,7 +51,7 @@ class NamespaceController(object):
resp, body = self.http_client.post(url, data=namespace)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
def update(self, namespace_name, **kwargs):
"""Update a namespace.
@ -72,23 +73,34 @@ class NamespaceController(object):
del namespace[elem]
url = '/v2/metadefs/namespaces/{0}'.format(namespace_name)
self.http_client.put(url, data=namespace)
return self.get(namespace.namespace)
# Pass the original wrapped value to http client.
resp, _ = self.http_client.put(url, data=namespace.wrapped)
# Get request id from `put` request so it can be passed to the
# following `get` call
req_id_hdr = {
'x-openstack-request-id': utils._extract_request_id(resp)
}
return self._get(namespace.namespace, header=req_id_hdr)
def get(self, namespace, **kwargs):
return self._get(namespace, **kwargs)
@utils.add_req_id_to_object()
def _get(self, namespace, header=None, **kwargs):
"""Get one namespace."""
query_params = parse.urlencode(kwargs)
if kwargs:
query_params = '?%s' % query_params
url = '/v2/metadefs/namespaces/{0}{1}'.format(namespace, query_params)
resp, body = self.http_client.get(url)
header = header or {}
resp, body = self.http_client.get(url, headers=header)
# NOTE(bcwaldon): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, **kwargs):
"""Retrieve a listing of Namespace objects.
@ -117,7 +129,7 @@ class NamespaceController(object):
# an elegant way to pass it into the model constructor
# without conflict.
namespace.pop('self', None)
yield self.model(**namespace)
yield self.model(**namespace), resp
# NOTE(zhiyan): In order to resolve the performance issue
# of JSON schema validation for image listing case, we
# don't validate each image entry but do it only on first
@ -132,8 +144,8 @@ class NamespaceController(object):
except KeyError:
return
else:
for namespace in paginate(next_url):
yield namespace
for namespace, resp in paginate(next_url):
yield namespace, resp
filters = kwargs.get('filters', {})
filters = {} if filters is None else filters
@ -170,13 +182,15 @@ class NamespaceController(object):
url = '/v2/metadefs/namespaces?%s' % parse.urlencode(filters)
for namespace in paginate(url):
yield namespace
for namespace, resp in paginate(url):
yield namespace, resp
@utils.add_req_id_to_object()
def delete(self, namespace):
"""Delete a namespace."""
url = '/v2/metadefs/namespaces/{0}'.format(namespace)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
class ResourceTypeController(object):
@ -190,6 +204,7 @@ class ResourceTypeController(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def associate(self, namespace, **kwargs):
"""Associate a resource type with a namespace."""
try:
@ -201,14 +216,17 @@ class ResourceTypeController(object):
res_type)
resp, body = self.http_client.post(url, data=res_type)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_object()
def deassociate(self, namespace, resource):
"""Deassociate a resource type with a namespace."""
url = '/v2/metadefs/namespaces/{0}/resource_types/{1}'. \
format(namespace, resource)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
@utils.add_req_id_to_generator()
def list(self):
"""Retrieve a listing of available resource types.
@ -218,14 +236,15 @@ class ResourceTypeController(object):
url = '/v2/metadefs/resource_types'
resp, body = self.http_client.get(url)
for resource_type in body['resource_types']:
yield self.model(**resource_type)
yield self.model(**resource_type), resp
@utils.add_req_id_to_generator()
def get(self, namespace):
url = '/v2/metadefs/namespaces/{0}/resource_types'.format(namespace)
resp, body = self.http_client.get(url)
body.pop('self', None)
for resource_type in body['resource_type_associations']:
yield self.model(**resource_type)
yield self.model(**resource_type), resp
class PropertyController(object):
@ -239,6 +258,7 @@ class PropertyController(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, namespace, **kwargs):
"""Create a property.
@ -254,7 +274,7 @@ class PropertyController(object):
resp, body = self.http_client.post(url, data=prop)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
def update(self, namespace, prop_name, **kwargs):
"""Update a property.
@ -272,18 +292,29 @@ class PropertyController(object):
url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace,
prop_name)
self.http_client.put(url, data=prop)
# Pass the original wrapped value to http client.
resp, _ = self.http_client.put(url, data=prop.wrapped)
# Get request id from `put` request so it can be passed to the
# following `get` call
req_id_hdr = {
'x-openstack-request-id': utils._extract_request_id(resp)}
return self.get(namespace, prop.name)
return self._get(namespace, prop.name, req_id_hdr)
def get(self, namespace, prop_name):
return self._get(namespace, prop_name)
@utils.add_req_id_to_object()
def _get(self, namespace, prop_name, header=None):
url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace,
prop_name)
resp, body = self.http_client.get(url)
header = header or {}
resp, body = self.http_client.get(url, headers=header)
body.pop('self', None)
body['name'] = prop_name
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata properties.
@ -295,18 +326,22 @@ class PropertyController(object):
for key, value in body['properties'].items():
value['name'] = key
yield self.model(value)
yield self.model(value), resp
@utils.add_req_id_to_object()
def delete(self, namespace, prop_name):
"""Delete a property."""
url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace,
prop_name)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
@utils.add_req_id_to_object()
def delete_all(self, namespace):
"""Delete all properties in a namespace."""
url = '/v2/metadefs/namespaces/{0}/properties'.format(namespace)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
class ObjectController(object):
@ -320,6 +355,7 @@ class ObjectController(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, namespace, **kwargs):
"""Create an object.
@ -335,7 +371,7 @@ class ObjectController(object):
resp, body = self.http_client.post(url, data=obj)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
def update(self, namespace, object_name, **kwargs):
"""Update an object.
@ -359,17 +395,28 @@ class ObjectController(object):
url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace,
object_name)
self.http_client.put(url, data=obj)
# Pass the original wrapped value to http client.
resp, _ = self.http_client.put(url, data=obj.wrapped)
# Get request id from `put` request so it can be passed to the
# following `get` call
req_id_hdr = {
'x-openstack-request-id': utils._extract_request_id(resp)}
return self.get(namespace, obj.name)
return self._get(namespace, obj.name, req_id_hdr)
def get(self, namespace, object_name):
return self._get(namespace, object_name)
@utils.add_req_id_to_object()
def _get(self, namespace, object_name, header=None):
url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace,
object_name)
resp, body = self.http_client.get(url)
header = header or {}
resp, body = self.http_client.get(url, headers=header)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata objects.
@ -379,18 +426,22 @@ class ObjectController(object):
resp, body = self.http_client.get(url)
for obj in body['objects']:
yield self.model(obj)
yield self.model(obj), resp
@utils.add_req_id_to_object()
def delete(self, namespace, object_name):
"""Delete an object."""
url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace,
object_name)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
@utils.add_req_id_to_object()
def delete_all(self, namespace):
"""Delete all objects in a namespace."""
url = '/v2/metadefs/namespaces/{0}/objects'.format(namespace)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
class TagController(object):
@ -404,6 +455,7 @@ class TagController(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, namespace, tag_name):
"""Create a tag.
@ -416,8 +468,9 @@ class TagController(object):
resp, body = self.http_client.post(url)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_generator()
def create_multiple(self, namespace, **kwargs):
"""Create the list of tags.
@ -440,7 +493,7 @@ class TagController(object):
resp, body = self.http_client.post(url, data=tags)
body.pop('self', None)
for tag in body['tags']:
yield self.model(tag)
yield self.model(tag), resp
def update(self, namespace, tag_name, **kwargs):
"""Update a tag.
@ -464,17 +517,28 @@ class TagController(object):
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name)
self.http_client.put(url, data=tag)
# Pass the original wrapped value to http client.
resp, _ = self.http_client.put(url, data=tag.wrapped)
# Get request id from `put` request so it can be passed to the
# following `get` call
req_id_hdr = {
'x-openstack-request-id': utils._extract_request_id(resp)}
return self.get(namespace, tag.name)
return self._get(namespace, tag.name, req_id_hdr)
def get(self, namespace, tag_name):
return self._get(namespace, tag_name)
@utils.add_req_id_to_object()
def _get(self, namespace, tag_name, header=None):
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name)
resp, body = self.http_client.get(url)
header = header or {}
resp, body = self.http_client.get(url, headers=header)
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata tags.
@ -484,15 +548,19 @@ class TagController(object):
resp, body = self.http_client.get(url)
for tag in body['tags']:
yield self.model(tag)
yield self.model(tag), resp
@utils.add_req_id_to_object()
def delete(self, namespace, tag_name):
"""Delete a tag."""
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp
@utils.add_req_id_to_object()
def delete_all(self, namespace):
"""Delete all tags in a namespace."""
url = '/v2/metadefs/namespaces/{0}/tags'.format(namespace)
self.http_client.delete(url)
resp, body = self.http_client.delete(url)
return (resp, body), resp

View File

@ -38,6 +38,7 @@ class Controller(object):
return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_generator()
def list(self, **kwargs):
"""Retrieve a listing of Task objects.
@ -48,14 +49,14 @@ class Controller(object):
def paginate(url):
resp, body = self.http_client.get(url)
for task in body['tasks']:
yield task
yield task, resp
try:
next_url = body['next']
except KeyError:
return
else:
for task in paginate(next_url):
yield task
for task, resp in paginate(next_url):
yield task, resp
filters = kwargs.get('filters', {})
@ -88,12 +89,13 @@ class Controller(object):
filters[param] = encodeutils.safe_encode(value)
url = '/v2/tasks?%s' % six.moves.urllib.parse.urlencode(filters)
for task in paginate(url):
for task, resp in paginate(url):
# NOTE(flwang): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict
task.pop('self', None)
yield self.model(**task)
yield self.model(**task), resp
@utils.add_req_id_to_object()
def get(self, task_id):
"""Get a task based on given task id."""
url = '/v2/tasks/%s' % task_id
@ -101,8 +103,9 @@ class Controller(object):
# NOTE(flwang): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp
@utils.add_req_id_to_object()
def create(self, **kwargs):
"""Create a new task."""
url = '/v2/tasks'
@ -118,4 +121,4 @@ class Controller(object):
# NOTE(flwang): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict
body.pop('self', None)
return self.model(**body)
return self.model(**body), resp

View File

@ -0,0 +1,10 @@
---
features:
- Added support to return "x-openstack-request-id" header in request_ids attribute
for better tracing.
| For ex.
| >>> from glanceclient import Client
| >>> glance = Client('2', endpoint='OS_IMAGE_ENDPOINT', token='OS_AUTH_TOKEN')
| >>> res = glance.images.get('<image_id>')
| >>> res.request_ids

View File

@ -10,3 +10,4 @@ warlock!=1.3.0,<2,>=1.0.1 # Apache-2.0
six>=1.9.0 # MIT
oslo.utils>=3.18.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
wrapt>=1.7.0 # BSD License