summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Bobrov <bbobrov@mirantis.com>2015-11-26 19:10:10 +0300
committerBoris Bobrov <bbobrov@mirantis.com>2015-12-28 14:54:00 +0000
commitc28d40814962b3a8ccb81e5e7d7f832c8f0a3c9a (patch)
tree750b05cf26353d7c7d3defa45048994c445fb6fc
parentf8c47a1aa0c12e2fb374e66f8787287069acbdd9 (diff)
downloadpython-keystoneclient-c28d40814962b3a8ccb81e5e7d7f832c8f0a3c9a.tar.gz
Support `truncated` flag returned by keystone
Wrap a list of objects into custom class with additional attributes. This is wanted by Horizon, that wants to know that the list returned from keystone is not full and that more strict filters need to be applied. Change-Id: Icfabfd055aed1648dc4130b03ec3dbf9bad4e45a Closes-Bug: 1520244
-rw-r--r--keystoneclient/base.py22
-rw-r--r--keystoneclient/tests/unit/v3/utils.py41
2 files changed, 55 insertions, 8 deletions
diff --git a/keystoneclient/base.py b/keystoneclient/base.py
index a03ecee..b61305d 100644
--- a/keystoneclient/base.py
+++ b/keystoneclient/base.py
@@ -20,6 +20,7 @@ Base utilities to build API operation managers and objects on top of.
"""
import abc
+import collections
import copy
import functools
import warnings
@@ -76,6 +77,23 @@ def filter_kwargs(f):
return func
+class KeystoneReturnedList(collections.Sequence):
+ """A list of entities with additional attributes."""
+
+ def __init__(self, collection, truncated=False):
+ self.collection = collection
+ self.truncated = truncated
+
+ def __getitem__(self, i):
+ return self.collection[i]
+
+ def __len__(self):
+ return len(self.collection)
+
+ def sort(self, *args, **kwargs):
+ return self.collection.sort(*args, **kwargs)
+
+
class Manager(object):
"""Basic manager type providing common operations.
@@ -127,6 +145,7 @@ class Manager(object):
obj_class = self.resource_class
data = body[response_key]
+ truncated = body.get('truncated', False)
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
@@ -134,7 +153,8 @@ class Manager(object):
except (KeyError, TypeError):
pass
- return [obj_class(self, res, loaded=True) for res in data if res]
+ objects = [obj_class(self, res, loaded=True) for res in data if res]
+ return KeystoneReturnedList(objects, truncated=truncated)
def _get(self, url, response_key, **kwargs):
"""Get an object from collection.
diff --git a/keystoneclient/tests/unit/v3/utils.py b/keystoneclient/tests/unit/v3/utils.py
index fcb546e..0e88a55 100644
--- a/keystoneclient/tests/unit/v3/utils.py
+++ b/keystoneclient/tests/unit/v3/utils.py
@@ -195,11 +195,16 @@ class CrudTests(object):
kwargs.setdefault(uuid.uuid4().hex, uuid.uuid4().hex)
return kwargs
- def encode(self, entity):
+ def encode(self, entity, truncated=None):
+ encoded = {}
+ if truncated is not None:
+ encoded['truncated'] = truncated
if isinstance(entity, dict):
- return {self.key: entity}
+ encoded[self.key] = entity
+ return encoded
if isinstance(entity, list):
- return {self.collection_key: entity}
+ encoded[self.collection_key] = entity
+ return encoded
raise NotImplementedError('Are you sure you want to encode that?')
def stub_entity(self, method, parts=None, entity=None, id=None, **kwargs):
@@ -290,14 +295,22 @@ class CrudTests(object):
self.assertRaises(TypeError, self.manager.list, **filter_kwargs)
- def test_list(self, ref_list=None, expected_path=None,
- expected_query=None, **filter_kwargs):
+ def _test_list(self, ref_list=None, expected_path=None,
+ expected_query=None, truncated=None, **filter_kwargs):
ref_list = ref_list or [self.new_ref(), self.new_ref()]
expected_path = self._get_expected_path(expected_path)
- self.requests_mock.get(urlparse.urljoin(self.TEST_URL, expected_path),
- json=self.encode(ref_list))
+ # We want to catch all cases: when `truncated` is not returned by the
+ # server, when it's False and when it's True.
+ # Attribute `truncated` of the returned list-like object should exist
+ # in all these cases. It should be False if the server returned a list
+ # without the flag.
+ expected_truncated = False
+ if truncated:
+ expected_truncated = truncated
+ self.requests_mock.get(urlparse.urljoin(self.TEST_URL, expected_path),
+ json=self.encode(ref_list, truncated=truncated))
returned_list = self.manager.list(**filter_kwargs)
self.assertEqual(len(ref_list), len(returned_list))
[self.assertIsInstance(r, self.model) for r in returned_list]
@@ -316,6 +329,20 @@ class CrudTests(object):
for key in qs_args:
self.assertIn(key, qs_args_expected)
+ self.assertEqual(expected_truncated, returned_list.truncated)
+
+ def test_list(self, ref_list=None, expected_path=None,
+ expected_query=None, **filter_kwargs):
+ # test simple list, without any truncation
+ self._test_list(ref_list, expected_path, expected_query,
+ **filter_kwargs)
+ # test when a server returned a list with truncated=False
+ self._test_list(ref_list, expected_path, expected_query,
+ truncated=False, **filter_kwargs)
+ # test when a server returned a list with truncated=True
+ self._test_list(ref_list, expected_path, expected_query,
+ truncated=True, **filter_kwargs)
+
def test_list_params(self):
ref_list = [self.new_ref()]
filter_kwargs = {uuid.uuid4().hex: uuid.uuid4().hex}