From 9a4d8580e890c3c55c2d02904f5f6983bd06bd1c Mon Sep 17 00:00:00 2001 From: Travis Tripp Date: Wed, 15 Oct 2014 17:46:54 -0600 Subject: [PATCH] Support Pagination for namespace list The rest api metadefs/namespaces supports pagination using the parameters of limit, marker, sort_dir, & sort_key. However, the glance client isn't passing those parameters through (they come in as kwargs). This is preventing pagination from working properly in horizon. This is affecting Horizon support: https://review.openstack.org/#/c/104063/ Change-Id: Ib349cf3a3a437eb1711f350b37d0bd0e7d01330a Closes-Bug: 1381816 --- glanceclient/v2/metadefs.py | 34 ++++++++++++- tests/v2/test_metadefs_namespaces.py | 75 +++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/glanceclient/v2/metadefs.py b/glanceclient/v2/metadefs.py index 3dc7efc2..8d9512bc 100644 --- a/glanceclient/v2/metadefs.py +++ b/glanceclient/v2/metadefs.py @@ -22,6 +22,8 @@ from glanceclient.openstack.common import strutils from glanceclient.v2 import schemas DEFAULT_PAGE_SIZE = 20 +SORT_DIR_VALUES = ('asc', 'desc') +SORT_KEY_VALUES = ('created_at', 'namespace') class NamespaceController(object): @@ -88,8 +90,17 @@ class NamespaceController(object): def list(self, **kwargs): """Retrieve a listing of Namespace objects - - :param page_size: Number of namespaces to request in each request + :param page_size: Number of items to request in each paginated request + :param limit: Use to request a specific page size. Expect a response + to a limited request to return between zero and limit + items. + :param marker: Specifies the namespace of the last-seen namespace. + The typical pattern of limit and marker is to make an + initial limited request and then to use the last + namespace from the response as the marker parameter + in a subsequent limited request. + :param sort_key: The field to sort on (for example, 'created_at') + :param sort_dir: The direction to sort ('asc' or 'desc') :returns generator over list of Namespaces """ @@ -129,6 +140,25 @@ class NamespaceController(object): else: filters['limit'] = kwargs['page_size'] + if 'marker' in kwargs: + filters['marker'] = kwargs['marker'] + + sort_key = kwargs.get('sort_key') + if sort_key is not None: + if sort_key in SORT_KEY_VALUES: + filters['sort_key'] = sort_key + else: + raise ValueError('sort_key must be one of the following: %s.' + % ', '.join(SORT_KEY_VALUES)) + + sort_dir = kwargs.get('sort_dir') + if sort_dir is not None: + if sort_dir in SORT_DIR_VALUES: + filters['sort_dir'] = sort_dir + else: + raise ValueError('sort_dir must be one of the following: %s.' + % ', '.join(SORT_DIR_VALUES)) + for param, value in six.iteritems(filters): if isinstance(value, list): filters[param] = strutils.safe_encode(','.join(value)) diff --git a/tests/v2/test_metadefs_namespaces.py b/tests/v2/test_metadefs_namespaces.py index 4267dd95..b03dcd15 100644 --- a/tests/v2/test_metadefs_namespaces.py +++ b/tests/v2/test_metadefs_namespaces.py @@ -89,7 +89,7 @@ data_fixtures = { "GET": ( {}, { - "first": "/v2/metadefs/namespaces?limit=1", + "first": "/v2/metadefs/namespaces?limit=2", "namespaces": [ _get_namespace_fixture(NAMESPACE8), ], @@ -97,6 +97,43 @@ data_fixtures = { } ) }, + "/v2/metadefs/namespaces?limit=2&marker=%s" % NAMESPACE6: { + "GET": ( + {}, + { + "first": "/v2/metadefs/namespaces?limit=2", + "namespaces": [ + _get_namespace_fixture(NAMESPACE7), + _get_namespace_fixture(NAMESPACE8), + ], + "schema": "/v2/schemas/metadefs/namespaces" + } + ) + }, + "/v2/metadefs/namespaces?limit=20&sort_dir=asc": { + "GET": ( + {}, + { + "first": "/v2/metadefs/namespaces?limit=1", + "namespaces": [ + _get_namespace_fixture(NAMESPACE1), + ], + "schema": "/v2/schemas/metadefs/namespaces" + } + ) + }, + "/v2/metadefs/namespaces?limit=20&sort_key=created_at": { + "GET": ( + {}, + { + "first": "/v2/metadefs/namespaces?limit=1", + "namespaces": [ + _get_namespace_fixture(NAMESPACE1), + ], + "schema": "/v2/schemas/metadefs/namespaces" + } + ) + }, "/v2/metadefs/namespaces?limit=20&resource_types=%s" % RESOURCE_TYPE1: { "GET": ( {}, @@ -269,7 +306,7 @@ data_fixtures = { "updated_at": "2014-08-14T09:07:06Z", } ), - } + }, } schema_fixtures = { @@ -521,6 +558,40 @@ class TestNamespaceController(testtools.TestCase): self.assertEqual(NAMESPACE7, namespaces[0]['namespace']) self.assertEqual(NAMESPACE8, namespaces[1]['namespace']) + def test_list_with_limit_greater_than_page_size(self): + namespaces = list(self.controller.list(page_size=1, limit=2)) + self.assertEqual(2, len(namespaces)) + self.assertEqual(NAMESPACE7, namespaces[0]['namespace']) + self.assertEqual(NAMESPACE8, namespaces[1]['namespace']) + + def test_list_with_marker(self): + namespaces = list(self.controller.list(marker=NAMESPACE6, page_size=2)) + self.assertEqual(2, len(namespaces)) + self.assertEqual(NAMESPACE7, namespaces[0]['namespace']) + self.assertEqual(NAMESPACE8, namespaces[1]['namespace']) + + def test_list_with_sort_dir(self): + namespaces = list(self.controller.list(sort_dir='asc', limit=1)) + self.assertEqual(1, len(namespaces)) + self.assertEqual(NAMESPACE1, namespaces[0]['namespace']) + + def test_list_with_sort_dir_invalid(self): + # NOTE(TravT): The clients work by returning an iterator. + # Invoking the iterator is what actually executes the logic. + ns_iterator = self.controller.list(sort_dir='foo') + self.assertRaises(ValueError, next, ns_iterator) + + def test_list_with_sort_key(self): + namespaces = list(self.controller.list(sort_key='created_at', limit=1)) + self.assertEqual(1, len(namespaces)) + self.assertEqual(NAMESPACE1, namespaces[0]['namespace']) + + def test_list_with_sort_key_invalid(self): + # NOTE(TravT): The clients work by returning an iterator. + # Invoking the iterator is what actually executes the logic. + ns_iterator = self.controller.list(sort_key='foo') + self.assertRaises(ValueError, next, ns_iterator) + def test_list_namespaces_with_one_resource_type_filter(self): namespaces = list(self.controller.list( filters={