Merge "On Volume list only retrieve needed data from DB"
This commit is contained in:
commit
fb95d86da0
|
@ -69,42 +69,64 @@ def validate_key_names(key_names_list):
|
|||
return True
|
||||
|
||||
|
||||
def get_pagination_params(request):
|
||||
"""Return marker, limit tuple from request.
|
||||
|
||||
:param request: `wsgi.Request` possibly containing 'marker' and 'limit'
|
||||
GET variables. 'marker' is the id of the last element
|
||||
the client has seen, and 'limit' is the maximum number
|
||||
of items to return. If 'limit' is not specified, 0, or
|
||||
> max_limit, we default to max_limit. Negative values
|
||||
for either marker or limit will cause
|
||||
exc.HTTPBadRequest() exceptions to be raised.
|
||||
def get_pagination_params(params, max_limit=CONF.osapi_max_limit):
|
||||
"""Return marker, limit, offset tuple from request.
|
||||
|
||||
:param params: `wsgi.Request`'s GET dictionary, possibly containing
|
||||
'marker', 'limit', and 'offset' variables. 'marker' is the
|
||||
id of the last element the client has seen, 'limit' is the
|
||||
maximum number of items to return and 'offset' is the number
|
||||
of items to skip from the marker or from the first element.
|
||||
If 'limit' is not specified, or > max_limit, we default to
|
||||
max_limit. Negative values for either offset or limit will
|
||||
cause exc.HTTPBadRequest() exceptions to be raised. If no
|
||||
offset is present we'll default to 0 and if no marker is
|
||||
present we'll default to None.
|
||||
:max_limit: Max value 'limit' return value can take
|
||||
:returns: Tuple (marker, limit, offset)
|
||||
"""
|
||||
params = {}
|
||||
if 'limit' in request.GET:
|
||||
params['limit'] = _get_limit_param(request)
|
||||
if 'marker' in request.GET:
|
||||
params['marker'] = _get_marker_param(request)
|
||||
return params
|
||||
limit = _get_limit_param(params, max_limit)
|
||||
marker = _get_marker_param(params)
|
||||
offset = _get_offset_param(params)
|
||||
return marker, limit, offset
|
||||
|
||||
|
||||
def _get_limit_param(request):
|
||||
"""Extract integer limit from request or fail."""
|
||||
def _get_limit_param(params, max_limit=CONF.osapi_max_limit):
|
||||
"""Extract integer limit from request's dictionary or fail.
|
||||
|
||||
Defaults to max_limit if not present and returns max_limit if present
|
||||
'limit' is greater than max_limit.
|
||||
"""
|
||||
try:
|
||||
limit = int(request.GET['limit'])
|
||||
limit = int(params.pop('limit', max_limit))
|
||||
except ValueError:
|
||||
msg = _('limit param must be an integer')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
if limit < 0:
|
||||
msg = _('limit param must be positive')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
limit = min(limit, max_limit)
|
||||
return limit
|
||||
|
||||
|
||||
def _get_marker_param(request):
|
||||
"""Extract marker id from request or fail."""
|
||||
return request.GET['marker']
|
||||
def _get_marker_param(params):
|
||||
"""Extract marker id from request's dictionary (defaults to None)."""
|
||||
return params.pop('marker', None)
|
||||
|
||||
|
||||
def _get_offset_param(params):
|
||||
"""Extract offset id from request's dictionary (defaults to 0) or fail."""
|
||||
try:
|
||||
offset = int(params.pop('offset', 0))
|
||||
except ValueError:
|
||||
msg = _('offset param must be an integer')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if offset < 0:
|
||||
msg = _('offset param must be positive')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
return offset
|
||||
|
||||
|
||||
def limited(items, request, max_limit=CONF.osapi_max_limit):
|
||||
|
@ -119,39 +141,16 @@ def limited(items, request, max_limit=CONF.osapi_max_limit):
|
|||
will cause exc.HTTPBadRequest() exceptions to be raised.
|
||||
:kwarg max_limit: The maximum number of items to return from 'items'
|
||||
"""
|
||||
try:
|
||||
offset = int(request.GET.get('offset', 0))
|
||||
except ValueError:
|
||||
msg = _('offset param must be an integer')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
try:
|
||||
limit = int(request.GET.get('limit', max_limit))
|
||||
except ValueError:
|
||||
msg = _('limit param must be an integer')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if limit < 0:
|
||||
msg = _('limit param must be positive')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if offset < 0:
|
||||
msg = _('offset param must be positive')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
limit = min(max_limit, limit or max_limit)
|
||||
range_end = offset + limit
|
||||
marker, limit, offset = get_pagination_params(request.GET.copy(),
|
||||
max_limit)
|
||||
range_end = offset + (limit or max_limit)
|
||||
return items[offset:range_end]
|
||||
|
||||
|
||||
def limited_by_marker(items, request, max_limit=CONF.osapi_max_limit):
|
||||
"""Return a slice of items according to the requested marker and limit."""
|
||||
params = get_pagination_params(request)
|
||||
marker, limit, __ = get_pagination_params(request.GET.copy(), max_limit)
|
||||
|
||||
limit = params.get('limit', max_limit)
|
||||
marker = params.get('marker')
|
||||
|
||||
limit = min(max_limit, limit)
|
||||
start_index = 0
|
||||
if marker:
|
||||
start_index = -1
|
||||
|
@ -289,19 +288,19 @@ class ViewBuilder(object):
|
|||
str(identifier))
|
||||
|
||||
def _get_collection_links(self, request, items, collection_name,
|
||||
item_count, id_key="uuid"):
|
||||
item_count=None, id_key="uuid"):
|
||||
"""Retrieve 'next' link, if applicable.
|
||||
|
||||
The next link is included if:
|
||||
1) 'limit' param is specified and equal to or less than the
|
||||
number of items.
|
||||
2) 'limit' param is specified and both the limit and the number
|
||||
of items are greater than CONF.osapi_max_limit, even if limit
|
||||
is greater than the number of items.
|
||||
3) 'limit' param is NOT specified and the number of items is
|
||||
greater than CONF.osapi_max_limit.
|
||||
Notes: The case limit equals to 0 or CONF.osapi_max_limit equals to 0
|
||||
is not included in the above conditions.
|
||||
The next link is included if we are returning as many items as we can,
|
||||
given the restrictions of limit optional request parameter and
|
||||
osapi_max_limit configuration parameter as long as we are returning
|
||||
some elements.
|
||||
|
||||
So we return next link if:
|
||||
|
||||
1) 'limit' param is specified and equal to the number of items.
|
||||
2) 'limit' param is NOT specified and the number of items is
|
||||
equal to CONF.osapi_max_limit.
|
||||
|
||||
:param request: API request
|
||||
:param items: List of collection items
|
||||
|
@ -313,25 +312,12 @@ class ViewBuilder(object):
|
|||
to generate the next link marker for a pagination query
|
||||
:returns links
|
||||
"""
|
||||
limit = request.params.get("limit", None)
|
||||
if limit is None or int(limit) > CONF.osapi_max_limit:
|
||||
# If limit is not set in the request or greater than
|
||||
# osapi_max_limit, len(items) < item_count means the items
|
||||
# are limited by osapi_max_limit and we need to generate the
|
||||
# next link. Otherwise, all the items will be returned in
|
||||
# the response, no next link is generated.
|
||||
if len(items) < item_count:
|
||||
return self._generate_next_link(items, id_key, request,
|
||||
collection_name)
|
||||
else:
|
||||
# If limit is set in the request and not more than
|
||||
# osapi_max_limit, int(limit) == len(items) means it is possible
|
||||
# that the DB still have more items left. In this case,
|
||||
# we generate the next link.
|
||||
limit = int(limit)
|
||||
if limit and limit == len(items):
|
||||
return self._generate_next_link(items, id_key, request,
|
||||
collection_name)
|
||||
item_count = item_count or len(items)
|
||||
limit = _get_limit_param(request.GET.copy())
|
||||
if len(items) and limit <= item_count:
|
||||
return self._generate_next_link(items, id_key, request,
|
||||
collection_name)
|
||||
|
||||
return []
|
||||
|
||||
def _generate_next_link(self, items, id_key, request,
|
||||
|
|
|
@ -30,12 +30,12 @@ class ViewBuilder(common.ViewBuilder):
|
|||
"""Initialize view builder."""
|
||||
super(ViewBuilder, self).__init__()
|
||||
|
||||
def summary_list(self, request, volumes, volume_count):
|
||||
def summary_list(self, request, volumes, volume_count=None):
|
||||
"""Show a list of volumes without many details."""
|
||||
return self._list_view(self.summary, request, volumes,
|
||||
volume_count)
|
||||
|
||||
def detail_list(self, request, volumes, volume_count):
|
||||
def detail_list(self, request, volumes, volume_count=None):
|
||||
"""Detailed view of a list of volumes."""
|
||||
return self._list_view(self.detail, request, volumes,
|
||||
volume_count,
|
||||
|
|
|
@ -227,10 +227,8 @@ class VolumeController(wsgi.Controller):
|
|||
context = req.environ['cinder.context']
|
||||
|
||||
params = req.params.copy()
|
||||
marker = params.pop('marker', None)
|
||||
limit = params.pop('limit', None)
|
||||
marker, limit, offset = common.get_pagination_params(params)
|
||||
sort_keys, sort_dirs = common.get_sort_params(params)
|
||||
params.pop('offset', None)
|
||||
filters = params
|
||||
|
||||
utils.remove_invalid_filter_options(context,
|
||||
|
@ -255,23 +253,20 @@ class VolumeController(wsgi.Controller):
|
|||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters,
|
||||
viewable_admin_meta=True)
|
||||
viewable_admin_meta=True,
|
||||
offset=offset)
|
||||
|
||||
volumes = [dict(vol) for vol in volumes]
|
||||
|
||||
for volume in volumes:
|
||||
utils.add_visible_admin_metadata(volume)
|
||||
|
||||
limited_list = common.limited(volumes, req)
|
||||
volume_count = len(volumes)
|
||||
req.cache_db_volumes(limited_list)
|
||||
req.cache_db_volumes(volumes)
|
||||
|
||||
if is_detail:
|
||||
volumes = self._view_builder.detail_list(req, limited_list,
|
||||
volume_count)
|
||||
volumes = self._view_builder.detail_list(req, volumes)
|
||||
else:
|
||||
volumes = self._view_builder.summary_list(req, limited_list,
|
||||
volume_count)
|
||||
volumes = self._view_builder.summary_list(req, volumes)
|
||||
return volumes
|
||||
|
||||
def _image_uuid_from_ref(self, image_ref, context):
|
||||
|
|
|
@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
# copied from glance/db/sqlalchemy/api.py
|
||||
def paginate_query(query, model, limit, sort_keys, marker=None,
|
||||
sort_dir=None, sort_dirs=None):
|
||||
sort_dir=None, sort_dirs=None, offset=None):
|
||||
"""Returns a query with sorting / pagination criteria added.
|
||||
|
||||
Pagination works by requiring a unique sort_key, specified by sort_keys.
|
||||
|
@ -125,4 +125,7 @@ def paginate_query(query, model, limit, sort_keys, marker=None,
|
|||
if limit is not None:
|
||||
query = query.limit(limit)
|
||||
|
||||
if offset:
|
||||
query = query.offset(offset)
|
||||
|
||||
return query
|
||||
|
|
|
@ -206,10 +206,11 @@ def volume_get(context, volume_id):
|
|||
|
||||
|
||||
def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
|
||||
filters=None):
|
||||
filters=None, offset=None):
|
||||
"""Get all volumes."""
|
||||
return IMPL.volume_get_all(context, marker, limit, sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs, filters=filters)
|
||||
sort_dirs=sort_dirs, filters=filters,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def volume_get_all_by_host(context, host, filters=None):
|
||||
|
@ -223,12 +224,14 @@ def volume_get_all_by_group(context, group_id, filters=None):
|
|||
|
||||
|
||||
def volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None, filters=None):
|
||||
sort_keys=None, sort_dirs=None, filters=None,
|
||||
offset=None):
|
||||
"""Get all volumes belonging to a project."""
|
||||
return IMPL.volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters)
|
||||
filters=filters,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def volume_get_iscsi_target_num(context, volume_id):
|
||||
|
|
|
@ -1343,7 +1343,7 @@ def volume_get(context, volume_id):
|
|||
|
||||
@require_admin_context
|
||||
def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
|
||||
filters=None):
|
||||
filters=None, offset=None):
|
||||
"""Retrieves all volumes.
|
||||
|
||||
If no sort parameters are specified then the returned volumes are sorted
|
||||
|
@ -1368,7 +1368,7 @@ def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
|
|||
with session.begin():
|
||||
# Generate the query
|
||||
query = _generate_paginate_query(context, session, marker, limit,
|
||||
sort_keys, sort_dirs, filters)
|
||||
sort_keys, sort_dirs, filters, offset)
|
||||
# No volumes would match, return empty list
|
||||
if query is None:
|
||||
return []
|
||||
|
@ -1433,7 +1433,8 @@ def volume_get_all_by_group(context, group_id, filters=None):
|
|||
|
||||
@require_context
|
||||
def volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None, filters=None):
|
||||
sort_keys=None, sort_dirs=None, filters=None,
|
||||
offset=None):
|
||||
"""Retrieves all volumes in a project.
|
||||
|
||||
If no sort parameters are specified then the returned volumes are sorted
|
||||
|
@ -1463,7 +1464,7 @@ def volume_get_all_by_project(context, project_id, marker, limit,
|
|||
filters['project_id'] = project_id
|
||||
# Generate the query
|
||||
query = _generate_paginate_query(context, session, marker, limit,
|
||||
sort_keys, sort_dirs, filters)
|
||||
sort_keys, sort_dirs, filters, offset)
|
||||
# No volumes would match, return empty list
|
||||
if query is None:
|
||||
return []
|
||||
|
@ -1471,7 +1472,7 @@ def volume_get_all_by_project(context, project_id, marker, limit,
|
|||
|
||||
|
||||
def _generate_paginate_query(context, session, marker, limit, sort_keys,
|
||||
sort_dirs, filters):
|
||||
sort_dirs, filters, offset=None):
|
||||
"""Generate the query to include the filters and the paginate options.
|
||||
|
||||
Returns a query with sorting / pagination criteria added or None
|
||||
|
@ -1490,6 +1491,7 @@ def _generate_paginate_query(context, session, marker, limit, sort_keys,
|
|||
or sets cause an 'IN' operation, while exact matching
|
||||
is used for other values, see _process_volume_filters
|
||||
function for more information
|
||||
:param offset: number of items to skip
|
||||
:returns: updated query or None
|
||||
"""
|
||||
sort_keys, sort_dirs = process_sort_params(sort_keys,
|
||||
|
@ -1509,7 +1511,8 @@ def _generate_paginate_query(context, session, marker, limit, sort_keys,
|
|||
return sqlalchemyutils.paginate_query(query, models.Volume, limit,
|
||||
sort_keys,
|
||||
marker=marker_volume,
|
||||
sort_dirs=sort_dirs)
|
||||
sort_dirs=sort_dirs,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def _process_volume_filters(query, filters):
|
||||
|
|
|
@ -22,12 +22,15 @@ from testtools import matchers
|
|||
import webob
|
||||
import webob.exc
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from cinder.api import common
|
||||
from cinder import test
|
||||
|
||||
|
||||
NS = "{http://docs.openstack.org/compute/api/v1.1}"
|
||||
ATOMNS = "{http://www.w3.org/2005/Atom}"
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class LimiterTest(test.TestCase):
|
||||
|
@ -172,37 +175,45 @@ class PaginationParamsTest(test.TestCase):
|
|||
"""Test nonnumerical limit param."""
|
||||
req = webob.Request.blank('/?limit=hello')
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest, common.get_pagination_params, req)
|
||||
webob.exc.HTTPBadRequest, common.get_pagination_params,
|
||||
req.GET.copy())
|
||||
|
||||
def test_no_params(self):
|
||||
"""Test no params."""
|
||||
req = webob.Request.blank('/')
|
||||
self.assertEqual({}, common.get_pagination_params(req))
|
||||
expected = (None, CONF.osapi_max_limit, 0)
|
||||
self.assertEqual(expected,
|
||||
common.get_pagination_params(req.GET.copy()))
|
||||
|
||||
def test_valid_marker(self):
|
||||
"""Test valid marker param."""
|
||||
req = webob.Request.blank(
|
||||
'/?marker=263abb28-1de6-412f-b00b-f0ee0c4333c2')
|
||||
self.assertEqual({'marker': '263abb28-1de6-412f-b00b-f0ee0c4333c2'},
|
||||
common.get_pagination_params(req))
|
||||
marker = '263abb28-1de6-412f-b00b-f0ee0c4333c2'
|
||||
req = webob.Request.blank('/?marker=' + marker)
|
||||
expected = (marker, CONF.osapi_max_limit, 0)
|
||||
self.assertEqual(expected,
|
||||
common.get_pagination_params(req.GET.copy()))
|
||||
|
||||
def test_valid_limit(self):
|
||||
"""Test valid limit param."""
|
||||
req = webob.Request.blank('/?limit=10')
|
||||
self.assertEqual({'limit': 10}, common.get_pagination_params(req))
|
||||
expected = (None, 10, 0)
|
||||
self.assertEqual(expected,
|
||||
common.get_pagination_params(req.GET.copy()))
|
||||
|
||||
def test_invalid_limit(self):
|
||||
"""Test invalid limit param."""
|
||||
req = webob.Request.blank('/?limit=-2')
|
||||
self.assertRaises(
|
||||
webob.exc.HTTPBadRequest, common.get_pagination_params, req)
|
||||
webob.exc.HTTPBadRequest, common.get_pagination_params,
|
||||
req.GET.copy())
|
||||
|
||||
def test_valid_limit_and_marker(self):
|
||||
"""Test valid limit and marker parameters."""
|
||||
marker = '263abb28-1de6-412f-b00b-f0ee0c4333c2'
|
||||
req = webob.Request.blank('/?limit=20&marker=%s' % marker)
|
||||
self.assertEqual({'marker': marker, 'limit': 20},
|
||||
common.get_pagination_params(req))
|
||||
expected = (marker, 20, 0)
|
||||
self.assertEqual(expected,
|
||||
common.get_pagination_params(req.GET.copy()))
|
||||
|
||||
|
||||
class SortParamUtilsTest(test.TestCase):
|
||||
|
@ -354,25 +365,35 @@ class MiscFunctionsTest(test.TestCase):
|
|||
class TestCollectionLinks(test.TestCase):
|
||||
"""Tests the _get_collection_links method."""
|
||||
|
||||
def _validate_next_link(self, href_link_mock, item_count,
|
||||
osapi_max_limit, limit, should_link_exist):
|
||||
req = mock.MagicMock()
|
||||
href_link_mock.return_value = [{"rel": "next",
|
||||
"href": "fake_link"}]
|
||||
def _validate_next_link(self, item_count, osapi_max_limit, limit,
|
||||
should_link_exist):
|
||||
req = webob.Request.blank('/?limit=%s' % limit if limit else '/')
|
||||
link_return = [{"rel": "next", "href": "fake_link"}]
|
||||
self.flags(osapi_max_limit=osapi_max_limit)
|
||||
if limit is None:
|
||||
params = mock.PropertyMock(return_value=dict())
|
||||
limited_list_size = min(item_count, osapi_max_limit)
|
||||
else:
|
||||
params = mock.PropertyMock(return_value=dict(limit=limit))
|
||||
limited_list_size = min(item_count, osapi_max_limit,
|
||||
limit)
|
||||
limited_list_size = min(item_count, osapi_max_limit, limit)
|
||||
limited_list = [{"uuid": str(i)} for i in range(limited_list_size)]
|
||||
type(req).params = params
|
||||
builder = common.ViewBuilder()
|
||||
results = builder._get_collection_links(req, limited_list,
|
||||
mock.sentinel.coll_key,
|
||||
item_count, "uuid")
|
||||
|
||||
def get_pagination_params(params, max_limit=CONF.osapi_max_limit,
|
||||
original_call=common.get_pagination_params):
|
||||
return original_call(params, max_limit)
|
||||
|
||||
def _get_limit_param(params, max_limit=CONF.osapi_max_limit,
|
||||
original_call=common._get_limit_param):
|
||||
return original_call(params, max_limit)
|
||||
|
||||
with mock.patch.object(common, 'get_pagination_params',
|
||||
get_pagination_params), \
|
||||
mock.patch.object(common, '_get_limit_param',
|
||||
_get_limit_param), \
|
||||
mock.patch.object(common.ViewBuilder, '_generate_next_link',
|
||||
return_value=link_return) as href_link_mock:
|
||||
results = builder._get_collection_links(req, limited_list,
|
||||
mock.sentinel.coll_key,
|
||||
item_count, "uuid")
|
||||
if should_link_exist:
|
||||
href_link_mock.assert_called_once_with(limited_list, "uuid",
|
||||
req,
|
||||
|
@ -382,171 +403,133 @@ class TestCollectionLinks(test.TestCase):
|
|||
self.assertFalse(href_link_mock.called)
|
||||
self.assertThat(results, matchers.HasLength(0))
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_equals_osapi_max_no_limit(self, href_link_mock):
|
||||
def test_items_equals_osapi_max_no_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 5
|
||||
limit = None
|
||||
should_link_exist = False
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
should_link_exist = True
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_equals_osapi_max_greater_than_limit(self,
|
||||
href_link_mock):
|
||||
def test_items_equals_osapi_max_greater_than_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 5
|
||||
limit = 4
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_equals_osapi_max_equals_limit(self, href_link_mock):
|
||||
def test_items_equals_osapi_max_equals_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 5
|
||||
limit = 5
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_equals_osapi_max_less_than_limit(self, href_link_mock):
|
||||
def test_items_equals_osapi_max_less_than_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 5
|
||||
limit = 6
|
||||
should_link_exist = False
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
should_link_exist = True
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_less_than_osapi_max_no_limit(self, href_link_mock):
|
||||
def test_items_less_than_osapi_max_no_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 7
|
||||
limit = None
|
||||
should_link_exist = False
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_limit_less_than_items_less_than_osapi_max(self, href_link_mock):
|
||||
def test_limit_less_than_items_less_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 7
|
||||
limit = 4
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_limit_equals_items_less_than_osapi_max(self, href_link_mock):
|
||||
def test_limit_equals_items_less_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 7
|
||||
limit = 5
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_less_than_limit_less_than_osapi_max(self, href_link_mock):
|
||||
def test_items_less_than_limit_less_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 7
|
||||
limit = 6
|
||||
should_link_exist = False
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_less_than_osapi_max_equals_limit(self, href_link_mock):
|
||||
def test_items_less_than_osapi_max_equals_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 7
|
||||
limit = 7
|
||||
should_link_exist = False
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_less_than_osapi_max_less_than_limit(self, href_link_mock):
|
||||
def test_items_less_than_osapi_max_less_than_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 7
|
||||
limit = 8
|
||||
should_link_exist = False
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_greater_than_osapi_max_no_limit(self, href_link_mock):
|
||||
def test_items_greater_than_osapi_max_no_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 3
|
||||
limit = None
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_limit_less_than_items_greater_than_osapi_max(self,
|
||||
href_link_mock):
|
||||
def test_limit_less_than_items_greater_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 3
|
||||
limit = 2
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_greater_than_osapi_max_equals_limit(self,
|
||||
href_link_mock):
|
||||
def test_items_greater_than_osapi_max_equals_limit(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 3
|
||||
limit = 3
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_greater_than_limit_greater_than_osapi_max(self,
|
||||
href_link_mock):
|
||||
def test_items_greater_than_limit_greater_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 3
|
||||
limit = 4
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_items_equals_limit_greater_than_osapi_max(self,
|
||||
href_link_mock):
|
||||
def test_items_equals_limit_greater_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 3
|
||||
limit = 5
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link')
|
||||
def test_limit_greater_than_items_greater_than_osapi_max(self,
|
||||
href_link_mock):
|
||||
def test_limit_greater_than_items_greater_than_osapi_max(self):
|
||||
item_count = 5
|
||||
osapi_max_limit = 3
|
||||
limit = 6
|
||||
should_link_exist = True
|
||||
self._validate_next_link(href_link_mock, item_count,
|
||||
osapi_max_limit,
|
||||
limit, should_link_exist)
|
||||
self._validate_next_link(item_count, osapi_max_limit, limit,
|
||||
should_link_exist)
|
||||
|
||||
|
||||
class LinkPrefixTest(test.TestCase):
|
||||
|
|
|
@ -639,7 +639,8 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all_by_project(context, project_id, marker,
|
||||
limit, sort_keys=None,
|
||||
sort_dirs=None, filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False,
|
||||
offset=None):
|
||||
return [
|
||||
stubs.stub_volume(1, display_name='vol1'),
|
||||
stubs.stub_volume(2, display_name='vol2'),
|
||||
|
|
|
@ -149,7 +149,7 @@ def stub_volume_get_db(context, volume_id):
|
|||
|
||||
def stub_volume_get_all(context, search_opts=None, marker=None, limit=None,
|
||||
sort_keys=None, sort_dirs=None, filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False, offset=None):
|
||||
return [stub_volume(100, project_id='fake'),
|
||||
stub_volume(101, project_id='superfake'),
|
||||
stub_volume(102, project_id='superduperfake')]
|
||||
|
@ -158,7 +158,7 @@ def stub_volume_get_all(context, search_opts=None, marker=None, limit=None,
|
|||
def stub_volume_get_all_by_project(self, context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False, offset=None):
|
||||
filters = filters or {}
|
||||
return [stub_volume_get(self, context, '1', viewable_admin_meta=True)]
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ from six.moves import range
|
|||
from six.moves import urllib
|
||||
import webob
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api.v2 import volumes
|
||||
from cinder import consistencygroup as consistencygroupAPI
|
||||
|
@ -37,6 +38,7 @@ from cinder.tests.unit.api import fakes
|
|||
from cinder.tests.unit.api.v2 import stubs
|
||||
from cinder.tests.unit import fake_notifier
|
||||
from cinder.tests.unit.image import fake as fake_image
|
||||
from cinder.tests.unit import utils
|
||||
from cinder.volume import api as volume_api
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -61,6 +63,7 @@ class VolumeApiTest(test.TestCase):
|
|||
self.stubs.Set(db, 'service_get_all_by_topic',
|
||||
stubs.stub_service_get_all_by_topic)
|
||||
self.maxDiff = None
|
||||
self.ctxt = context.RequestContext('admin', 'fakeproject', True)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
|
@ -763,7 +766,8 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False,
|
||||
offset=0):
|
||||
return [
|
||||
stubs.stub_volume(1, display_name='vol1'),
|
||||
stubs.stub_volume(2, display_name='vol2'),
|
||||
|
@ -808,13 +812,13 @@ class VolumeApiTest(test.TestCase):
|
|||
|
||||
def test_volume_index_limit_negative(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes?limit=-1')
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index,
|
||||
req)
|
||||
|
||||
def test_volume_index_limit_non_int(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes?limit=a')
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index,
|
||||
req)
|
||||
|
||||
|
@ -829,32 +833,29 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertEqual(1, len(volumes))
|
||||
self.assertEqual('1', volumes[0]['id'])
|
||||
|
||||
def test_volume_index_limit_offset(self):
|
||||
def stub_volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
return [
|
||||
stubs.stub_volume(1, display_name='vol1'),
|
||||
stubs.stub_volume(2, display_name='vol2'),
|
||||
]
|
||||
self.stubs.Set(db, 'volume_get_all_by_project',
|
||||
stub_volume_get_all_by_project)
|
||||
self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get)
|
||||
def _create_db_volumes(self, num_volumes):
|
||||
volumes = [utils.create_volume(self.ctxt, display_name='vol%s' % i)
|
||||
for i in range(num_volumes)]
|
||||
for vol in volumes:
|
||||
self.addCleanup(db.volume_destroy, self.ctxt, vol.id)
|
||||
volumes.reverse()
|
||||
return volumes
|
||||
|
||||
def test_volume_index_limit_offset(self):
|
||||
created_volumes = self._create_db_volumes(2)
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes?limit=2&offset=1')
|
||||
res_dict = self.controller.index(req)
|
||||
volumes = res_dict['volumes']
|
||||
self.assertEqual(1, len(volumes))
|
||||
self.assertEqual(2, volumes[0]['id'])
|
||||
self.assertEqual(created_volumes[1].id, volumes[0]['id'])
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes?limit=-1&offset=1')
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index,
|
||||
req)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes?limit=a&offset=1')
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index,
|
||||
req)
|
||||
|
||||
|
@ -862,7 +863,8 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False,
|
||||
offset=0):
|
||||
return [
|
||||
stubs.stub_volume(1, display_name='vol1'),
|
||||
stubs.stub_volume(2, display_name='vol2'),
|
||||
|
@ -899,13 +901,13 @@ class VolumeApiTest(test.TestCase):
|
|||
|
||||
def test_volume_detail_limit_negative(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=-1')
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.detail,
|
||||
req)
|
||||
|
||||
def test_volume_detail_limit_non_int(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=a')
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.detail,
|
||||
req)
|
||||
|
||||
|
@ -921,38 +923,27 @@ class VolumeApiTest(test.TestCase):
|
|||
self.assertEqual('1', volumes[0]['id'])
|
||||
|
||||
def test_volume_detail_limit_offset(self):
|
||||
def stub_volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
return [
|
||||
stubs.stub_volume(1, display_name='vol1'),
|
||||
stubs.stub_volume(2, display_name='vol2'),
|
||||
]
|
||||
self.stubs.Set(db, 'volume_get_all_by_project',
|
||||
stub_volume_get_all_by_project)
|
||||
self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get)
|
||||
|
||||
created_volumes = self._create_db_volumes(2)
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=2&offset=1')
|
||||
res_dict = self.controller.detail(req)
|
||||
volumes = res_dict['volumes']
|
||||
self.assertEqual(1, len(volumes))
|
||||
self.assertEqual(2, volumes[0]['id'])
|
||||
self.assertEqual(created_volumes[1].id, volumes[0]['id'])
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=2&offset=1',
|
||||
use_admin_context=True)
|
||||
res_dict = self.controller.detail(req)
|
||||
volumes = res_dict['volumes']
|
||||
self.assertEqual(1, len(volumes))
|
||||
self.assertEqual(2, volumes[0]['id'])
|
||||
self.assertEqual(created_volumes[1].id, volumes[0]['id'])
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=-1&offset=1')
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.detail,
|
||||
req)
|
||||
|
||||
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=a&offset=1')
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.detail,
|
||||
req)
|
||||
|
||||
|
@ -965,84 +956,71 @@ class VolumeApiTest(test.TestCase):
|
|||
expected = {'volumes': []}
|
||||
self.assertEqual(expected, res_dict)
|
||||
|
||||
def test_volume_default_limit(self):
|
||||
self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get)
|
||||
def _validate_next_link(self, detailed, item_count, osapi_max_limit, limit,
|
||||
should_link_exist):
|
||||
keys_fns = (('volumes', self.controller.index),
|
||||
('volumes/detail', self.controller.detail))
|
||||
key, fn = keys_fns[detailed]
|
||||
|
||||
def _verify_links(links, url_key):
|
||||
"""Verify next link and url."""
|
||||
self.assertEqual('next', links[0]['rel'])
|
||||
href_parts = urllib.parse.urlparse(links[0]['href'])
|
||||
self.assertEqual('/v2/fakeproject/%s' % key, href_parts.path)
|
||||
req_string = '/v2/%s?all_tenants=1' % key
|
||||
if limit:
|
||||
req_string += '&limit=%s' % limit
|
||||
req = fakes.HTTPRequest.blank(req_string, use_admin_context=True)
|
||||
|
||||
link_return = [{"rel": "next", "href": "fake_link"}]
|
||||
self.flags(osapi_max_limit=osapi_max_limit)
|
||||
|
||||
def get_pagination_params(params, max_limit=CONF.osapi_max_limit,
|
||||
original_call=common.get_pagination_params):
|
||||
return original_call(params, max_limit)
|
||||
|
||||
def _get_limit_param(params, max_limit=CONF.osapi_max_limit,
|
||||
original_call=common._get_limit_param):
|
||||
return original_call(params, max_limit)
|
||||
|
||||
with mock.patch.object(common, 'get_pagination_params',
|
||||
get_pagination_params), \
|
||||
mock.patch.object(common, '_get_limit_param',
|
||||
_get_limit_param), \
|
||||
mock.patch.object(common.ViewBuilder, '_generate_next_link',
|
||||
return_value=link_return):
|
||||
res_dict = fn(req)
|
||||
self.assertEqual(item_count, len(res_dict['volumes']))
|
||||
self.assertEqual(should_link_exist, 'volumes_links' in res_dict)
|
||||
|
||||
def test_volume_default_limit(self):
|
||||
self.stubs.UnsetAll()
|
||||
self._create_db_volumes(3)
|
||||
|
||||
# Verify both the index and detail queries
|
||||
api_keys = ['volumes', 'volumes/detail']
|
||||
fns = [self.controller.index, self.controller.detail]
|
||||
for detailed in (True, False):
|
||||
# Number of volumes less than max, do not include
|
||||
self._validate_next_link(detailed, item_count=3, osapi_max_limit=4,
|
||||
limit=None, should_link_exist=False)
|
||||
|
||||
# Number of volumes equals the max, next link not included
|
||||
def stub_volume_get_all(context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
vols = [stubs.stub_volume(i)
|
||||
for i in range(CONF.osapi_max_limit)]
|
||||
if limit is None or limit >= len(vols):
|
||||
return vols
|
||||
return vols[:limit]
|
||||
self.stubs.Set(db, 'volume_get_all', stub_volume_get_all)
|
||||
for key, fn in zip(api_keys, fns):
|
||||
req = fakes.HTTPRequest.blank('/v2/%s?all_tenants=1' % key,
|
||||
use_admin_context=True)
|
||||
res_dict = fn(req)
|
||||
self.assertEqual(len(res_dict['volumes']), CONF.osapi_max_limit)
|
||||
self.assertFalse('volumes_links' in res_dict)
|
||||
# Number of volumes equals the max, next link will be included
|
||||
self._validate_next_link(detailed, item_count=3, osapi_max_limit=3,
|
||||
limit=None, should_link_exist=True)
|
||||
|
||||
# Number of volumes less than max, do not include
|
||||
def stub_volume_get_all2(context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
vols = [stubs.stub_volume(i)
|
||||
for i in range(100)]
|
||||
if limit is None or limit >= len(vols):
|
||||
return vols
|
||||
return vols[:limit]
|
||||
self.stubs.Set(db, 'volume_get_all', stub_volume_get_all2)
|
||||
for key, fn in zip(api_keys, fns):
|
||||
req = fakes.HTTPRequest.blank('/v2/%s?all_tenants=1' % key,
|
||||
use_admin_context=True)
|
||||
res_dict = fn(req)
|
||||
self.assertEqual(100, len(res_dict['volumes']))
|
||||
self.assertFalse('volumes_links' in res_dict)
|
||||
# Number of volumes more than the max, include next link
|
||||
self._validate_next_link(detailed, item_count=2, osapi_max_limit=2,
|
||||
limit=None, should_link_exist=True)
|
||||
|
||||
# Number of volumes more than the max, include next link
|
||||
def stub_volume_get_all3(context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
vols = [stubs.stub_volume(i)
|
||||
for i in range(CONF.osapi_max_limit + 100)]
|
||||
if limit is None or limit >= len(vols):
|
||||
return vols
|
||||
return vols[:limit]
|
||||
self.stubs.Set(db, 'volume_get_all', stub_volume_get_all3)
|
||||
for key, fn in zip(api_keys, fns):
|
||||
req = fakes.HTTPRequest.blank('/v2/%s?all_tenants=1' % key,
|
||||
use_admin_context=True)
|
||||
res_dict = fn(req)
|
||||
self.assertEqual(CONF.osapi_max_limit, len(res_dict['volumes']))
|
||||
volumes_links = res_dict['volumes_links']
|
||||
_verify_links(volumes_links, key)
|
||||
# Pass a limit that is greater than the max and the total number of
|
||||
# volumes, ensure only the maximum is returned and that the next
|
||||
# link is present.
|
||||
for key, fn in zip(api_keys, fns):
|
||||
req = fakes.HTTPRequest.blank('/v2/%s?all_tenants=1&limit=%d'
|
||||
% (key, CONF.osapi_max_limit * 2),
|
||||
use_admin_context=True)
|
||||
res_dict = fn(req)
|
||||
self.assertEqual(CONF.osapi_max_limit, len(res_dict['volumes']))
|
||||
volumes_links = res_dict['volumes_links']
|
||||
_verify_links(volumes_links, key)
|
||||
# Limit lower than max but doesn't limit, no next link
|
||||
self._validate_next_link(detailed, item_count=3, osapi_max_limit=5,
|
||||
limit=4, should_link_exist=False)
|
||||
|
||||
# Limit lower than max and limits, we have next link
|
||||
self._validate_next_link(detailed, item_count=2, osapi_max_limit=4,
|
||||
limit=2, should_link_exist=True)
|
||||
|
||||
# Limit higher than max and max limits, we have next link
|
||||
self._validate_next_link(detailed, item_count=2, osapi_max_limit=2,
|
||||
limit=4, should_link_exist=True)
|
||||
|
||||
# Limit higher than max but none of them limiting, no next link
|
||||
self._validate_next_link(detailed, item_count=3, osapi_max_limit=4,
|
||||
limit=5, should_link_exist=False)
|
||||
|
||||
def test_volume_list_default_filters(self):
|
||||
"""Tests that the default filters from volume.api.API.get_all are set.
|
||||
|
@ -1059,7 +1037,8 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all_by_project(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False,
|
||||
offset=0):
|
||||
self.assertEqual(True, filters['no_migration_targets'])
|
||||
self.assertFalse('all_tenants' in filters)
|
||||
return [stubs.stub_volume(1, display_name='vol1')]
|
||||
|
@ -1067,7 +1046,7 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all(context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False, offset=0):
|
||||
return []
|
||||
self.stubs.Set(db, 'volume_get_all_by_project',
|
||||
stub_volume_get_all_by_project)
|
||||
|
@ -1085,14 +1064,15 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all_by_project2(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False,
|
||||
offset=0):
|
||||
self.assertFalse('no_migration_targets' in filters)
|
||||
return [stubs.stub_volume(1, display_name='vol2')]
|
||||
|
||||
def stub_volume_get_all2(context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False, offset=0):
|
||||
return []
|
||||
self.stubs.Set(db, 'volume_get_all_by_project',
|
||||
stub_volume_get_all_by_project2)
|
||||
|
@ -1108,13 +1088,14 @@ class VolumeApiTest(test.TestCase):
|
|||
def stub_volume_get_all_by_project3(context, project_id, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False,
|
||||
offset=0):
|
||||
return []
|
||||
|
||||
def stub_volume_get_all3(context, marker, limit,
|
||||
sort_keys=None, sort_dirs=None,
|
||||
filters=None,
|
||||
viewable_admin_meta=False):
|
||||
viewable_admin_meta=False, offset=0):
|
||||
self.assertFalse('no_migration_targets' in filters)
|
||||
self.assertFalse('all_tenants' in filters)
|
||||
return [stubs.stub_volume(1, display_name='vol3')]
|
||||
|
@ -1311,10 +1292,10 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
context, None, None,
|
||||
context, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'display_name': 'Volume-573108026'},
|
||||
viewable_admin_meta=True)
|
||||
viewable_admin_meta=True, offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_filter_with_list(self, get_all):
|
||||
|
@ -1325,9 +1306,10 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
context, None, None,
|
||||
context, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'id': ['1', '2', '3']}, viewable_admin_meta=True)
|
||||
filters={'id': ['1', '2', '3']}, viewable_admin_meta=True,
|
||||
offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_filter_with_expression(self, get_all):
|
||||
|
@ -1338,9 +1320,9 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
context, None, None,
|
||||
context, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'display_name': 'd-'}, viewable_admin_meta=True)
|
||||
filters={'display_name': 'd-'}, viewable_admin_meta=True, offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_filter_with_status(self, get_all):
|
||||
|
@ -1351,9 +1333,10 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
ctxt, None, None,
|
||||
ctxt, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'status': 'available'}, viewable_admin_meta=True)
|
||||
filters={'status': 'available'}, viewable_admin_meta=True,
|
||||
offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_filter_with_metadata(self, get_all):
|
||||
|
@ -1364,10 +1347,10 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
ctxt, None, None,
|
||||
ctxt, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'metadata': {'fake_key': 'fake_value'}},
|
||||
viewable_admin_meta=True)
|
||||
viewable_admin_meta=True, offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_filter_with_availability_zone(self, get_all):
|
||||
|
@ -1378,9 +1361,10 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
ctxt, None, None,
|
||||
ctxt, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'availability_zone': 'nova'}, viewable_admin_meta=True)
|
||||
filters={'availability_zone': 'nova'}, viewable_admin_meta=True,
|
||||
offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_filter_with_invalid_filter(self, get_all):
|
||||
|
@ -1392,9 +1376,10 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
ctxt, None, None,
|
||||
ctxt, None, CONF.osapi_max_limit,
|
||||
sort_keys=['created_at'], sort_dirs=['desc'],
|
||||
filters={'availability_zone': 'nova'}, viewable_admin_meta=True)
|
||||
filters={'availability_zone': 'nova'}, viewable_admin_meta=True,
|
||||
offset=0)
|
||||
|
||||
@mock.patch('cinder.volume.api.API.get_all')
|
||||
def test_get_volumes_sort_by_name(self, get_all):
|
||||
|
@ -1407,9 +1392,9 @@ class VolumeApiTest(test.TestCase):
|
|||
self.controller._view_builder.detail_list = mock.Mock()
|
||||
self.controller._get_volumes(req, True)
|
||||
get_all.assert_called_once_with(
|
||||
ctxt, None, None,
|
||||
ctxt, None, CONF.osapi_max_limit,
|
||||
sort_dirs=['desc'], viewable_admin_meta=True,
|
||||
sort_keys=['display_name'], filters={})
|
||||
sort_keys=['display_name'], filters={}, offset=0)
|
||||
|
||||
def test_get_volume_filter_options_using_config(self):
|
||||
self.override_config('query_volume_filters', ['name', 'status',
|
||||
|
|
|
@ -441,7 +441,8 @@ class API(base.Base):
|
|||
return b
|
||||
|
||||
def get_all(self, context, marker=None, limit=None, sort_keys=None,
|
||||
sort_dirs=None, filters=None, viewable_admin_meta=False):
|
||||
sort_dirs=None, filters=None, viewable_admin_meta=False,
|
||||
offset=None):
|
||||
check_policy(context, 'get_all')
|
||||
|
||||
if filters is None:
|
||||
|
@ -475,7 +476,8 @@ class API(base.Base):
|
|||
volumes = self.db.volume_get_all(context, marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters)
|
||||
filters=filters,
|
||||
offset=offset)
|
||||
else:
|
||||
if viewable_admin_meta:
|
||||
context = context.elevated()
|
||||
|
@ -484,7 +486,8 @@ class API(base.Base):
|
|||
marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters)
|
||||
filters=filters,
|
||||
offset=offset)
|
||||
|
||||
LOG.info(_LI("Get all volumes completed successfully."))
|
||||
return volumes
|
||||
|
|
Loading…
Reference in New Issue