Merge "Extend images api v2 with new sorting syntax"

This commit is contained in:
Jenkins 2015-03-13 09:26:31 +00:00 committed by Gerrit Code Review
commit 15fea34808
2 changed files with 187 additions and 35 deletions

View File

@ -323,6 +323,8 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
_path_depth_limits = {'locations': {'add': 2, 'remove': 2, 'replace': 1}}
_default_sort_dir = 'desc'
def __init__(self, schema=None):
super(RequestDeserializer, self).__init__()
self.schema = schema or get_schema()
@ -585,6 +587,63 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
return filters
def _get_sorting_params(self, params):
"""
Process sorting params.
Currently glance supports two sorting syntax: classic and new one,
that is uniform for all Openstack projects.
Classic syntax: sort_key=name&sort_dir=asc&sort_key=size&sort_dir=desc
New syntax: sort=name:asc,size:desc
"""
sort_keys = []
sort_dirs = []
if 'sort' in params:
# use new sorting syntax here
if 'sort_key' in params or 'sort_dir' in params:
msg = _('Old and new sorting syntax cannot be combined')
raise webob.exc.HTTPBadRequest(explanation=msg)
for sort_param in params.pop('sort').strip().split(','):
key, _sep, dir = sort_param.partition(':')
if not dir:
dir = self._default_sort_dir
sort_keys.append(self._validate_sort_key(key.strip()))
sort_dirs.append(self._validate_sort_dir(dir.strip()))
else:
# continue with classic syntax
# NOTE(mfedosin): we have 3 options here:
# 1. sort_dir wasn't passed: we use default one - 'desc'.
# 2. Only one sort_dir was passed: use it for every sort_key
# in the list.
# 3. Multiple sort_dirs were passed: consistently apply each one to
# the corresponding sort_key.
# If number of sort_dirs and sort_keys doesn't match then raise an
# exception.
while 'sort_key' in params:
sort_keys.append(self._validate_sort_key(
params.pop('sort_key').strip()))
while 'sort_dir' in params:
sort_dirs.append(self._validate_sort_dir(
params.pop('sort_dir').strip()))
if sort_dirs:
dir_len = len(sort_dirs)
key_len = len(sort_keys)
if dir_len > 1 and dir_len != key_len:
msg = _('Number of sort dirs does not match the number '
'of sort keys')
raise webob.exc.HTTPBadRequest(explanation=msg)
if not sort_keys:
sort_keys = [self._default_sort_key]
if not sort_dirs:
sort_dirs = [self._default_sort_dir]
return sort_keys, sort_dirs
def index(self, request):
params = request.params.copy()
limit = params.pop('limit', None)
@ -598,19 +657,6 @@ 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()))
sort_dirs = []
while 'sort_dir' in params:
sort_dirs.append(self._validate_sort_dir(
params.pop('sort_dir').strip()))
query_params = {
'filters': self._get_filters(params),
'member_status': self._validate_member_status(member_status),
@ -625,31 +671,12 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
if tags:
query_params['filters']['tags'] = tags
# NOTE(mfedosin): params is still called sort_key and sort_dir,
# NOTE(mfedosin): param is still called sort_key and sort_dir,
# instead of sort_keys and sort_dirs respectively.
# It's done because in v1 it's still a single value.
query_params['sort_key'] = sort_keys if sort_keys \
else [self._default_sort_key]
# NOTE(mfedosin): we have 3 options here:
# 1. sort_dir wasn't passed: we use default one - 'desc'.
# 2. Only one sort_dir was passed: use it for every sort_key
# in the list.
# 3. Multiple sort_dirs were passed: consistently apply each one to
# the corresponding sort_key.
# If number of sort_dirs and sort_keys doesn't match then raise an
# exception.
if sort_dirs:
dir_len = len(sort_dirs)
key_len = len(sort_keys)
if dir_len > 1 and dir_len != key_len:
msg = _('Number of sort dirs does not match the number '
'of sort keys')
raise webob.exc.HTTPBadRequest(explanation=msg)
query_params['sort_dir'] = sort_dirs
else:
query_params['sort_dir'] = [self._default_sort_dir]
query_params['sort_key'], query_params['sort_dir'] = \
self._get_sorting_params(params)
return query_params

View File

@ -2633,6 +2633,102 @@ class TestImagesDeserializer(test_utils.BaseTestCase):
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_single_key_default_dir(self):
req_string = '/images?sort=name'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name'],
'sort_dir': ['desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_single_key_desc_dir(self):
req_string = '/images?sort=name:desc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name'],
'sort_dir': ['desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_default_dir(self):
req_string = '/images?sort=name,size'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_asc_dir(self):
req_string = '/images?sort=name:asc,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['asc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_different_dirs(self):
req_string = '/images?sort=name:desc,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_new_sorting_syntax_multiple_keys_optional_dir(self):
req_string = '/images?sort=name:asc,size'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['asc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
req_string = '/images?sort=name,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'size'],
'sort_dir': ['desc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
req_string = '/images?sort=name,id:asc,size'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'id', 'size'],
'sort_dir': ['desc', 'asc', 'desc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
req_string = '/images?sort=name:asc,id,size:asc'
request = unit_test_utils.get_fake_request(req_string)
output = self.deserializer.index(request)
expected = {
'sort_key': ['name', 'id', 'size'],
'sort_dir': ['asc', 'desc', 'asc'],
'member_status': 'accepted',
'filters': {}}
self.assertEqual(expected, output)
def test_index_sort_wrong_sort_dirs_number(self):
req_string = '/images?sort_key=name&sort_dir=asc&sort_dir=desc'
request = unit_test_utils.get_fake_request(req_string)
@ -2669,6 +2765,35 @@ class TestImagesDeserializer(test_utils.BaseTestCase):
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_new_sorting_syntax_invalid_request(self):
# 'blah' is not a supported sorting key
req_string = '/images?sort=blah'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
req_string = '/images?sort=name,blah'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
# 'foo' isn't a valid sort direction
req_string = '/images?sort=name:foo'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
# 'asc:desc' isn't a valid sort direction
req_string = '/images?sort=name:asc:desc'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_combined_sorting_syntax(self):
req_string = '/images?sort_dir=name&sort=name'
request = unit_test_utils.get_fake_request(req_string)
self.assertRaises(webob.exc.HTTPBadRequest,
self.deserializer.index, request)
def test_index_with_tag(self):
path = '/images?tag=%s&tag=%s' % ('x86', '64bit')
request = unit_test_utils.get_fake_request(path)