On Volume list only retrieve needed data from DB

Currently when there is no limit set on a volume list query we retrieve
all volumes and then limit them locally using osapi_max_limit.  Similar
thing happens when we are using the marker for next pages, we get all
volumes from that marker until the last volume and then limit it
locally.

We should be limiting it on the DB side so we only retrieve the data we
are actually going to return to the API caller.

This patch always limits the data retrieved from the DB and for the
offset to keep working as it was before we need to do the offset on the
DB side as well.

For reference some tests were performed:

On a deployment with 60,000 volumes, 370,000 volume_metadata items and
240,000 volume_glance_metadata items in cinder db.  Before the patch
this will use nearly 10G memory.  With the patch we will just use about
500M.

Co-Authored-By: wangxiyuan <wangxiyuan@huawei.com>
Closes-bug:#1483165i
Change-Id: Ie903e546074fe118299e8e1acfb9c88c8a10d78c
This commit is contained in:
Gorka Eguileor 2015-08-12 19:09:53 +02:00
parent 7e84f149e8
commit 28879f9a78
11 changed files with 313 additions and 351 deletions

View File

@ -69,42 +69,64 @@ def validate_key_names(key_names_list):
return True return True
def get_pagination_params(request): def get_pagination_params(params, max_limit=CONF.osapi_max_limit):
"""Return marker, limit tuple from request. """Return marker, limit, offset 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.
: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 = {} limit = _get_limit_param(params, max_limit)
if 'limit' in request.GET: marker = _get_marker_param(params)
params['limit'] = _get_limit_param(request) offset = _get_offset_param(params)
if 'marker' in request.GET: return marker, limit, offset
params['marker'] = _get_marker_param(request)
return params
def _get_limit_param(request): def _get_limit_param(params, max_limit=CONF.osapi_max_limit):
"""Extract integer limit from request or fail.""" """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: try:
limit = int(request.GET['limit']) limit = int(params.pop('limit', max_limit))
except ValueError: except ValueError:
msg = _('limit param must be an integer') msg = _('limit param must be an integer')
raise webob.exc.HTTPBadRequest(explanation=msg) raise webob.exc.HTTPBadRequest(explanation=msg)
if limit < 0: if limit < 0:
msg = _('limit param must be positive') msg = _('limit param must be positive')
raise webob.exc.HTTPBadRequest(explanation=msg) raise webob.exc.HTTPBadRequest(explanation=msg)
limit = min(limit, max_limit)
return limit return limit
def _get_marker_param(request): def _get_marker_param(params):
"""Extract marker id from request or fail.""" """Extract marker id from request's dictionary (defaults to None)."""
return request.GET['marker'] 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): 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. will cause exc.HTTPBadRequest() exceptions to be raised.
:kwarg max_limit: The maximum number of items to return from 'items' :kwarg max_limit: The maximum number of items to return from 'items'
""" """
try: marker, limit, offset = get_pagination_params(request.GET.copy(),
offset = int(request.GET.get('offset', 0)) max_limit)
except ValueError: range_end = offset + (limit or max_limit)
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
return items[offset:range_end] return items[offset:range_end]
def limited_by_marker(items, request, max_limit=CONF.osapi_max_limit): def limited_by_marker(items, request, max_limit=CONF.osapi_max_limit):
"""Return a slice of items according to the requested marker and 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 start_index = 0
if marker: if marker:
start_index = -1 start_index = -1
@ -289,19 +288,19 @@ class ViewBuilder(object):
str(identifier)) str(identifier))
def _get_collection_links(self, request, items, collection_name, 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. """Retrieve 'next' link, if applicable.
The next link is included if: The next link is included if we are returning as many items as we can,
1) 'limit' param is specified and equal to or less than the given the restrictions of limit optional request parameter and
number of items. osapi_max_limit configuration parameter as long as we are returning
2) 'limit' param is specified and both the limit and the number some elements.
of items are greater than CONF.osapi_max_limit, even if limit
is greater than the number of items. So we return next link if:
3) 'limit' param is NOT specified and the number of items is
greater than CONF.osapi_max_limit. 1) 'limit' param is specified and equal to the number of items.
Notes: The case limit equals to 0 or CONF.osapi_max_limit equals to 0 2) 'limit' param is NOT specified and the number of items is
is not included in the above conditions. equal to CONF.osapi_max_limit.
:param request: API request :param request: API request
:param items: List of collection items :param items: List of collection items
@ -313,25 +312,12 @@ class ViewBuilder(object):
to generate the next link marker for a pagination query to generate the next link marker for a pagination query
:returns links :returns links
""" """
limit = request.params.get("limit", None) item_count = item_count or len(items)
if limit is None or int(limit) > CONF.osapi_max_limit: limit = _get_limit_param(request.GET.copy())
# If limit is not set in the request or greater than if len(items) and limit <= item_count:
# 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, return self._generate_next_link(items, id_key, request,
collection_name) collection_name)
return [] return []
def _generate_next_link(self, items, id_key, request, def _generate_next_link(self, items, id_key, request,

View File

@ -30,12 +30,12 @@ class ViewBuilder(common.ViewBuilder):
"""Initialize view builder.""" """Initialize view builder."""
super(ViewBuilder, self).__init__() 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.""" """Show a list of volumes without many details."""
return self._list_view(self.summary, request, volumes, return self._list_view(self.summary, request, volumes,
volume_count) 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.""" """Detailed view of a list of volumes."""
return self._list_view(self.detail, request, volumes, return self._list_view(self.detail, request, volumes,
volume_count, volume_count,

View File

@ -227,10 +227,8 @@ class VolumeController(wsgi.Controller):
context = req.environ['cinder.context'] context = req.environ['cinder.context']
params = req.params.copy() params = req.params.copy()
marker = params.pop('marker', None) marker, limit, offset = common.get_pagination_params(params)
limit = params.pop('limit', None)
sort_keys, sort_dirs = common.get_sort_params(params) sort_keys, sort_dirs = common.get_sort_params(params)
params.pop('offset', None)
filters = params filters = params
utils.remove_invalid_filter_options(context, utils.remove_invalid_filter_options(context,
@ -255,23 +253,20 @@ class VolumeController(wsgi.Controller):
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs, sort_dirs=sort_dirs,
filters=filters, filters=filters,
viewable_admin_meta=True) viewable_admin_meta=True,
offset=offset)
volumes = [dict(vol) for vol in volumes] volumes = [dict(vol) for vol in volumes]
for volume in volumes: for volume in volumes:
utils.add_visible_admin_metadata(volume) utils.add_visible_admin_metadata(volume)
limited_list = common.limited(volumes, req) req.cache_db_volumes(volumes)
volume_count = len(volumes)
req.cache_db_volumes(limited_list)
if is_detail: if is_detail:
volumes = self._view_builder.detail_list(req, limited_list, volumes = self._view_builder.detail_list(req, volumes)
volume_count)
else: else:
volumes = self._view_builder.summary_list(req, limited_list, volumes = self._view_builder.summary_list(req, volumes)
volume_count)
return volumes return volumes
def _image_uuid_from_ref(self, image_ref, context): def _image_uuid_from_ref(self, image_ref, context):

View File

@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
# copied from glance/db/sqlalchemy/api.py # copied from glance/db/sqlalchemy/api.py
def paginate_query(query, model, limit, sort_keys, marker=None, 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. """Returns a query with sorting / pagination criteria added.
Pagination works by requiring a unique sort_key, specified by sort_keys. 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: if limit is not None:
query = query.limit(limit) query = query.limit(limit)
if offset:
query = query.offset(offset)
return query return query

View File

@ -206,10 +206,11 @@ def volume_get(context, volume_id):
def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None, def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
filters=None): filters=None, offset=None):
"""Get all volumes.""" """Get all volumes."""
return IMPL.volume_get_all(context, marker, limit, sort_keys=sort_keys, 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_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, 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.""" """Get all volumes belonging to a project."""
return IMPL.volume_get_all_by_project(context, project_id, marker, limit, return IMPL.volume_get_all_by_project(context, project_id, marker, limit,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs, sort_dirs=sort_dirs,
filters=filters) filters=filters,
offset=offset)
def volume_get_iscsi_target_num(context, volume_id): def volume_get_iscsi_target_num(context, volume_id):

View File

@ -1339,7 +1339,7 @@ def volume_get(context, volume_id):
@require_admin_context @require_admin_context
def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None, def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
filters=None): filters=None, offset=None):
"""Retrieves all volumes. """Retrieves all volumes.
If no sort parameters are specified then the returned volumes are sorted If no sort parameters are specified then the returned volumes are sorted
@ -1364,7 +1364,7 @@ def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
with session.begin(): with session.begin():
# Generate the query # Generate the query
query = _generate_paginate_query(context, session, marker, limit, 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 # No volumes would match, return empty list
if query is None: if query is None:
return [] return []
@ -1429,7 +1429,8 @@ def volume_get_all_by_group(context, group_id, filters=None):
@require_context @require_context
def volume_get_all_by_project(context, project_id, marker, limit, 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. """Retrieves all volumes in a project.
If no sort parameters are specified then the returned volumes are sorted If no sort parameters are specified then the returned volumes are sorted
@ -1459,7 +1460,7 @@ def volume_get_all_by_project(context, project_id, marker, limit,
filters['project_id'] = project_id filters['project_id'] = project_id
# Generate the query # Generate the query
query = _generate_paginate_query(context, session, marker, limit, 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 # No volumes would match, return empty list
if query is None: if query is None:
return [] return []
@ -1467,7 +1468,7 @@ def volume_get_all_by_project(context, project_id, marker, limit,
def _generate_paginate_query(context, session, marker, limit, sort_keys, 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. """Generate the query to include the filters and the paginate options.
Returns a query with sorting / pagination criteria added or None Returns a query with sorting / pagination criteria added or None
@ -1486,6 +1487,7 @@ def _generate_paginate_query(context, session, marker, limit, sort_keys,
or sets cause an 'IN' operation, while exact matching or sets cause an 'IN' operation, while exact matching
is used for other values, see _process_volume_filters is used for other values, see _process_volume_filters
function for more information function for more information
:param offset: number of items to skip
:returns: updated query or None :returns: updated query or None
""" """
sort_keys, sort_dirs = process_sort_params(sort_keys, sort_keys, sort_dirs = process_sort_params(sort_keys,
@ -1505,7 +1507,8 @@ def _generate_paginate_query(context, session, marker, limit, sort_keys,
return sqlalchemyutils.paginate_query(query, models.Volume, limit, return sqlalchemyutils.paginate_query(query, models.Volume, limit,
sort_keys, sort_keys,
marker=marker_volume, marker=marker_volume,
sort_dirs=sort_dirs) sort_dirs=sort_dirs,
offset=offset)
def _process_volume_filters(query, filters): def _process_volume_filters(query, filters):

View File

@ -22,12 +22,15 @@ from testtools import matchers
import webob import webob
import webob.exc import webob.exc
from oslo_config import cfg
from cinder.api import common from cinder.api import common
from cinder import test from cinder import test
NS = "{http://docs.openstack.org/compute/api/v1.1}" NS = "{http://docs.openstack.org/compute/api/v1.1}"
ATOMNS = "{http://www.w3.org/2005/Atom}" ATOMNS = "{http://www.w3.org/2005/Atom}"
CONF = cfg.CONF
class LimiterTest(test.TestCase): class LimiterTest(test.TestCase):
@ -172,37 +175,45 @@ class PaginationParamsTest(test.TestCase):
"""Test nonnumerical limit param.""" """Test nonnumerical limit param."""
req = webob.Request.blank('/?limit=hello') req = webob.Request.blank('/?limit=hello')
self.assertRaises( 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): def test_no_params(self):
"""Test no params.""" """Test no params."""
req = webob.Request.blank('/') 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): def test_valid_marker(self):
"""Test valid marker param.""" """Test valid marker param."""
req = webob.Request.blank( marker = '263abb28-1de6-412f-b00b-f0ee0c4333c2'
'/?marker=263abb28-1de6-412f-b00b-f0ee0c4333c2') req = webob.Request.blank('/?marker=' + marker)
self.assertEqual({'marker': '263abb28-1de6-412f-b00b-f0ee0c4333c2'}, expected = (marker, CONF.osapi_max_limit, 0)
common.get_pagination_params(req)) self.assertEqual(expected,
common.get_pagination_params(req.GET.copy()))
def test_valid_limit(self): def test_valid_limit(self):
"""Test valid limit param.""" """Test valid limit param."""
req = webob.Request.blank('/?limit=10') 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): def test_invalid_limit(self):
"""Test invalid limit param.""" """Test invalid limit param."""
req = webob.Request.blank('/?limit=-2') req = webob.Request.blank('/?limit=-2')
self.assertRaises( 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): def test_valid_limit_and_marker(self):
"""Test valid limit and marker parameters.""" """Test valid limit and marker parameters."""
marker = '263abb28-1de6-412f-b00b-f0ee0c4333c2' marker = '263abb28-1de6-412f-b00b-f0ee0c4333c2'
req = webob.Request.blank('/?limit=20&marker=%s' % marker) req = webob.Request.blank('/?limit=20&marker=%s' % marker)
self.assertEqual({'marker': marker, 'limit': 20}, expected = (marker, 20, 0)
common.get_pagination_params(req)) self.assertEqual(expected,
common.get_pagination_params(req.GET.copy()))
class SortParamUtilsTest(test.TestCase): class SortParamUtilsTest(test.TestCase):
@ -354,22 +365,32 @@ class MiscFunctionsTest(test.TestCase):
class TestCollectionLinks(test.TestCase): class TestCollectionLinks(test.TestCase):
"""Tests the _get_collection_links method.""" """Tests the _get_collection_links method."""
def _validate_next_link(self, href_link_mock, item_count, def _validate_next_link(self, item_count, osapi_max_limit, limit,
osapi_max_limit, limit, should_link_exist): should_link_exist):
req = mock.MagicMock() req = webob.Request.blank('/?limit=%s' % limit if limit else '/')
href_link_mock.return_value = [{"rel": "next", link_return = [{"rel": "next", "href": "fake_link"}]
"href": "fake_link"}]
self.flags(osapi_max_limit=osapi_max_limit) self.flags(osapi_max_limit=osapi_max_limit)
if limit is None: if limit is None:
params = mock.PropertyMock(return_value=dict())
limited_list_size = min(item_count, osapi_max_limit) limited_list_size = min(item_count, osapi_max_limit)
else: 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)] limited_list = [{"uuid": str(i)} for i in range(limited_list_size)]
type(req).params = params
builder = common.ViewBuilder() builder = common.ViewBuilder()
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, results = builder._get_collection_links(req, limited_list,
mock.sentinel.coll_key, mock.sentinel.coll_key,
item_count, "uuid") item_count, "uuid")
@ -382,171 +403,133 @@ class TestCollectionLinks(test.TestCase):
self.assertFalse(href_link_mock.called) self.assertFalse(href_link_mock.called)
self.assertThat(results, matchers.HasLength(0)) self.assertThat(results, matchers.HasLength(0))
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_equals_osapi_max_no_limit(self):
def test_items_equals_osapi_max_no_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 5 osapi_max_limit = 5
limit = None limit = None
should_link_exist = False should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_equals_osapi_max_greater_than_limit(self):
def test_items_equals_osapi_max_greater_than_limit(self,
href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 5 osapi_max_limit = 5
limit = 4 limit = 4
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_equals_osapi_max_equals_limit(self):
def test_items_equals_osapi_max_equals_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 5 osapi_max_limit = 5
limit = 5 limit = 5
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_equals_osapi_max_less_than_limit(self):
def test_items_equals_osapi_max_less_than_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 5 osapi_max_limit = 5
limit = 6 limit = 6
should_link_exist = False should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_less_than_osapi_max_no_limit(self):
def test_items_less_than_osapi_max_no_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 7 osapi_max_limit = 7
limit = None limit = None
should_link_exist = False should_link_exist = False
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_limit_less_than_items_less_than_osapi_max(self):
def test_limit_less_than_items_less_than_osapi_max(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 7 osapi_max_limit = 7
limit = 4 limit = 4
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_limit_equals_items_less_than_osapi_max(self):
def test_limit_equals_items_less_than_osapi_max(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 7 osapi_max_limit = 7
limit = 5 limit = 5
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_less_than_limit_less_than_osapi_max(self):
def test_items_less_than_limit_less_than_osapi_max(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 7 osapi_max_limit = 7
limit = 6 limit = 6
should_link_exist = False should_link_exist = False
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_less_than_osapi_max_equals_limit(self):
def test_items_less_than_osapi_max_equals_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 7 osapi_max_limit = 7
limit = 7 limit = 7
should_link_exist = False should_link_exist = False
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_less_than_osapi_max_less_than_limit(self):
def test_items_less_than_osapi_max_less_than_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 7 osapi_max_limit = 7
limit = 8 limit = 8
should_link_exist = False should_link_exist = False
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_greater_than_osapi_max_no_limit(self):
def test_items_greater_than_osapi_max_no_limit(self, href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 3 osapi_max_limit = 3
limit = None limit = None
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_limit_less_than_items_greater_than_osapi_max(self):
def test_limit_less_than_items_greater_than_osapi_max(self,
href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 3 osapi_max_limit = 3
limit = 2 limit = 2
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_greater_than_osapi_max_equals_limit(self):
def test_items_greater_than_osapi_max_equals_limit(self,
href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 3 osapi_max_limit = 3
limit = 3 limit = 3
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_greater_than_limit_greater_than_osapi_max(self):
def test_items_greater_than_limit_greater_than_osapi_max(self,
href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 3 osapi_max_limit = 3
limit = 4 limit = 4
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_items_equals_limit_greater_than_osapi_max(self):
def test_items_equals_limit_greater_than_osapi_max(self,
href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 3 osapi_max_limit = 3
limit = 5 limit = 5
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
@mock.patch('cinder.api.common.ViewBuilder._generate_next_link') def test_limit_greater_than_items_greater_than_osapi_max(self):
def test_limit_greater_than_items_greater_than_osapi_max(self,
href_link_mock):
item_count = 5 item_count = 5
osapi_max_limit = 3 osapi_max_limit = 3
limit = 6 limit = 6
should_link_exist = True should_link_exist = True
self._validate_next_link(href_link_mock, item_count, self._validate_next_link(item_count, osapi_max_limit, limit,
osapi_max_limit, should_link_exist)
limit, should_link_exist)
class LinkPrefixTest(test.TestCase): class LinkPrefixTest(test.TestCase):

View File

@ -620,7 +620,8 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all_by_project(context, project_id, marker, def stub_volume_get_all_by_project(context, project_id, marker,
limit, sort_keys=None, limit, sort_keys=None,
sort_dirs=None, filters=None, sort_dirs=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False,
offset=None):
return [ return [
stubs.stub_volume(1, display_name='vol1'), stubs.stub_volume(1, display_name='vol1'),
stubs.stub_volume(2, display_name='vol2'), stubs.stub_volume(2, display_name='vol2'),

View File

@ -139,7 +139,7 @@ def stub_volume_get_db(context, volume_id):
def stub_volume_get_all(context, search_opts=None, marker=None, limit=None, def stub_volume_get_all(context, search_opts=None, marker=None, limit=None,
sort_keys=None, sort_dirs=None, filters=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'), return [stub_volume(100, project_id='fake'),
stub_volume(101, project_id='superfake'), stub_volume(101, project_id='superfake'),
stub_volume(102, project_id='superduperfake')] stub_volume(102, project_id='superduperfake')]
@ -148,7 +148,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, def stub_volume_get_all_by_project(self, context, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False, offset=None):
filters = filters or {} filters = filters or {}
return [stub_volume_get(self, context, '1')] return [stub_volume_get(self, context, '1')]

View File

@ -25,6 +25,7 @@ from six.moves import range
from six.moves import urllib from six.moves import urllib
import webob import webob
from cinder.api import common
from cinder.api import extensions from cinder.api import extensions
from cinder.api.v2 import volumes from cinder.api.v2 import volumes
from cinder import consistencygroup as consistencygroupAPI from cinder import consistencygroup as consistencygroupAPI
@ -36,6 +37,7 @@ from cinder.tests.unit.api import fakes
from cinder.tests.unit.api.v2 import stubs from cinder.tests.unit.api.v2 import stubs
from cinder.tests.unit import fake_notifier from cinder.tests.unit import fake_notifier
from cinder.tests.unit.image import fake as fake_image from cinder.tests.unit.image import fake as fake_image
from cinder.tests.unit import utils
from cinder.volume import api as volume_api from cinder.volume import api as volume_api
CONF = cfg.CONF CONF = cfg.CONF
@ -60,6 +62,7 @@ class VolumeApiTest(test.TestCase):
self.stubs.Set(db, 'service_get_all_by_topic', self.stubs.Set(db, 'service_get_all_by_topic',
stubs.stub_service_get_all_by_topic) stubs.stub_service_get_all_by_topic)
self.maxDiff = None self.maxDiff = None
self.ctxt = context.RequestContext('admin', 'fakeproject', True)
def test_volume_create(self): def test_volume_create(self):
self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get) self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get)
@ -731,7 +734,8 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all_by_project(context, project_id, marker, limit, def stub_volume_get_all_by_project(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False,
offset=0):
return [ return [
stubs.stub_volume(1, display_name='vol1'), stubs.stub_volume(1, display_name='vol1'),
stubs.stub_volume(2, display_name='vol2'), stubs.stub_volume(2, display_name='vol2'),
@ -776,13 +780,13 @@ class VolumeApiTest(test.TestCase):
def test_volume_index_limit_negative(self): def test_volume_index_limit_negative(self):
req = fakes.HTTPRequest.blank('/v2/volumes?limit=-1') req = fakes.HTTPRequest.blank('/v2/volumes?limit=-1')
self.assertRaises(exception.Invalid, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, self.controller.index,
req) req)
def test_volume_index_limit_non_int(self): def test_volume_index_limit_non_int(self):
req = fakes.HTTPRequest.blank('/v2/volumes?limit=a') req = fakes.HTTPRequest.blank('/v2/volumes?limit=a')
self.assertRaises(exception.Invalid, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, self.controller.index,
req) req)
@ -797,32 +801,29 @@ class VolumeApiTest(test.TestCase):
self.assertEqual(1, len(volumes)) self.assertEqual(1, len(volumes))
self.assertEqual('1', volumes[0]['id']) self.assertEqual('1', volumes[0]['id'])
def test_volume_index_limit_offset(self): def _create_db_volumes(self, num_volumes):
def stub_volume_get_all_by_project(context, project_id, marker, limit, volumes = [utils.create_volume(self.ctxt, display_name='vol%s' % i)
sort_keys=None, sort_dirs=None, for i in range(num_volumes)]
filters=None, for vol in volumes:
viewable_admin_meta=False): self.addCleanup(db.volume_destroy, self.ctxt, vol.id)
return [ volumes.reverse()
stubs.stub_volume(1, display_name='vol1'), return volumes
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 test_volume_index_limit_offset(self):
created_volumes = self._create_db_volumes(2)
req = fakes.HTTPRequest.blank('/v2/volumes?limit=2&offset=1') req = fakes.HTTPRequest.blank('/v2/volumes?limit=2&offset=1')
res_dict = self.controller.index(req) res_dict = self.controller.index(req)
volumes = res_dict['volumes'] volumes = res_dict['volumes']
self.assertEqual(1, len(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') req = fakes.HTTPRequest.blank('/v2/volumes?limit=-1&offset=1')
self.assertRaises(exception.InvalidInput, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, self.controller.index,
req) req)
req = fakes.HTTPRequest.blank('/v2/volumes?limit=a&offset=1') req = fakes.HTTPRequest.blank('/v2/volumes?limit=a&offset=1')
self.assertRaises(exception.InvalidInput, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.index, self.controller.index,
req) req)
@ -830,7 +831,8 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all_by_project(context, project_id, marker, limit, def stub_volume_get_all_by_project(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False,
offset=0):
return [ return [
stubs.stub_volume(1, display_name='vol1'), stubs.stub_volume(1, display_name='vol1'),
stubs.stub_volume(2, display_name='vol2'), stubs.stub_volume(2, display_name='vol2'),
@ -867,13 +869,13 @@ class VolumeApiTest(test.TestCase):
def test_volume_detail_limit_negative(self): def test_volume_detail_limit_negative(self):
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=-1') req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=-1')
self.assertRaises(exception.Invalid, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.detail, self.controller.detail,
req) req)
def test_volume_detail_limit_non_int(self): def test_volume_detail_limit_non_int(self):
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=a') req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=a')
self.assertRaises(exception.Invalid, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.detail, self.controller.detail,
req) req)
@ -889,38 +891,27 @@ class VolumeApiTest(test.TestCase):
self.assertEqual('1', volumes[0]['id']) self.assertEqual('1', volumes[0]['id'])
def test_volume_detail_limit_offset(self): def test_volume_detail_limit_offset(self):
def stub_volume_get_all_by_project(context, project_id, marker, limit, created_volumes = self._create_db_volumes(2)
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)
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=2&offset=1') req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=2&offset=1')
res_dict = self.controller.detail(req) res_dict = self.controller.detail(req)
volumes = res_dict['volumes'] volumes = res_dict['volumes']
self.assertEqual(1, len(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', req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=2&offset=1',
use_admin_context=True) use_admin_context=True)
res_dict = self.controller.detail(req) res_dict = self.controller.detail(req)
volumes = res_dict['volumes'] volumes = res_dict['volumes']
self.assertEqual(1, len(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') req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=-1&offset=1')
self.assertRaises(exception.InvalidInput, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.detail, self.controller.detail,
req) req)
req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=a&offset=1') req = fakes.HTTPRequest.blank('/v2/volumes/detail?limit=a&offset=1')
self.assertRaises(exception.InvalidInput, self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.detail, self.controller.detail,
req) req)
@ -933,84 +924,71 @@ class VolumeApiTest(test.TestCase):
expected = {'volumes': []} expected = {'volumes': []}
self.assertEqual(expected, res_dict) self.assertEqual(expected, res_dict)
def test_volume_default_limit(self): def _validate_next_link(self, detailed, item_count, osapi_max_limit, limit,
self.stubs.Set(volume_api.API, 'get', stubs.stub_volume_get) 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): req_string = '/v2/%s?all_tenants=1' % key
"""Verify next link and url.""" if limit:
self.assertEqual('next', links[0]['rel']) req_string += '&limit=%s' % limit
href_parts = urllib.parse.urlparse(links[0]['href']) req = fakes.HTTPRequest.blank(req_string, use_admin_context=True)
self.assertEqual('/v2/fakeproject/%s' % key, href_parts.path)
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 # Verify both the index and detail queries
api_keys = ['volumes', 'volumes/detail'] for detailed in (True, False):
fns = [self.controller.index, self.controller.detail]
# 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 less than max, do not include # Number of volumes less than max, do not include
def stub_volume_get_all2(context, marker, limit, self._validate_next_link(detailed, item_count=3, osapi_max_limit=4,
sort_keys=None, sort_dirs=None, limit=None, should_link_exist=False)
filters=None,
viewable_admin_meta=False): # Number of volumes equals the max, next link will be included
vols = [stubs.stub_volume(i) self._validate_next_link(detailed, item_count=3, osapi_max_limit=3,
for i in range(100)] limit=None, should_link_exist=True)
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 # Number of volumes more than the max, include next link
def stub_volume_get_all3(context, marker, limit, self._validate_next_link(detailed, item_count=2, osapi_max_limit=2,
sort_keys=None, sort_dirs=None, limit=None, should_link_exist=True)
filters=None,
viewable_admin_meta=False): # Limit lower than max but doesn't limit, no next link
vols = [stubs.stub_volume(i) self._validate_next_link(detailed, item_count=3, osapi_max_limit=5,
for i in range(CONF.osapi_max_limit + 100)] limit=4, should_link_exist=False)
if limit is None or limit >= len(vols):
return vols # Limit lower than max and limits, we have next link
return vols[:limit] self._validate_next_link(detailed, item_count=2, osapi_max_limit=4,
self.stubs.Set(db, 'volume_get_all', stub_volume_get_all3) limit=2, should_link_exist=True)
for key, fn in zip(api_keys, fns):
req = fakes.HTTPRequest.blank('/v2/%s?all_tenants=1' % key, # Limit higher than max and max limits, we have next link
use_admin_context=True) self._validate_next_link(detailed, item_count=2, osapi_max_limit=2,
res_dict = fn(req) limit=4, should_link_exist=True)
self.assertEqual(CONF.osapi_max_limit, len(res_dict['volumes']))
volumes_links = res_dict['volumes_links'] # Limit higher than max but none of them limiting, no next link
_verify_links(volumes_links, key) self._validate_next_link(detailed, item_count=3, osapi_max_limit=4,
# Pass a limit that is greater than the max and the total number of limit=5, should_link_exist=False)
# 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)
def test_volume_list_default_filters(self): def test_volume_list_default_filters(self):
"""Tests that the default filters from volume.api.API.get_all are set. """Tests that the default filters from volume.api.API.get_all are set.
@ -1027,7 +1005,8 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all_by_project(context, project_id, marker, limit, def stub_volume_get_all_by_project(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False,
offset=0):
self.assertEqual(True, filters['no_migration_targets']) self.assertEqual(True, filters['no_migration_targets'])
self.assertFalse('all_tenants' in filters) self.assertFalse('all_tenants' in filters)
return [stubs.stub_volume(1, display_name='vol1')] return [stubs.stub_volume(1, display_name='vol1')]
@ -1035,7 +1014,7 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all(context, marker, limit, def stub_volume_get_all(context, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False, offset=0):
return [] return []
self.stubs.Set(db, 'volume_get_all_by_project', self.stubs.Set(db, 'volume_get_all_by_project',
stub_volume_get_all_by_project) stub_volume_get_all_by_project)
@ -1053,14 +1032,15 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all_by_project2(context, project_id, marker, limit, def stub_volume_get_all_by_project2(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False,
offset=0):
self.assertFalse('no_migration_targets' in filters) self.assertFalse('no_migration_targets' in filters)
return [stubs.stub_volume(1, display_name='vol2')] return [stubs.stub_volume(1, display_name='vol2')]
def stub_volume_get_all2(context, marker, limit, def stub_volume_get_all2(context, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False, offset=0):
return [] return []
self.stubs.Set(db, 'volume_get_all_by_project', self.stubs.Set(db, 'volume_get_all_by_project',
stub_volume_get_all_by_project2) stub_volume_get_all_by_project2)
@ -1076,13 +1056,14 @@ class VolumeApiTest(test.TestCase):
def stub_volume_get_all_by_project3(context, project_id, marker, limit, def stub_volume_get_all_by_project3(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False,
offset=0):
return [] return []
def stub_volume_get_all3(context, marker, limit, def stub_volume_get_all3(context, marker, limit,
sort_keys=None, sort_dirs=None, sort_keys=None, sort_dirs=None,
filters=None, filters=None,
viewable_admin_meta=False): viewable_admin_meta=False, offset=0):
self.assertFalse('no_migration_targets' in filters) self.assertFalse('no_migration_targets' in filters)
self.assertFalse('all_tenants' in filters) self.assertFalse('all_tenants' in filters)
return [stubs.stub_volume(1, display_name='vol3')] return [stubs.stub_volume(1, display_name='vol3')]
@ -1279,10 +1260,10 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
context, None, None, context, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], sort_keys=['created_at'], sort_dirs=['desc'],
filters={'display_name': 'Volume-573108026'}, filters={'display_name': 'Volume-573108026'},
viewable_admin_meta=True) viewable_admin_meta=True, offset=0)
@mock.patch('cinder.volume.api.API.get_all') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_filter_with_list(self, get_all): def test_get_volumes_filter_with_list(self, get_all):
@ -1293,9 +1274,10 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
context, None, None, context, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], 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') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_filter_with_expression(self, get_all): def test_get_volumes_filter_with_expression(self, get_all):
@ -1306,9 +1288,9 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
context, None, None, context, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], 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') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_filter_with_status(self, get_all): def test_get_volumes_filter_with_status(self, get_all):
@ -1319,9 +1301,10 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
ctxt, None, None, ctxt, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], 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') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_filter_with_metadata(self, get_all): def test_get_volumes_filter_with_metadata(self, get_all):
@ -1332,10 +1315,10 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
ctxt, None, None, ctxt, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], sort_keys=['created_at'], sort_dirs=['desc'],
filters={'metadata': {'fake_key': 'fake_value'}}, filters={'metadata': {'fake_key': 'fake_value'}},
viewable_admin_meta=True) viewable_admin_meta=True, offset=0)
@mock.patch('cinder.volume.api.API.get_all') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_filter_with_availability_zone(self, get_all): def test_get_volumes_filter_with_availability_zone(self, get_all):
@ -1346,9 +1329,10 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
ctxt, None, None, ctxt, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], 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') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_filter_with_invalid_filter(self, get_all): def test_get_volumes_filter_with_invalid_filter(self, get_all):
@ -1360,9 +1344,10 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
ctxt, None, None, ctxt, None, CONF.osapi_max_limit,
sort_keys=['created_at'], sort_dirs=['desc'], 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') @mock.patch('cinder.volume.api.API.get_all')
def test_get_volumes_sort_by_name(self, get_all): def test_get_volumes_sort_by_name(self, get_all):
@ -1375,9 +1360,9 @@ class VolumeApiTest(test.TestCase):
self.controller._view_builder.detail_list = mock.Mock() self.controller._view_builder.detail_list = mock.Mock()
self.controller._get_volumes(req, True) self.controller._get_volumes(req, True)
get_all.assert_called_once_with( get_all.assert_called_once_with(
ctxt, None, None, ctxt, None, CONF.osapi_max_limit,
sort_dirs=['desc'], viewable_admin_meta=True, 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): def test_get_volume_filter_options_using_config(self):
self.override_config('query_volume_filters', ['name', 'status', self.override_config('query_volume_filters', ['name', 'status',

View File

@ -437,7 +437,8 @@ class API(base.Base):
return b return b
def get_all(self, context, marker=None, limit=None, sort_keys=None, 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') check_policy(context, 'get_all')
if filters is None: if filters is None:
@ -471,7 +472,8 @@ class API(base.Base):
volumes = self.db.volume_get_all(context, marker, limit, volumes = self.db.volume_get_all(context, marker, limit,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs, sort_dirs=sort_dirs,
filters=filters) filters=filters,
offset=offset)
else: else:
if viewable_admin_meta: if viewable_admin_meta:
context = context.elevated() context = context.elevated()
@ -480,7 +482,8 @@ class API(base.Base):
marker, limit, marker, limit,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs, sort_dirs=sort_dirs,
filters=filters) filters=filters,
offset=offset)
LOG.info(_LI("Get all volumes completed successfully.")) LOG.info(_LI("Get all volumes completed successfully."))
return volumes return volumes