diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py index a1a15112e5..795435101f 100644 --- a/glance/api/v2/images.py +++ b/glance/api/v2/images.py @@ -88,7 +88,7 @@ class ImagesController(object): return image - def index(self, req, marker=None, limit=None, sort_key='created_at', + def index(self, req, marker=None, limit=None, sort_key=['created_at'], sort_dir='desc', filters=None, member_status='accepted'): result = {} if filters is None: @@ -102,7 +102,8 @@ class ImagesController(object): image_repo = self.gateway.get_repo(req.context) try: images = image_repo.list(marker=marker, limit=limit, - sort_key=sort_key, sort_dir=sort_dir, + sort_key=sort_key, + sort_dir=sort_dir, filters=filters, member_status=member_status) if len(images) != 0 and len(images) == limit: @@ -590,9 +591,15 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer): while 'tag' in params: tags.append(params.pop('tag').strip()) + # NOTE (mfedosin) Do the same with sorting keys + # v2/images?sort_key=name&sort_key=size + + sort_keys = [] + while 'sort_key' in params: + sort_keys.append(self._validate_sort_key( + params.pop('sort_key').strip())) + query_params = { - 'sort_key': self._validate_sort_key( - params.pop('sort_key', 'created_at')), 'sort_dir': self._validate_sort_dir(sort_dir), 'filters': self._get_filters(params), 'member_status': self._validate_member_status(member_status), @@ -607,6 +614,13 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer): if tags: query_params['filters']['tags'] = tags + # NOTE(mfedosin): param is still called sort_key, instead of sort_keys + # It's done because in v1 it's still a single value. + if sort_keys: + query_params['sort_key'] = sort_keys + else: + query_params['sort_key'] = ['created_at'] + return query_params diff --git a/glance/db/__init__.py b/glance/db/__init__.py index 16b7ddaa00..c1ccbe4ea0 100644 --- a/glance/db/__init__.py +++ b/glance/db/__init__.py @@ -75,7 +75,7 @@ class ImageRepo(object): image = self._format_image_from_db(db_api_image, tags) return ImageProxy(image, self.context, self.db_api) - def list(self, marker=None, limit=None, sort_key='created_at', + def list(self, marker=None, limit=None, sort_key=['created_at'], sort_dir='desc', filters=None, member_status='accepted'): db_api_images = self.db_api.image_get_all( self.context, filters=filters, marker=marker, limit=limit, diff --git a/glance/db/registry/api.py b/glance/db/registry/api.py index dc947d5341..6ec2785685 100644 --- a/glance/db/registry/api.py +++ b/glance/db/registry/api.py @@ -118,7 +118,7 @@ def is_image_visible(context, image, status=None): @_get_client def image_get_all(client, filters=None, marker=None, limit=None, - sort_key='created_at', sort_dir='desc', + sort_key=['created_at'], sort_dir='desc', member_status='accepted', is_public=None, admin_as_user=False, return_tag=False): """ diff --git a/glance/db/simple/api.py b/glance/db/simple/api.py index 2b3ddef486..2c26995f45 100644 --- a/glance/db/simple/api.py +++ b/glance/db/simple/api.py @@ -15,6 +15,7 @@ # under the License. import copy +import datetime import functools import uuid @@ -337,13 +338,29 @@ def _do_pagination(context, images, marker, limit, show_deleted, def _sort_images(images, sort_key, sort_dir): - reverse = False - if images and not (sort_key in images[0]): - raise exception.InvalidSortKey() - keyfn = lambda x: (x[sort_key] if x[sort_key] is not None else '', - x['created_at'], x['id']) reverse = sort_dir == 'desc' - images.sort(key=keyfn, reverse=reverse) + + for key in ['created_at', 'id']: + if key not in sort_key: + sort_key.append(key) + + for key in sort_key: + if images and not (key in images[0]): + raise exception.InvalidSortKey() + + def sort_func(element): + keys = [] + for key in sort_key: + if element[key] is not None: + keys.append(element[key]) + else: + if key in ['created_at', 'updated_at', 'deleted_at']: + keys.append(datetime.datetime.min) + else: + keys.append('') + return tuple(keys) + + images.sort(key=sort_func, reverse=reverse) return images @@ -375,7 +392,7 @@ def image_get(context, image_id, session=None, force_show_deleted=False): @log_call def image_get_all(context, filters=None, marker=None, limit=None, - sort_key='created_at', sort_dir='desc', + sort_key=['created_at'], sort_dir='desc', member_status='accepted', is_public=None, admin_as_user=False, return_tag=False): filters = filters or {} @@ -975,7 +992,6 @@ def _sort_tasks(tasks, sort_key, sort_dir): x['created_at'], x['id']) reverse = sort_dir == 'desc' tasks.sort(key=keyfn, reverse=reverse) - return tasks diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py index 53a4443e68..ce9100369e 100644 --- a/glance/db/sqlalchemy/api.py +++ b/glance/db/sqlalchemy/api.py @@ -524,7 +524,7 @@ def _select_images_query(context, image_conditions, admin_as_user, def image_get_all(context, filters=None, marker=None, limit=None, - sort_key='created_at', sort_dir='desc', + sort_key=['created_at'], sort_dir='desc', member_status='accepted', is_public=None, admin_as_user=False, return_tag=False): """ @@ -535,7 +535,7 @@ def image_get_all(context, filters=None, marker=None, limit=None, filters on the image properties attribute :param marker: image id after which to start page :param limit: maximum number of images to return - :param sort_key: image attribute by which results should be sorted + :param sort_key: list of image attributes by which results should be sorted :param sort_dir: direction in which results should be sorted (asc, desc) :param member_status: only return shared images that have this membership status @@ -585,11 +585,12 @@ def image_get_all(context, filters=None, marker=None, limit=None, marker, force_show_deleted=showing_deleted) - sort_keys = ['created_at', 'id'] - sort_keys.insert(0, sort_key) if sort_key not in sort_keys else sort_keys + for key in ['created_at', 'id']: + if key not in sort_key: + sort_key.append(key) query = _paginate_query(query, models.Image, limit, - sort_keys, + sort_key, marker=marker_image, sort_dir=sort_dir) diff --git a/glance/registry/api/v1/images.py b/glance/registry/api/v1/images.py index 1bbfe01c68..107b1eb95c 100644 --- a/glance/registry/api/v1/images.py +++ b/glance/registry/api/v1/images.py @@ -195,7 +195,7 @@ class Controller(object): params = { 'filters': self._get_filters(req), 'limit': self._get_limit(req), - 'sort_key': self._get_sort_key(req), + 'sort_key': [self._get_sort_key(req)], 'sort_dir': self._get_sort_dir(req), 'marker': self._get_marker(req), } @@ -283,7 +283,7 @@ class Controller(object): def _get_sort_key(self, req): """Parse a sort key query param from the request object.""" - sort_key = req.params.get('sort_key', None) + sort_key = req.params.get('sort_key', 'created_at') if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS: _keys = ', '.join(SUPPORTED_SORT_KEYS) msg = _("Unsupported sort_key. Acceptable values: %s") % (_keys,) diff --git a/glance/tests/functional/db/base.py b/glance/tests/functional/db/base.py index 8099eb174d..79118b4f4b 100644 --- a/glance/tests/functional/db/base.py +++ b/glance/tests/functional/db/base.py @@ -538,7 +538,7 @@ class DriverTests(object): 'owner': TENANT1}) images = self.db_api.image_get_all(ctxt1, marker=UUIDX, - sort_key='name', + sort_key=['name'], sort_dir='desc') image_ids = [image['id'] for image in images] expected = [] @@ -560,7 +560,7 @@ class DriverTests(object): 'owner': TENANT1}) images = self.db_api.image_get_all(ctxt1, marker=UUIDX, - sort_key='disk_format', + sort_key=['disk_format'], sort_dir='desc') image_ids = [image['id'] for image in images] expected = [] @@ -582,7 +582,7 @@ class DriverTests(object): 'owner': TENANT1}) images = self.db_api.image_get_all(ctxt1, marker=UUIDX, - sort_key='container_format', + sort_key=['container_format'], sort_dir='desc') image_ids = [image['id'] for image in images] expected = [] @@ -604,7 +604,7 @@ class DriverTests(object): 'owner': TENANT1}) images = self.db_api.image_get_all(ctxt1, marker=UUIDX, - sort_key='name', + sort_key=['name'], sort_dir='asc') image_ids = [image['id'] for image in images] expected = [UUID3, UUID2, UUID1] @@ -626,7 +626,7 @@ class DriverTests(object): 'owner': TENANT1}) images = self.db_api.image_get_all(ctxt1, marker=UUIDX, - sort_key='disk_format', + sort_key=['disk_format'], sort_dir='asc') image_ids = [image['id'] for image in images] expected = [UUID3, UUID2, UUID1] @@ -648,7 +648,7 @@ class DriverTests(object): 'owner': TENANT1}) images = self.db_api.image_get_all(ctxt1, marker=UUIDX, - sort_key='container_format', + sort_key=['container_format'], sort_dir='asc') image_ids = [image['id'] for image in images] expected = [UUID3, UUID2, UUID1] @@ -814,7 +814,7 @@ class DriverTests(object): def test_image_get_all_invalid_sort_key(self): self.assertRaises(exception.InvalidSortKey, self.db_api.image_get_all, - self.context, sort_key='blah') + self.context, sort_key=['blah']) def test_image_get_all_limit_marker(self): images = self.db_api.image_get_all(self.context, limit=2) diff --git a/glance/tests/functional/db/test_sqlalchemy.py b/glance/tests/functional/db/test_sqlalchemy.py index 8c8081bd28..56c8522e4a 100644 --- a/glance/tests/functional/db/test_sqlalchemy.py +++ b/glance/tests/functional/db/test_sqlalchemy.py @@ -113,7 +113,7 @@ class TestSqlAlchemyDBDataIntegrity(base.TestDriver): self.stubs.Set(self.db_api, '_paginate_query', fake_paginate_query) - self.db_api.image_get_all(self.context, sort_key='created_at') + self.db_api.image_get_all(self.context, sort_key=['created_at']) def test_paginate_non_redundant_sort_keys(self): original_method = self.db_api._paginate_query @@ -126,7 +126,7 @@ class TestSqlAlchemyDBDataIntegrity(base.TestDriver): self.stubs.Set(self.db_api, '_paginate_query', fake_paginate_query) - self.db_api.image_get_all(self.context, sort_key='name') + self.db_api.image_get_all(self.context, sort_key=['name']) class TestSqlAlchemyTask(base.TaskTests): diff --git a/glance/tests/unit/test_db.py b/glance/tests/unit/test_db.py index 33acb945d4..929e42def8 100644 --- a/glance/tests/unit/test_db.py +++ b/glance/tests/unit/test_db.py @@ -302,10 +302,29 @@ class TestImageRepo(test_utils.BaseTestCase): self.assertEqual(set([UUID1, UUID3]), image_ids) def test_sorted_list(self): - images = self.image_repo.list(sort_key='size', sort_dir='asc') + images = self.image_repo.list(sort_key=['size'], sort_dir='asc') image_ids = [i.image_id for i in images] self.assertEqual([UUID1, UUID2, UUID3], image_ids) + def test_sorted_list_with_multiple_keys(self): + temp_id = 'd80a1a6c-bd1f-41c5-90ee-81afedb1d58d' + image = _db_fixture(temp_id, owner=TENANT1, checksum=CHECKSUM, + name='1', size=1024, + is_public=True, status='active', + locations=[{'url': UUID1_LOCATION, + 'metadata': UUID1_LOCATION_METADATA, + 'status': 'active'}]) + self.db.image_create(None, image) + images = self.image_repo.list(sort_key=['name', 'size'], + sort_dir='asc') + image_ids = [i.image_id for i in images] + self.assertEqual([UUID1, temp_id, UUID2, UUID3], image_ids) + + images = self.image_repo.list(sort_key=['size', 'name'], + sort_dir='asc') + image_ids = [i.image_id for i in images] + self.assertEqual([UUID1, UUID2, temp_id, UUID3], image_ids) + def test_add_image(self): image = self.image_factory.new_image(name='added image') self.assertEqual(image.updated_at, image.created_at) diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py index eda89e1525..8c03ec9cf5 100644 --- a/glance/tests/unit/v2/test_images_resource.py +++ b/glance/tests/unit/v2/test_images_resource.py @@ -214,7 +214,8 @@ class TestImagesController(base.IsolatedUnitTest): self.config(limit_param_default=1, api_limit_max=3) request = unit_test_utils.get_fake_request() output = self.controller.index(request, marker=UUID3, limit=1, - sort_key='created_at', sort_dir='desc') + sort_key=['created_at'], + sort_dir='desc') self.assertEqual(1, len(output['images'])) actual = set([image.image_id for image in output['images']]) expected = set([UUID2]) @@ -423,7 +424,20 @@ class TestImagesController(base.IsolatedUnitTest): def test_index_with_sort_key(self): path = '/images' request = unit_test_utils.get_fake_request(path) - output = self.controller.index(request, sort_key='created_at', limit=3) + output = self.controller.index(request, sort_key=['created_at'], + limit=3) + actual = [image.image_id for image in output['images']] + self.assertEqual(3, len(actual)) + self.assertEqual(UUID3, actual[0]) + self.assertEqual(UUID2, actual[1]) + self.assertEqual(UUID1, actual[2]) + + def test_index_with_multiple_sort_keys(self): + path = '/images' + request = unit_test_utils.get_fake_request(path) + output = self.controller.index(request, + sort_key=['created_at', 'name'], + limit=3) actual = [image.image_id for image in output['images']] self.assertEqual(3, len(actual)) self.assertEqual(UUID3, actual[0]) @@ -441,7 +455,7 @@ class TestImagesController(base.IsolatedUnitTest): path = '/images' request = unit_test_utils.get_fake_request(path) self.assertRaises(webob.exc.HTTPBadRequest, - self.controller.index, request, sort_key='foo') + self.controller.index, request, sort_key=['foo']) def test_index_zero_images(self): self.db.reset() @@ -2433,7 +2447,7 @@ class TestImagesDeserializer(test_utils.BaseTestCase): request = unit_test_utils.get_fake_request(path) expected = {'limit': 1, 'marker': marker, - 'sort_key': 'created_at', + 'sort_key': ['created_at'], 'sort_dir': 'desc', 'member_status': 'pending', 'filters': {}} @@ -2481,7 +2495,7 @@ class TestImagesDeserializer(test_utils.BaseTestCase): def test_index_zero_limit(self): request = unit_test_utils.get_fake_request('/images?limit=0') expected = {'limit': 0, - 'sort_key': 'created_at', + 'sort_key': ['created_at'], 'member_status': 'accepted', 'sort_dir': 'desc', 'filters': {}} @@ -2525,18 +2539,38 @@ class TestImagesDeserializer(test_utils.BaseTestCase): request = unit_test_utils.get_fake_request('/images?sort_key=id') output = self.deserializer.index(request) expected = { - 'sort_key': 'id', + 'sort_key': ['id'], 'sort_dir': 'desc', 'member_status': 'accepted', 'filters': {} } self.assertEqual(expected, output) + def test_index_multiple_sort_keys(self): + request = unit_test_utils.get_fake_request('/images?' + 'sort_key=name&' + 'sort_key=size') + output = self.deserializer.index(request) + expected = { + 'sort_key': ['name', 'size'], + 'sort_dir': 'desc', + 'member_status': 'accepted', + 'filters': {} + } + self.assertEqual(expected, output) + + def test_index_invalid_multiple_sort_keys(self): + request = unit_test_utils.get_fake_request('/images?' + 'sort_key=name&' + 'sort_key=blah') + self.assertRaises(webob.exc.HTTPBadRequest, + self.deserializer.index, request) + def test_index_sort_dir_asc(self): request = unit_test_utils.get_fake_request('/images?sort_dir=asc') output = self.deserializer.index(request) expected = { - 'sort_key': 'created_at', + 'sort_key': ['created_at'], 'sort_dir': 'asc', 'member_status': 'accepted', 'filters': {}} diff --git a/glance/tests/unit/v2/test_registry_api.py b/glance/tests/unit/v2/test_registry_api.py index 7cd0aa0630..22f1852479 100644 --- a/glance/tests/unit/v2/test_registry_api.py +++ b/glance/tests/unit/v2/test_registry_api.py @@ -264,7 +264,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'marker': UUID3, 'sort_key': 'name', + 'kwargs': {'marker': UUID3, 'sort_key': ['name'], 'sort_dir': 'asc'}, }] req.body = jsonutils.dumps(cmd) @@ -297,7 +297,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'marker': UUID3, 'sort_key': 'name', + 'kwargs': {'marker': UUID3, 'sort_key': ['name'], 'sort_dir': 'desc'}, }] req.body = jsonutils.dumps(cmd) @@ -330,7 +330,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'marker': UUID3, 'sort_key': 'disk_format', + 'kwargs': {'marker': UUID3, 'sort_key': ['disk_format'], 'sort_dir': 'asc'}, }] req.body = jsonutils.dumps(cmd) @@ -363,7 +363,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'marker': UUID3, 'sort_key': 'disk_format', + 'kwargs': {'marker': UUID3, 'sort_key': ['disk_format'], 'sort_dir': 'desc'}, }] req.body = jsonutils.dumps(cmd) @@ -396,7 +396,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'marker': UUID3, 'sort_key': 'container_format', + 'kwargs': {'marker': UUID3, 'sort_key': ['container_format'], 'sort_dir': 'asc'}, }] req.body = jsonutils.dumps(cmd) @@ -429,7 +429,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'marker': UUID3, 'sort_key': 'container_format', + 'kwargs': {'marker': UUID3, 'sort_key': ['container_format'], 'sort_dir': 'desc'}, }] req.body = jsonutils.dumps(cmd) @@ -831,7 +831,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'name', 'sort_dir': 'asc'} + 'kwargs': {'sort_key': ['name'], 'sort_dir': 'asc'} }] req.body = jsonutils.dumps(cmd) res = req.get_response(self.api) @@ -883,7 +883,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'status', 'sort_dir': 'asc'} + 'kwargs': {'sort_key': ['status'], 'sort_dir': 'asc'} }] req.body = jsonutils.dumps(cmd) res = req.get_response(self.api) @@ -934,7 +934,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'disk_format', 'sort_dir': 'asc'} + 'kwargs': {'sort_key': ['disk_format'], 'sort_dir': 'asc'} }] req.body = jsonutils.dumps(cmd) res = req.get_response(self.api) @@ -985,7 +985,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'container_format', + 'kwargs': {'sort_key': ['container_format'], 'sort_dir': 'desc'} }] req.body = jsonutils.dumps(cmd) @@ -1033,7 +1033,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'size', + 'kwargs': {'sort_key': ['size'], 'sort_dir': 'asc'} }] req.body = jsonutils.dumps(cmd) @@ -1088,7 +1088,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'created_at', + 'kwargs': {'sort_key': ['created_at'], 'sort_dir': 'asc'} }] req.body = jsonutils.dumps(cmd) @@ -1143,7 +1143,7 @@ class TestRegistryRPC(base.IsolatedUnitTest): req.method = "POST" cmd = [{ 'command': 'image_get_all', - 'kwargs': {'sort_key': 'updated_at', + 'kwargs': {'sort_key': ['updated_at'], 'sort_dir': 'desc'} }] req.body = jsonutils.dumps(cmd) @@ -1158,6 +1158,94 @@ class TestRegistryRPC(base.IsolatedUnitTest): self.assertEqual(UUID2, images[2]['id']) self.assertEqual(UUID1, images[3]['id']) + def test_get_index_sort_multiple_keys(self): + """ + Tests that the registry API returns list of + public images sorted by name-size and size-name. + """ + uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10) + uuid3_time = uuid4_time + datetime.timedelta(seconds=5) + + UUID3 = _gen_uuid() + extra_fixture = {'id': UUID3, + 'status': 'active', + 'is_public': True, + 'disk_format': 'vhd', + 'container_format': 'ovf', + 'name': 'asdf', + 'size': 19, + 'checksum': None, + 'created_at': None, + 'updated_at': uuid3_time} + + db_api.image_create(self.context, extra_fixture) + + UUID4 = _gen_uuid() + extra_fixture = {'id': UUID4, + 'status': 'active', + 'is_public': True, + 'disk_format': 'vhd', + 'container_format': 'ovf', + 'name': 'xyz', + 'size': 20, + 'checksum': None, + 'created_at': None, + 'updated_at': uuid4_time} + + db_api.image_create(self.context, extra_fixture) + + UUID5 = _gen_uuid() + extra_fixture = {'id': UUID5, + 'status': 'active', + 'is_public': True, + 'disk_format': 'vhd', + 'container_format': 'ovf', + 'name': 'asdf', + 'size': 20, + 'checksum': None, + 'created_at': None, + 'updated_at': uuid4_time} + + db_api.image_create(self.context, extra_fixture) + + req = webob.Request.blank('/rpc') + req.method = "POST" + cmd = [{ + 'command': 'image_get_all', + 'kwargs': {'sort_key': ['name', 'size'], + 'sort_dir': 'asc'} + }] + req.body = jsonutils.dumps(cmd) + res = req.get_response(self.api) + self.assertEqual(200, res.status_int) + res_dict = jsonutils.loads(res.body)[0] + + images = res_dict + self.assertEqual(5, len(images)) + self.assertEqual(UUID3, images[0]['id']) + self.assertEqual(UUID5, images[1]['id']) + self.assertEqual(UUID1, images[2]['id']) + self.assertEqual(UUID2, images[3]['id']) + self.assertEqual(UUID4, images[4]['id']) + + cmd = [{ + 'command': 'image_get_all', + 'kwargs': {'sort_key': ['size', 'name'], + 'sort_dir': 'asc'} + }] + req.body = jsonutils.dumps(cmd) + res = req.get_response(self.api) + self.assertEqual(200, res.status_int) + res_dict = jsonutils.loads(res.body)[0] + + images = res_dict + self.assertEqual(5, len(images)) + self.assertEqual(UUID1, images[0]['id']) + self.assertEqual(UUID3, images[1]['id']) + self.assertEqual(UUID2, images[2]['id']) + self.assertEqual(UUID5, images[3]['id']) + self.assertEqual(UUID4, images[4]['id']) + def test_create_image(self): """Tests that the registry API creates the image""" fixture = {'name': 'fake public image', diff --git a/glance/tests/unit/v2/test_registry_client.py b/glance/tests/unit/v2/test_registry_client.py index 220935acd8..3a65ccf302 100644 --- a/glance/tests/unit/v2/test_registry_client.py +++ b/glance/tests/unit/v2/test_registry_client.py @@ -116,7 +116,8 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='name', sort_dir='asc') + images = self.client.image_get_all(sort_key=['name'], + sort_dir='asc') self.assertEqualImages(images, (UUID3, UUID1, UUID2, UUID4), unjsonify=False) @@ -140,7 +141,8 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='status', sort_dir='desc') + images = self.client.image_get_all(sort_key=['status'], + sort_dir='desc') self.assertEqualImages(images, (UUID3, UUID4, UUID2, UUID1), unjsonify=False) @@ -163,7 +165,7 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='disk_format', + images = self.client.image_get_all(sort_key=['disk_format'], sort_dir='asc') self.assertEqualImages(images, (UUID1, UUID3, UUID4, UUID2), @@ -188,7 +190,7 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='container_format', + images = self.client.image_get_all(sort_key=['container_format'], sort_dir='desc') self.assertEqualImages(images, (UUID2, UUID4, UUID3, UUID1), @@ -215,7 +217,7 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='size', sort_dir='asc') + images = self.client.image_get_all(sort_key=['size'], sort_dir='asc') self.assertEqualImages(images, (UUID4, UUID1, UUID2, UUID3), unjsonify=False) @@ -238,7 +240,7 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='created_at', + images = self.client.image_get_all(sort_key=['created_at'], sort_dir='asc') self.assertEqualImages(images, (UUID1, UUID2, UUID4, UUID3), @@ -264,12 +266,48 @@ class TestRegistryV2Client(base.IsolatedUnitTest, db_api.image_create(self.context, extra_fixture) - images = self.client.image_get_all(sort_key='updated_at', + images = self.client.image_get_all(sort_key=['updated_at'], sort_dir='desc') self.assertEqualImages(images, (UUID3, UUID4, UUID2, UUID1), unjsonify=False) + def test_get_image_details_sort_multiple_keys(self): + """ + Tests that a detailed call returns list of + public images sorted alphabetically by name-size and + size-name in ascending order. + """ + UUID3 = _gen_uuid() + extra_fixture = self.get_fixture(id=UUID3, name='asdf', + size=19) + + db_api.image_create(self.context, extra_fixture) + + UUID4 = _gen_uuid() + extra_fixture = self.get_fixture(id=UUID4, name='xyz', + size=20) + + db_api.image_create(self.context, extra_fixture) + + UUID5 = _gen_uuid() + extra_fixture = self.get_fixture(id=UUID5, name='asdf', + size=20) + + db_api.image_create(self.context, extra_fixture) + + images = self.client.image_get_all(sort_key=['name', 'size'], + sort_dir='asc') + + self.assertEqualImages(images, (UUID3, UUID5, UUID1, UUID2, UUID4), + unjsonify=False) + + images = self.client.image_get_all(sort_key=['size', 'name'], + sort_dir='asc') + + self.assertEqualImages(images, (UUID1, UUID3, UUID2, UUID5, UUID4), + unjsonify=False) + def test_image_get_index_marker(self): """Test correct set of images returned with marker param.""" uuid4_time = timeutils.utcnow() + datetime.timedelta(seconds=10)