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(params, max_limit=CONF.osapi_max_limit):
+ """Extract integer limit from request's dictionary or fail.
-def _get_limit_param(request):
- """Extract integer limit from request 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):
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)
-
- limit = params.get('limit', max_limit)
- marker = params.get('marker')
+ marker, limit, __ = get_pagination_params(request.GET.copy(), max_limit)
- limit = min(max_limit, limit)
start_index = 0
if marker:
start_index = -1
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
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,
"""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,
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,
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):
# 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.
if limit is not None:
query = query.limit(limit)
+ if offset:
+ query = query.offset(offset)
+
return query
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):
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):
@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
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 []
@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
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 []
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
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,
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):
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):
"""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):
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,
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):
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'),
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')]
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')]
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
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
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)
def test_volume_create(self):
self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get)
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'),
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)
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)
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'),
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)
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)
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]
+
+ 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 _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)
+ 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.
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')]
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)
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)
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')]
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):
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):
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):
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):
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):
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):
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):
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',
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:
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()
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