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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@
import testtools import testtools
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils from glanceclient.tests import utils
from glanceclient.v2 import image_tags from glanceclient.v2 import image_tags
@ -54,7 +55,8 @@ class TestController(testtools.TestCase):
super(TestController, self).setUp() super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures) self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_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): def test_update_image_tag(self):
image_id = IMAGE image_id = IMAGE

View File

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

View File

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

View File

@ -32,24 +32,29 @@ class Controller(object):
schema = self.schema_client.get('member') schema = self.schema_client.get('member')
return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel) return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel)
@utils.add_req_id_to_generator()
def list(self, image_id): def list(self, image_id):
url = '/v2/images/%s/members' % image_id url = '/v2/images/%s/members' % image_id
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
for member in body['members']: 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): def delete(self, image_id, member_id):
self.http_client.delete('/v2/images/%s/members/%s' % resp, body = self.http_client.delete('/v2/images/%s/members/%s' %
(image_id, member_id)) (image_id, member_id))
return (resp, body), resp
@utils.add_req_id_to_object()
def update(self, image_id, member_id, member_status): def update(self, image_id, member_id, member_status):
url = '/v2/images/%s/members/%s' % (image_id, member_id) url = '/v2/images/%s/members/%s' % (image_id, member_id)
body = {'status': member_status} body = {'status': member_status}
resp, updated_member = self.http_client.put(url, data=body) 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): def create(self, image_id, member_id):
url = '/v2/images/%s/members' % image_id url = '/v2/images/%s/members' % image_id
body = {'member': member_id} body = {'member': member_id}
resp, created_member = self.http_client.post(url, data=body) 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(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def update(self, image_id, tag_value): def update(self, image_id, tag_value):
"""Update an image with the given tag. """Update an image with the given tag.
@ -37,8 +38,10 @@ class Controller(object):
:param tag_value: value of the tag. :param tag_value: value of the tag.
""" """
url = '/v2/images/%s/tags/%s' % (image_id, tag_value) 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): def delete(self, image_id, tag_value):
"""Delete the tag associated with the given image. """Delete the tag associated with the given image.
@ -46,4 +49,5 @@ class Controller(object):
:param tag_value: tag value to be deleted. :param tag_value: tag value to be deleted.
""" """
url = '/v2/images/%s/tags/%s' % (image_id, tag_value) 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) raise exc.HTTPBadRequest(msg)
return sort return sort
@utils.add_req_id_to_generator()
def list(self, **kwargs): def list(self, **kwargs):
"""Retrieve a listing of Image objects. """Retrieve a listing of Image objects.
@ -97,6 +98,7 @@ class Controller(object):
def paginate(url, page_size, limit=None): def paginate(url, page_size, limit=None):
next_url = url next_url = url
req_id_hdr = {}
while True: while True:
if limit and page_size > limit: if limit and page_size > limit:
@ -105,7 +107,12 @@ class Controller(object):
next_url = next_url.replace("limit=%s" % page_size, next_url = next_url.replace("limit=%s" % page_size,
"limit=%s" % limit) "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']: for image in body['images']:
# NOTE(bcwaldon): remove 'self' for now until we have # NOTE(bcwaldon): remove 'self' for now until we have
# an elegant way to pass it into the model constructor # 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. # We do not validate the model when listing.
# This prevents side-effects of injecting invalid # This prevents side-effects of injecting invalid
# schema values via v1. # schema values via v1.
yield self.unvalidated_model(**image) yield self.unvalidated_model(**image), resp
if limit: if limit:
limit -= 1 limit -= 1
if limit <= 0: if limit <= 0:
@ -173,17 +180,23 @@ class Controller(object):
if isinstance(kwargs.get('marker'), six.string_types): if isinstance(kwargs.get('marker'), six.string_types):
url = '%s&marker=%s' % (url, kwargs['marker']) url = '%s&marker=%s' % (url, kwargs['marker'])
for image in paginate(url, page_size, limit): for image, resp in paginate(url, page_size, limit):
yield image 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 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 # NOTE(bcwaldon): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict # way to pass it into the model constructor without conflict
body.pop('self', None) 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): def data(self, image_id, do_checksum=True):
"""Retrieve data of an image. """Retrieve data of an image.
@ -194,7 +207,7 @@ class Controller(object):
url = '/v2/images/%s/file' % image_id url = '/v2/images/%s/file' % image_id
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
if resp.status_code == codes.no_content: if resp.status_code == codes.no_content:
return None return None, resp
checksum = resp.headers.get('content-md5', None) checksum = resp.headers.get('content-md5', None)
content_length = int(resp.headers.get('content-length', 0)) content_length = int(resp.headers.get('content-length', 0))
@ -202,8 +215,9 @@ class Controller(object):
if do_checksum and checksum is not None: if do_checksum and checksum is not None:
body = utils.integrity_iter(body, checksum) 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): def upload(self, image_id, image_data, image_size=None):
"""Upload the data for an image. """Upload the data for an image.
@ -214,13 +228,17 @@ class Controller(object):
url = '/v2/images/%s/file' % image_id url = '/v2/images/%s/file' % image_id
hdrs = {'Content-Type': 'application/octet-stream'} hdrs = {'Content-Type': 'application/octet-stream'}
body = image_data 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): def delete(self, image_id):
"""Delete an image.""" """Delete an image."""
url = '/v2/images/%s' % image_id 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): def create(self, **kwargs):
"""Create an image.""" """Create an image."""
url = '/v2/images' url = '/v2/images'
@ -236,17 +254,21 @@ class Controller(object):
# NOTE(esheffield): remove 'self' for now until we have an elegant # NOTE(esheffield): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict # way to pass it into the model constructor without conflict
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_object()
def deactivate(self, image_id): def deactivate(self, image_id):
"""Deactivate an image.""" """Deactivate an image."""
url = '/v2/images/%s/actions/deactivate' % image_id 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): def reactivate(self, image_id):
"""Reactivate an image.""" """Reactivate an image."""
url = '/v2/images/%s/actions/reactivate' % image_id 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): def update(self, image_id, remove_props=None, **kwargs):
"""Update attributes of an image. """Update attributes of an image.
@ -276,12 +298,16 @@ class Controller(object):
url = '/v2/images/%s' % image_id url = '/v2/images/%s' % image_id
hdrs = {'Content-Type': 'application/openstack-images-v2.1-json-patch'} 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 # 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 # we need to fetch the image again to get a clean history. This is
# an obvious optimization for warlock # 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): def _get_image_with_locations_or_fail(self, image_id):
image = self.get(image_id) image = self.get(image_id)
@ -290,10 +316,13 @@ class Controller(object):
'API access to image locations') 'API access to image locations')
return image return image
@utils.add_req_id_to_object()
def _send_image_update_request(self, image_id, patch_body): def _send_image_update_request(self, image_id, patch_body):
url = '/v2/images/%s' % image_id url = '/v2/images/%s' % image_id
hdrs = {'Content-Type': 'application/openstack-images-v2.1-json-patch'} 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): def add_location(self, image_id, url, metadata):
"""Add a new location entry to an image's list of locations. """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/-', add_patch = [{'op': 'add', 'path': '/locations/-',
'value': {'url': url, 'metadata': metadata}}] 'value': {'url': url, 'metadata': metadata}}]
self._send_image_update_request(image_id, add_patch) response = self._send_image_update_request(image_id, add_patch)
return self.get(image_id) # 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): def delete_locations(self, image_id, url_set):
"""Remove one or more location entries of an image. """Remove one or more location entries of an image.
@ -332,7 +364,7 @@ class Controller(object):
url_indices.sort(reverse=True) url_indices.sort(reverse=True)
patches = [{'op': 'remove', 'path': '/locations/%s' % url_idx} patches = [{'op': 'remove', 'path': '/locations/%s' % url_idx}
for url_idx in url_indices] 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): def update_location(self, image_id, url, metadata):
"""Update an existing location entry in an image's list of locations. """Update an existing location entry in an image's list of locations.
@ -359,6 +391,9 @@ class Controller(object):
patches = [{'op': 'replace', patches = [{'op': 'replace',
'path': '/locations', 'path': '/locations',
'value': list(url_map.values())}] '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(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, **kwargs): def create(self, **kwargs):
"""Create a namespace. """Create a namespace.
@ -50,7 +51,7 @@ class NamespaceController(object):
resp, body = self.http_client.post(url, data=namespace) resp, body = self.http_client.post(url, data=namespace)
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
def update(self, namespace_name, **kwargs): def update(self, namespace_name, **kwargs):
"""Update a namespace. """Update a namespace.
@ -72,23 +73,34 @@ class NamespaceController(object):
del namespace[elem] del namespace[elem]
url = '/v2/metadefs/namespaces/{0}'.format(namespace_name) url = '/v2/metadefs/namespaces/{0}'.format(namespace_name)
self.http_client.put(url, data=namespace) # Pass the original wrapped value to http client.
resp, _ = self.http_client.put(url, data=namespace.wrapped)
return self.get(namespace.namespace) # 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): 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.""" """Get one namespace."""
query_params = parse.urlencode(kwargs) query_params = parse.urlencode(kwargs)
if kwargs: if kwargs:
query_params = '?%s' % query_params query_params = '?%s' % query_params
url = '/v2/metadefs/namespaces/{0}{1}'.format(namespace, 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 # NOTE(bcwaldon): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict # way to pass it into the model constructor without conflict
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, **kwargs): def list(self, **kwargs):
"""Retrieve a listing of Namespace objects. """Retrieve a listing of Namespace objects.
@ -117,7 +129,7 @@ class NamespaceController(object):
# an elegant way to pass it into the model constructor # an elegant way to pass it into the model constructor
# without conflict. # without conflict.
namespace.pop('self', None) namespace.pop('self', None)
yield self.model(**namespace) yield self.model(**namespace), resp
# NOTE(zhiyan): In order to resolve the performance issue # NOTE(zhiyan): In order to resolve the performance issue
# of JSON schema validation for image listing case, we # of JSON schema validation for image listing case, we
# don't validate each image entry but do it only on first # don't validate each image entry but do it only on first
@ -132,8 +144,8 @@ class NamespaceController(object):
except KeyError: except KeyError:
return return
else: else:
for namespace in paginate(next_url): for namespace, resp in paginate(next_url):
yield namespace yield namespace, resp
filters = kwargs.get('filters', {}) filters = kwargs.get('filters', {})
filters = {} if filters is None else filters filters = {} if filters is None else filters
@ -170,13 +182,15 @@ class NamespaceController(object):
url = '/v2/metadefs/namespaces?%s' % parse.urlencode(filters) url = '/v2/metadefs/namespaces?%s' % parse.urlencode(filters)
for namespace in paginate(url): for namespace, resp in paginate(url):
yield namespace yield namespace, resp
@utils.add_req_id_to_object()
def delete(self, namespace): def delete(self, namespace):
"""Delete a namespace.""" """Delete a namespace."""
url = '/v2/metadefs/namespaces/{0}'.format(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): class ResourceTypeController(object):
@ -190,6 +204,7 @@ class ResourceTypeController(object):
return warlock.model_factory(schema.raw(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def associate(self, namespace, **kwargs): def associate(self, namespace, **kwargs):
"""Associate a resource type with a namespace.""" """Associate a resource type with a namespace."""
try: try:
@ -201,14 +216,17 @@ class ResourceTypeController(object):
res_type) res_type)
resp, body = self.http_client.post(url, data=res_type) resp, body = self.http_client.post(url, data=res_type)
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_object()
def deassociate(self, namespace, resource): def deassociate(self, namespace, resource):
"""Deassociate a resource type with a namespace.""" """Deassociate a resource type with a namespace."""
url = '/v2/metadefs/namespaces/{0}/resource_types/{1}'. \ url = '/v2/metadefs/namespaces/{0}/resource_types/{1}'. \
format(namespace, resource) 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): def list(self):
"""Retrieve a listing of available resource types. """Retrieve a listing of available resource types.
@ -218,14 +236,15 @@ class ResourceTypeController(object):
url = '/v2/metadefs/resource_types' url = '/v2/metadefs/resource_types'
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
for resource_type in body['resource_types']: 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): def get(self, namespace):
url = '/v2/metadefs/namespaces/{0}/resource_types'.format(namespace) url = '/v2/metadefs/namespaces/{0}/resource_types'.format(namespace)
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
body.pop('self', None) body.pop('self', None)
for resource_type in body['resource_type_associations']: for resource_type in body['resource_type_associations']:
yield self.model(**resource_type) yield self.model(**resource_type), resp
class PropertyController(object): class PropertyController(object):
@ -239,6 +258,7 @@ class PropertyController(object):
return warlock.model_factory(schema.raw(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, namespace, **kwargs): def create(self, namespace, **kwargs):
"""Create a property. """Create a property.
@ -254,7 +274,7 @@ class PropertyController(object):
resp, body = self.http_client.post(url, data=prop) resp, body = self.http_client.post(url, data=prop)
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
def update(self, namespace, prop_name, **kwargs): def update(self, namespace, prop_name, **kwargs):
"""Update a property. """Update a property.
@ -272,18 +292,29 @@ class PropertyController(object):
url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace, url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace,
prop_name) 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): 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, url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace,
prop_name) 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.pop('self', None)
body['name'] = prop_name body['name'] = prop_name
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, namespace, **kwargs): def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata properties. """Retrieve a listing of metadata properties.
@ -295,18 +326,22 @@ class PropertyController(object):
for key, value in body['properties'].items(): for key, value in body['properties'].items():
value['name'] = key value['name'] = key
yield self.model(value) yield self.model(value), resp
@utils.add_req_id_to_object()
def delete(self, namespace, prop_name): def delete(self, namespace, prop_name):
"""Delete a property.""" """Delete a property."""
url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace, url = '/v2/metadefs/namespaces/{0}/properties/{1}'.format(namespace,
prop_name) 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): def delete_all(self, namespace):
"""Delete all properties in a namespace.""" """Delete all properties in a namespace."""
url = '/v2/metadefs/namespaces/{0}/properties'.format(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): class ObjectController(object):
@ -320,6 +355,7 @@ class ObjectController(object):
return warlock.model_factory(schema.raw(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, namespace, **kwargs): def create(self, namespace, **kwargs):
"""Create an object. """Create an object.
@ -335,7 +371,7 @@ class ObjectController(object):
resp, body = self.http_client.post(url, data=obj) resp, body = self.http_client.post(url, data=obj)
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
def update(self, namespace, object_name, **kwargs): def update(self, namespace, object_name, **kwargs):
"""Update an object. """Update an object.
@ -359,17 +395,28 @@ class ObjectController(object):
url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace, url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace,
object_name) 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): 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, url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace,
object_name) 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) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, namespace, **kwargs): def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata objects. """Retrieve a listing of metadata objects.
@ -379,18 +426,22 @@ class ObjectController(object):
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
for obj in body['objects']: 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): def delete(self, namespace, object_name):
"""Delete an object.""" """Delete an object."""
url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace, url = '/v2/metadefs/namespaces/{0}/objects/{1}'.format(namespace,
object_name) 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): def delete_all(self, namespace):
"""Delete all objects in a namespace.""" """Delete all objects in a namespace."""
url = '/v2/metadefs/namespaces/{0}/objects'.format(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): class TagController(object):
@ -404,6 +455,7 @@ class TagController(object):
return warlock.model_factory(schema.raw(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_object()
def create(self, namespace, tag_name): def create(self, namespace, tag_name):
"""Create a tag. """Create a tag.
@ -416,8 +468,9 @@ class TagController(object):
resp, body = self.http_client.post(url) resp, body = self.http_client.post(url)
body.pop('self', None) 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): def create_multiple(self, namespace, **kwargs):
"""Create the list of tags. """Create the list of tags.
@ -440,7 +493,7 @@ class TagController(object):
resp, body = self.http_client.post(url, data=tags) resp, body = self.http_client.post(url, data=tags)
body.pop('self', None) body.pop('self', None)
for tag in body['tags']: for tag in body['tags']:
yield self.model(tag) yield self.model(tag), resp
def update(self, namespace, tag_name, **kwargs): def update(self, namespace, tag_name, **kwargs):
"""Update a tag. """Update a tag.
@ -464,17 +517,28 @@ class TagController(object):
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace, url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name) 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): 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, url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name) 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) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_generator()
def list(self, namespace, **kwargs): def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata tags. """Retrieve a listing of metadata tags.
@ -484,15 +548,19 @@ class TagController(object):
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
for tag in body['tags']: 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): def delete(self, namespace, tag_name):
"""Delete a tag.""" """Delete a tag."""
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace, url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name) 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): def delete_all(self, namespace):
"""Delete all tags in a namespace.""" """Delete all tags in a namespace."""
url = '/v2/metadefs/namespaces/{0}/tags'.format(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(), return warlock.model_factory(schema.raw(),
base_class=schemas.SchemaBasedModel) base_class=schemas.SchemaBasedModel)
@utils.add_req_id_to_generator()
def list(self, **kwargs): def list(self, **kwargs):
"""Retrieve a listing of Task objects. """Retrieve a listing of Task objects.
@ -48,14 +49,14 @@ class Controller(object):
def paginate(url): def paginate(url):
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
for task in body['tasks']: for task in body['tasks']:
yield task yield task, resp
try: try:
next_url = body['next'] next_url = body['next']
except KeyError: except KeyError:
return return
else: else:
for task in paginate(next_url): for task, resp in paginate(next_url):
yield task yield task, resp
filters = kwargs.get('filters', {}) filters = kwargs.get('filters', {})
@ -88,12 +89,13 @@ class Controller(object):
filters[param] = encodeutils.safe_encode(value) filters[param] = encodeutils.safe_encode(value)
url = '/v2/tasks?%s' % six.moves.urllib.parse.urlencode(filters) 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 # NOTE(flwang): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict # way to pass it into the model constructor without conflict
task.pop('self', None) task.pop('self', None)
yield self.model(**task) yield self.model(**task), resp
@utils.add_req_id_to_object()
def get(self, task_id): def get(self, task_id):
"""Get a task based on given task id.""" """Get a task based on given task id."""
url = '/v2/tasks/%s' % 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 # NOTE(flwang): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict # way to pass it into the model constructor without conflict
body.pop('self', None) body.pop('self', None)
return self.model(**body) return self.model(**body), resp
@utils.add_req_id_to_object()
def create(self, **kwargs): def create(self, **kwargs):
"""Create a new task.""" """Create a new task."""
url = '/v2/tasks' url = '/v2/tasks'
@ -118,4 +121,4 @@ class Controller(object):
# NOTE(flwang): remove 'self' for now until we have an elegant # NOTE(flwang): remove 'self' for now until we have an elegant
# way to pass it into the model constructor without conflict # way to pass it into the model constructor without conflict
body.pop('self', None) 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 six>=1.9.0 # MIT
oslo.utils>=3.18.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0
wrapt>=1.7.0 # BSD License