Fix wrong totalcount returned by share listing query
This bugfix [1] modified the totalcount returned by pagination query when the argument 'limit' was specified. It caused manila to do not return precise count of shares in a query that satisfied the conditions. This bug has been fixed and now manila is returning the precise values of shares matched in a given query. Also, manila is now performing filtering actions in the database to have more performatic results. [1] https://review.opendev.org/#/c/688542/ Closes-Bug: #1860061 Co-Authored-By: Carlos Eduardo <ces.eduardo98@gmail.com> Change-Id: I6ddd919bbd5180593cc52bf986912f65a2dab3a7
This commit is contained in:
parent
0d8f415e86
commit
268686c448
@ -164,12 +164,16 @@ class ShareMixin(object):
|
|||||||
common.remove_invalid_options(
|
common.remove_invalid_options(
|
||||||
context, search_opts, self._get_share_search_options())
|
context, search_opts, self._get_share_search_options())
|
||||||
|
|
||||||
|
total_count = None
|
||||||
|
if show_count:
|
||||||
|
count, shares = self.share_api.get_all_with_count(
|
||||||
|
context, search_opts=search_opts, sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
total_count = count
|
||||||
|
else:
|
||||||
shares = self.share_api.get_all(
|
shares = self.share_api.get_all(
|
||||||
context, search_opts=search_opts, sort_key=sort_key,
|
context, search_opts=search_opts, sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
total_count = None
|
|
||||||
if show_count:
|
|
||||||
total_count = len(shares)
|
|
||||||
|
|
||||||
if is_detail:
|
if is_detail:
|
||||||
shares = self._view_builder.detail_list(req, shares, total_count)
|
shares = self._view_builder.detail_list(req, shares, total_count)
|
||||||
@ -189,8 +193,7 @@ class ShareMixin(object):
|
|||||||
'is_public', 'metadata', 'extra_specs', 'sort_key', 'sort_dir',
|
'is_public', 'metadata', 'extra_specs', 'sort_key', 'sort_dir',
|
||||||
'share_group_id', 'share_group_snapshot_id', 'export_location_id',
|
'share_group_id', 'share_group_snapshot_id', 'export_location_id',
|
||||||
'export_location_path', 'display_name~', 'display_description~',
|
'export_location_path', 'display_name~', 'display_description~',
|
||||||
'display_description', 'limit', 'offset'
|
'display_description', 'limit', 'offset')
|
||||||
)
|
|
||||||
|
|
||||||
@wsgi.Controller.authorize
|
@wsgi.Controller.authorize
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
|
@ -401,13 +401,28 @@ def share_get_all(context, filters=None, sort_key=None, sort_dir=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def share_get_all_with_count(context, filters=None, sort_key=None,
|
||||||
|
sort_dir=None):
|
||||||
|
"""Get all shares."""
|
||||||
|
return IMPL.share_get_all_with_count(
|
||||||
|
context, filters=filters, sort_key=sort_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
|
||||||
def share_get_all_by_project(context, project_id, filters=None,
|
def share_get_all_by_project(context, project_id, filters=None,
|
||||||
is_public=False, sort_key=None, sort_dir=None):
|
is_public=False, sort_key=None, sort_dir=None):
|
||||||
"""Returns all shares with given project ID."""
|
"""Returns all shares with given project ID."""
|
||||||
return IMPL.share_get_all_by_project(
|
return IMPL.share_get_all_by_project(
|
||||||
context, project_id, filters=filters, is_public=is_public,
|
context, project_id, filters=filters, is_public=is_public,
|
||||||
sort_key=sort_key, sort_dir=sort_dir,
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
)
|
|
||||||
|
|
||||||
|
def share_get_all_by_project_with_count(
|
||||||
|
context, project_id, filters=None, is_public=False, sort_key=None,
|
||||||
|
sort_dir=None,):
|
||||||
|
"""Returns all shares with given project ID."""
|
||||||
|
return IMPL.share_get_all_by_project_with_count(
|
||||||
|
context, project_id, filters=filters, is_public=is_public,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
|
||||||
def share_get_all_by_share_group_id(context, share_group_id,
|
def share_get_all_by_share_group_id(context, share_group_id,
|
||||||
@ -419,13 +434,29 @@ def share_get_all_by_share_group_id(context, share_group_id,
|
|||||||
sort_key=sort_key, sort_dir=sort_dir)
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def share_get_all_by_share_group_id_with_count(context, share_group_id,
|
||||||
|
filters=None, sort_key=None,
|
||||||
|
sort_dir=None):
|
||||||
|
"""Returns all shares with given project ID and share group id."""
|
||||||
|
return IMPL.share_get_all_by_share_group_id_with_count(
|
||||||
|
context, share_group_id, filters=filters, sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
|
|
||||||
def share_get_all_by_share_server(context, share_server_id, filters=None,
|
def share_get_all_by_share_server(context, share_server_id, filters=None,
|
||||||
sort_key=None, sort_dir=None):
|
sort_key=None, sort_dir=None):
|
||||||
"""Returns all shares with given share server ID."""
|
"""Returns all shares with given share server ID."""
|
||||||
return IMPL.share_get_all_by_share_server(
|
return IMPL.share_get_all_by_share_server(
|
||||||
context, share_server_id, filters=filters, sort_key=sort_key,
|
context, share_server_id, filters=filters, sort_key=sort_key,
|
||||||
sort_dir=sort_dir,
|
sort_dir=sort_dir)
|
||||||
)
|
|
||||||
|
|
||||||
|
def share_get_all_by_share_server_with_count(
|
||||||
|
context, share_server_id, filters=None, sort_key=None, sort_dir=None):
|
||||||
|
"""Returns all shares with given share server ID."""
|
||||||
|
return IMPL.share_get_all_by_share_server_with_count(
|
||||||
|
context, share_server_id, filters=filters, sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
|
|
||||||
def share_delete(context, share_id):
|
def share_delete(context, share_id):
|
||||||
|
@ -43,6 +43,7 @@ from sqlalchemy import MetaData
|
|||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from sqlalchemy.orm import subqueryload
|
from sqlalchemy.orm import subqueryload
|
||||||
|
from sqlalchemy.sql import distinct
|
||||||
from sqlalchemy.sql.expression import literal
|
from sqlalchemy.sql.expression import literal
|
||||||
from sqlalchemy.sql.expression import true
|
from sqlalchemy.sql.expression import true
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
@ -2108,7 +2109,7 @@ def share_get(context, share_id, session=None):
|
|||||||
def _share_get_all_with_filters(context, project_id=None, share_server_id=None,
|
def _share_get_all_with_filters(context, project_id=None, share_server_id=None,
|
||||||
share_group_id=None, filters=None,
|
share_group_id=None, filters=None,
|
||||||
is_public=False, sort_key=None,
|
is_public=False, sort_key=None,
|
||||||
sort_dir=None):
|
sort_dir=None, show_count=False):
|
||||||
"""Returns sorted list of shares that satisfies filters.
|
"""Returns sorted list of shares that satisfies filters.
|
||||||
|
|
||||||
:param context: context to query under
|
:param context: context to query under
|
||||||
@ -2154,12 +2155,23 @@ def _share_get_all_with_filters(context, project_id=None, share_server_id=None,
|
|||||||
msg = _("Wrong sorting key provided - '%s'.") % sort_key
|
msg = _("Wrong sorting key provided - '%s'.") % sort_key
|
||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
|
count = None
|
||||||
|
# NOTE(carloss): Count must be calculated before limit and offset are
|
||||||
|
# applied into the query.
|
||||||
|
if show_count:
|
||||||
|
count = query.with_entities(
|
||||||
|
func.count(distinct(models.Share.id))).scalar()
|
||||||
|
|
||||||
if 'limit' in filters:
|
if 'limit' in filters:
|
||||||
offset = filters.get('offset', 0)
|
offset = filters.get('offset', 0)
|
||||||
query = query.limit(filters['limit']).offset(offset)
|
query = query.limit(filters['limit']).offset(offset)
|
||||||
|
|
||||||
# Returns list of shares that satisfy filters.
|
# Returns list of shares that satisfy filters.
|
||||||
query = query.all()
|
query = query.all()
|
||||||
|
|
||||||
|
if show_count:
|
||||||
|
return count, query
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
@ -2174,17 +2186,37 @@ def share_get_all(context, filters=None, sort_key=None, sort_dir=None):
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
@require_admin_context
|
||||||
|
def share_get_all_with_count(context, filters=None, sort_key=None,
|
||||||
|
sort_dir=None):
|
||||||
|
count, query = _share_get_all_with_filters(
|
||||||
|
context,
|
||||||
|
filters=filters, sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
show_count=True)
|
||||||
|
return count, query
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def share_get_all_by_project(context, project_id, filters=None,
|
def share_get_all_by_project(context, project_id, filters=None,
|
||||||
is_public=False, sort_key=None, sort_dir=None):
|
is_public=False, sort_key=None, sort_dir=None):
|
||||||
"""Returns list of shares with given project ID."""
|
"""Returns list of shares with given project ID."""
|
||||||
query = _share_get_all_with_filters(
|
query = _share_get_all_with_filters(
|
||||||
context, project_id=project_id, filters=filters, is_public=is_public,
|
context, project_id=project_id, filters=filters, is_public=is_public,
|
||||||
sort_key=sort_key, sort_dir=sort_dir,
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def share_get_all_by_project_with_count(
|
||||||
|
context, project_id, filters=None, is_public=False, sort_key=None,
|
||||||
|
sort_dir=None):
|
||||||
|
"""Returns list of shares with given project ID."""
|
||||||
|
count, query = _share_get_all_with_filters(
|
||||||
|
context, project_id=project_id, filters=filters, is_public=is_public,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir, show_count=True)
|
||||||
|
return count, query
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def share_get_all_by_share_group_id(context, share_group_id,
|
def share_get_all_by_share_group_id(context, share_group_id,
|
||||||
filters=None, sort_key=None,
|
filters=None, sort_key=None,
|
||||||
@ -2192,22 +2224,41 @@ def share_get_all_by_share_group_id(context, share_group_id,
|
|||||||
"""Returns list of shares with given group ID."""
|
"""Returns list of shares with given group ID."""
|
||||||
query = _share_get_all_with_filters(
|
query = _share_get_all_with_filters(
|
||||||
context, share_group_id=share_group_id,
|
context, share_group_id=share_group_id,
|
||||||
filters=filters, sort_key=sort_key, sort_dir=sort_dir,
|
filters=filters, sort_key=sort_key, sort_dir=sort_dir)
|
||||||
)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def share_get_all_by_share_group_id_with_count(context, share_group_id,
|
||||||
|
filters=None, sort_key=None,
|
||||||
|
sort_dir=None):
|
||||||
|
"""Returns list of shares with given share group ID."""
|
||||||
|
count, query = _share_get_all_with_filters(
|
||||||
|
context, share_group_id=share_group_id,
|
||||||
|
filters=filters, sort_key=sort_key, sort_dir=sort_dir, show_count=True)
|
||||||
|
return count, query
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def share_get_all_by_share_server(context, share_server_id, filters=None,
|
def share_get_all_by_share_server(context, share_server_id, filters=None,
|
||||||
sort_key=None, sort_dir=None):
|
sort_key=None, sort_dir=None):
|
||||||
"""Returns list of shares with given share server."""
|
"""Returns list of shares with given share server."""
|
||||||
query = _share_get_all_with_filters(
|
query = _share_get_all_with_filters(
|
||||||
context, share_server_id=share_server_id, filters=filters,
|
context, share_server_id=share_server_id, filters=filters,
|
||||||
sort_key=sort_key, sort_dir=sort_dir,
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def share_get_all_by_share_server_with_count(
|
||||||
|
context, share_server_id, filters=None, sort_key=None, sort_dir=None):
|
||||||
|
"""Returns list of shares with given share server."""
|
||||||
|
count, query = _share_get_all_with_filters(
|
||||||
|
context, share_server_id=share_server_id, filters=filters,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir, show_count=True)
|
||||||
|
return count, query
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def share_delete(context, share_id):
|
def share_delete(context, share_id):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
|
@ -1763,6 +1763,17 @@ class API(base.Base):
|
|||||||
|
|
||||||
def get_all(self, context, search_opts=None, sort_key='created_at',
|
def get_all(self, context, search_opts=None, sort_key='created_at',
|
||||||
sort_dir='desc'):
|
sort_dir='desc'):
|
||||||
|
return self._get_all(context, search_opts=search_opts,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
def get_all_with_count(self, context, search_opts=None,
|
||||||
|
sort_key='created_at', sort_dir='desc'):
|
||||||
|
return self._get_all(context, search_opts=search_opts,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
show_count=True)
|
||||||
|
|
||||||
|
def _get_all(self, context, search_opts=None, sort_key='created_at',
|
||||||
|
sort_dir='desc', show_count=False):
|
||||||
policy.check_policy(context, 'share', 'get_all')
|
policy.check_policy(context, 'share', 'get_all')
|
||||||
|
|
||||||
if search_opts is None:
|
if search_opts is None:
|
||||||
@ -1811,36 +1822,43 @@ class API(base.Base):
|
|||||||
is_public = search_opts.pop('is_public', False)
|
is_public = search_opts.pop('is_public', False)
|
||||||
is_public = strutils.bool_from_string(is_public, strict=True)
|
is_public = strutils.bool_from_string(is_public, strict=True)
|
||||||
|
|
||||||
|
get_methods = {
|
||||||
|
'get_by_share_server': (
|
||||||
|
self.db.share_get_all_by_share_server_with_count
|
||||||
|
if show_count else self.db.share_get_all_by_share_server),
|
||||||
|
'get_all': (
|
||||||
|
self.db.share_get_all_with_count
|
||||||
|
if show_count else self.db.share_get_all),
|
||||||
|
'get_all_by_project': (
|
||||||
|
self.db.share_get_all_by_project_with_count
|
||||||
|
if show_count else self.db.share_get_all_by_project)}
|
||||||
|
|
||||||
# Get filtered list of shares
|
# Get filtered list of shares
|
||||||
if 'host' in filters:
|
if 'host' in filters:
|
||||||
policy.check_policy(context, 'share', 'list_by_host')
|
policy.check_policy(context, 'share', 'list_by_host')
|
||||||
if 'share_server_id' in search_opts:
|
if 'share_server_id' in search_opts:
|
||||||
# NOTE(vponomaryov): this is project_id independent
|
# NOTE(vponomaryov): this is project_id independent
|
||||||
policy.check_policy(context, 'share', 'list_by_share_server_id')
|
policy.check_policy(context, 'share', 'list_by_share_server_id')
|
||||||
shares = self.db.share_get_all_by_share_server(
|
result = get_methods['get_by_share_server'](
|
||||||
context, search_opts.pop('share_server_id'), filters=filters,
|
context, search_opts.pop('share_server_id'), filters=filters,
|
||||||
sort_key=sort_key, sort_dir=sort_dir)
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
elif (context.is_admin and utils.is_all_tenants(search_opts)):
|
elif context.is_admin and utils.is_all_tenants(search_opts):
|
||||||
shares = self.db.share_get_all(
|
result = get_methods['get_all'](
|
||||||
context, filters=filters, sort_key=sort_key, sort_dir=sort_dir)
|
context, filters=filters, sort_key=sort_key, sort_dir=sort_dir)
|
||||||
else:
|
else:
|
||||||
shares = self.db.share_get_all_by_project(
|
result = get_methods['get_all_by_project'](
|
||||||
context, project_id=context.project_id, filters=filters,
|
context, project_id=context.project_id, filters=filters,
|
||||||
is_public=is_public, sort_key=sort_key, sort_dir=sort_dir)
|
is_public=is_public, sort_key=sort_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
# NOTE(vponomaryov): we do not need 'all_tenants' opt anymore
|
if show_count:
|
||||||
search_opts.pop('all_tenants', None)
|
count = result[0]
|
||||||
|
shares = result[1]
|
||||||
|
else:
|
||||||
|
shares = result
|
||||||
|
|
||||||
if search_opts:
|
result = (count, shares) if show_count else shares
|
||||||
results = []
|
|
||||||
for s in shares:
|
return result
|
||||||
# values in search_opts can be only strings
|
|
||||||
if (all(s.get(k, None) == v or (v in (s.get(k.rstrip('~'))
|
|
||||||
if k.endswith('~') and s.get(k.rstrip('~')) else ()))
|
|
||||||
for k, v in search_opts.items())):
|
|
||||||
results.append(s)
|
|
||||||
shares = results
|
|
||||||
return shares
|
|
||||||
|
|
||||||
def get_snapshot(self, context, snapshot_id):
|
def get_snapshot(self, context, snapshot_id):
|
||||||
policy.check_policy(context, 'share_snapshot', 'get_snapshot')
|
policy.check_policy(context, 'share_snapshot', 'get_snapshot')
|
||||||
|
@ -1622,9 +1622,19 @@ class ShareAPITest(test.TestCase):
|
|||||||
search_opts.update(
|
search_opts.update(
|
||||||
{'display_name~': 'fake',
|
{'display_name~': 'fake',
|
||||||
'display_description~': 'fake'})
|
'display_description~': 'fake'})
|
||||||
|
method = 'get_all'
|
||||||
|
shares = [
|
||||||
|
{'id': 'id1', 'display_name': 'n1'},
|
||||||
|
{'id': 'id2', 'display_name': 'n2'},
|
||||||
|
{'id': 'id3', 'display_name': 'n3'},
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_action = {'return_value': [shares[1]]}
|
||||||
if (api_version.APIVersionRequest(version) >=
|
if (api_version.APIVersionRequest(version) >=
|
||||||
api_version.APIVersionRequest('2.42')):
|
api_version.APIVersionRequest('2.42')):
|
||||||
search_opts.update({'with_count': 'true'})
|
search_opts.update({'with_count': 'true'})
|
||||||
|
method = 'get_all_with_count'
|
||||||
|
mock_action = {'side_effect': [(1, [shares[1]])]}
|
||||||
if use_admin_context:
|
if use_admin_context:
|
||||||
search_opts['host'] = 'fake_host'
|
search_opts['host'] = 'fake_host'
|
||||||
# fake_key should be filtered for non-admin
|
# fake_key should be filtered for non-admin
|
||||||
@ -1634,13 +1644,8 @@ class ShareAPITest(test.TestCase):
|
|||||||
req = fakes.HTTPRequest.blank(url, version=version,
|
req = fakes.HTTPRequest.blank(url, version=version,
|
||||||
use_admin_context=use_admin_context)
|
use_admin_context=use_admin_context)
|
||||||
|
|
||||||
shares = [
|
mock_get_all = (
|
||||||
{'id': 'id1', 'display_name': 'n1'},
|
self.mock_object(share_api.API, method, mock.Mock(**mock_action)))
|
||||||
{'id': 'id2', 'display_name': 'n2'},
|
|
||||||
{'id': 'id3', 'display_name': 'n3'},
|
|
||||||
]
|
|
||||||
self.mock_object(share_api.API, 'get_all',
|
|
||||||
mock.Mock(return_value=[shares[1]]))
|
|
||||||
|
|
||||||
result = self.controller.index(req)
|
result = self.controller.index(req)
|
||||||
|
|
||||||
@ -1672,7 +1677,7 @@ class ShareAPITest(test.TestCase):
|
|||||||
if use_admin_context:
|
if use_admin_context:
|
||||||
search_opts_expected.update({'fake_key': 'fake_value'})
|
search_opts_expected.update({'fake_key': 'fake_value'})
|
||||||
search_opts_expected['host'] = search_opts['host']
|
search_opts_expected['host'] = search_opts['host']
|
||||||
share_api.API.get_all.assert_called_once_with(
|
mock_get_all.assert_called_once_with(
|
||||||
req.environ['manila.context'],
|
req.environ['manila.context'],
|
||||||
sort_key=search_opts['sort_key'],
|
sort_key=search_opts['sort_key'],
|
||||||
sort_dir=search_opts['sort_dir'],
|
sort_dir=search_opts['sort_dir'],
|
||||||
@ -1706,8 +1711,8 @@ class ShareAPITest(test.TestCase):
|
|||||||
req = fakes.HTTPRequest.blank(url, version=version,
|
req = fakes.HTTPRequest.blank(url, version=version,
|
||||||
use_admin_context=use_admin_context)
|
use_admin_context=use_admin_context)
|
||||||
|
|
||||||
self.mock_object(share_api.API, 'get_all',
|
self.mock_object(share_api.API, 'get_all_with_count',
|
||||||
mock.Mock(return_value=[]))
|
mock.Mock(side_effect=[(0, [])]))
|
||||||
|
|
||||||
result = self.controller.index(req)
|
result = self.controller.index(req)
|
||||||
|
|
||||||
@ -1716,7 +1721,7 @@ class ShareAPITest(test.TestCase):
|
|||||||
if use_admin_context:
|
if use_admin_context:
|
||||||
search_opts_expected.update({'fake_key': 'fake_value'})
|
search_opts_expected.update({'fake_key': 'fake_value'})
|
||||||
search_opts_expected['host'] = search_opts['host']
|
search_opts_expected['host'] = search_opts['host']
|
||||||
share_api.API.get_all.assert_called_once_with(
|
share_api.API.get_all_with_count.assert_called_once_with(
|
||||||
req.environ['manila.context'],
|
req.environ['manila.context'],
|
||||||
sort_key=search_opts['sort_key'],
|
sort_key=search_opts['sort_key'],
|
||||||
sort_dir=search_opts['sort_dir'],
|
sort_dir=search_opts['sort_dir'],
|
||||||
@ -1776,18 +1781,6 @@ class ShareAPITest(test.TestCase):
|
|||||||
'export_location_id': 'fake_export_location_id',
|
'export_location_id': 'fake_export_location_id',
|
||||||
'export_location_path': 'fake_export_location_path',
|
'export_location_path': 'fake_export_location_path',
|
||||||
}
|
}
|
||||||
if (api_version.APIVersionRequest(version) >=
|
|
||||||
api_version.APIVersionRequest('2.42')):
|
|
||||||
search_opts.update({'with_count': 'true'})
|
|
||||||
if use_admin_context:
|
|
||||||
search_opts['host'] = 'fake_host'
|
|
||||||
# fake_key should be filtered for non-admin
|
|
||||||
url = '/v2/fake/shares/detail?fake_key=fake_value'
|
|
||||||
for k, v in search_opts.items():
|
|
||||||
url = url + '&' + k + '=' + v
|
|
||||||
req = fakes.HTTPRequest.blank(url, version=version,
|
|
||||||
use_admin_context=use_admin_context)
|
|
||||||
|
|
||||||
shares = [
|
shares = [
|
||||||
{'id': 'id1', 'display_name': 'n1'},
|
{'id': 'id1', 'display_name': 'n1'},
|
||||||
{
|
{
|
||||||
@ -1805,8 +1798,24 @@ class ShareAPITest(test.TestCase):
|
|||||||
{'id': 'id3', 'display_name': 'n3'},
|
{'id': 'id3', 'display_name': 'n3'},
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mock_object(share_api.API, 'get_all',
|
method = 'get_all'
|
||||||
mock.Mock(return_value=[shares[1]]))
|
mock_action = {'return_value': [shares[1]]}
|
||||||
|
if (api_version.APIVersionRequest(version) >=
|
||||||
|
api_version.APIVersionRequest('2.42')):
|
||||||
|
search_opts.update({'with_count': 'true'})
|
||||||
|
method = 'get_all_with_count'
|
||||||
|
mock_action = {'side_effect': [(1, [shares[1]])]}
|
||||||
|
if use_admin_context:
|
||||||
|
search_opts['host'] = 'fake_host'
|
||||||
|
# fake_key should be filtered for non-admin
|
||||||
|
url = '/v2/fake/shares/detail?fake_key=fake_value'
|
||||||
|
for k, v in search_opts.items():
|
||||||
|
url = url + '&' + k + '=' + v
|
||||||
|
req = fakes.HTTPRequest.blank(url, version=version,
|
||||||
|
use_admin_context=use_admin_context)
|
||||||
|
|
||||||
|
mock_get_all = self.mock_object(share_api.API, method,
|
||||||
|
mock.Mock(**mock_action))
|
||||||
|
|
||||||
result = self.controller.detail(req)
|
result = self.controller.detail(req)
|
||||||
|
|
||||||
@ -1834,7 +1843,7 @@ class ShareAPITest(test.TestCase):
|
|||||||
if use_admin_context:
|
if use_admin_context:
|
||||||
search_opts_expected.update({'fake_key': 'fake_value'})
|
search_opts_expected.update({'fake_key': 'fake_value'})
|
||||||
search_opts_expected['host'] = search_opts['host']
|
search_opts_expected['host'] = search_opts['host']
|
||||||
share_api.API.get_all.assert_called_once_with(
|
mock_get_all.assert_called_once_with(
|
||||||
req.environ['manila.context'],
|
req.environ['manila.context'],
|
||||||
sort_key=search_opts['sort_key'],
|
sort_key=search_opts['sort_key'],
|
||||||
sort_dir=search_opts['sort_dir'],
|
sort_dir=search_opts['sort_dir'],
|
||||||
|
@ -534,6 +534,17 @@ class ShareDatabaseAPITestCase(test.TestCase):
|
|||||||
self.assertEqual(2, len(actual_result))
|
self.assertEqual(2, len(actual_result))
|
||||||
self.assertEqual(shares[0]['id'], actual_result[1]['id'])
|
self.assertEqual(shares[0]['id'], actual_result[1]['id'])
|
||||||
|
|
||||||
|
@ddt.data('id')
|
||||||
|
def test_share_get_all_sort_by_share_fields(self, sort_key):
|
||||||
|
shares = [db_utils.create_share(**{sort_key: n, 'size': 1})
|
||||||
|
for n in ('FAKE_UUID1', 'FAKE_UUID2')]
|
||||||
|
|
||||||
|
actual_result = db_api.share_get_all(
|
||||||
|
self.ctxt, sort_key=sort_key, sort_dir='desc')
|
||||||
|
|
||||||
|
self.assertEqual(2, len(actual_result))
|
||||||
|
self.assertEqual(shares[0]['id'], actual_result[1]['id'])
|
||||||
|
|
||||||
@ddt.data('id', 'path')
|
@ddt.data('id', 'path')
|
||||||
def test_share_get_all_by_export_location(self, type):
|
def test_share_get_all_by_export_location(self, type):
|
||||||
share = db_utils.create_share()
|
share = db_utils.create_share()
|
||||||
@ -585,6 +596,63 @@ class ShareDatabaseAPITestCase(test.TestCase):
|
|||||||
self.assertEqual(0, len(
|
self.assertEqual(0, len(
|
||||||
set(shares_requested_ids) & set(shares_not_requested_ids)))
|
set(shares_requested_ids) & set(shares_not_requested_ids)))
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
({'display_name~': 'fake_name'}, 3, 3),
|
||||||
|
({'display_name~': 'fake_name', 'limit': 2}, 3, 2)
|
||||||
|
)
|
||||||
|
@ddt.unpack
|
||||||
|
def test_share_get_all_with_count(self, filters, amount_of_shares,
|
||||||
|
expected_shares_len):
|
||||||
|
[db_utils.create_share(display_name='fake_name_%s' % str(i))
|
||||||
|
for i in range(amount_of_shares)]
|
||||||
|
|
||||||
|
count, shares = db_api.share_get_all_with_count(
|
||||||
|
self.ctxt, filters=filters)
|
||||||
|
|
||||||
|
self.assertEqual(count, amount_of_shares)
|
||||||
|
for share in shares:
|
||||||
|
self.assertIn('fake_name', share['display_name'])
|
||||||
|
self.assertEqual(expected_shares_len, len(shares))
|
||||||
|
|
||||||
|
def test_share_get_all_by_share_group_id_with_count(self):
|
||||||
|
share_groups = [db_utils.create_share_group() for i in range(2)]
|
||||||
|
shares = [
|
||||||
|
db_utils.create_share(share_group_id=share_group['id'])
|
||||||
|
for share_group in share_groups]
|
||||||
|
|
||||||
|
count, result = db_api.share_get_all_by_share_group_id_with_count(
|
||||||
|
self.ctxt, share_groups[0]['id'])
|
||||||
|
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
self.assertEqual(shares[0]['id'], result[0]['id'])
|
||||||
|
self.assertEqual(1, len(result))
|
||||||
|
|
||||||
|
def test_share_get_all_by_share_server_with_count(self):
|
||||||
|
share_servers = [db_utils.create_share_server() for i in range(2)]
|
||||||
|
shares = [
|
||||||
|
db_utils.create_share(share_server_id=share_server['id'])
|
||||||
|
for share_server in share_servers]
|
||||||
|
|
||||||
|
count, result = db_api.share_get_all_by_share_server_with_count(
|
||||||
|
self.ctxt, share_servers[0]['id'])
|
||||||
|
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
self.assertEqual(shares[0]['id'], result[0]['id'])
|
||||||
|
self.assertEqual(1, len(result))
|
||||||
|
|
||||||
|
def test_share_get_all_by_project_with_count(self):
|
||||||
|
project_ids = ['fake_id_1', 'fake_id_2']
|
||||||
|
shares = [
|
||||||
|
db_utils.create_share(project_id=project_id)
|
||||||
|
for project_id in project_ids]
|
||||||
|
|
||||||
|
count, result = db_api.share_get_all_by_project_with_count(
|
||||||
|
self.ctxt, project_ids[0])
|
||||||
|
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
self.assertEqual(shares[0]['id'], result[0]['id'])
|
||||||
|
self.assertEqual(1, len(result))
|
||||||
|
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
({'status': constants.STATUS_AVAILABLE}, 'status',
|
({'status': constants.STATUS_AVAILABLE}, 'status',
|
||||||
[constants.STATUS_AVAILABLE, constants.STATUS_ERROR]),
|
[constants.STATUS_AVAILABLE, constants.STATUS_ERROR]),
|
||||||
|
@ -329,44 +329,54 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_get_all_admin_filter_by_name(self):
|
def test_get_all_admin_filter_by_name(self):
|
||||||
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=True)
|
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=True)
|
||||||
self.mock_object(db_api, 'share_get_all_by_project',
|
self.mock_object(
|
||||||
mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[1:]))
|
db_api, 'share_get_all_by_project',
|
||||||
shares = self.api.get_all(ctx, {'name': 'bar'})
|
mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[1::2]))
|
||||||
|
expected_filters = {'display_name': 'bar'}
|
||||||
|
shares = self.api.get_all(ctx, {'display_name': 'bar'})
|
||||||
share_api.policy.check_policy.assert_has_calls([
|
share_api.policy.check_policy.assert_has_calls([
|
||||||
mock.call(ctx, 'share', 'get_all'),
|
mock.call(ctx, 'share', 'get_all'),
|
||||||
])
|
])
|
||||||
db_api.share_get_all_by_project.assert_called_once_with(
|
db_api.share_get_all_by_project.assert_called_once_with(
|
||||||
ctx, sort_dir='desc', sort_key='created_at',
|
ctx, sort_dir='desc', sort_key='created_at',
|
||||||
project_id='fake_pid_2', filters={}, is_public=False
|
project_id='fake_pid_2', filters=expected_filters, is_public=False
|
||||||
)
|
)
|
||||||
self.assertEqual(_FAKE_LIST_OF_ALL_SHARES[1::2], shares)
|
self.assertEqual(_FAKE_LIST_OF_ALL_SHARES[1::2], shares)
|
||||||
|
|
||||||
@ddt.data(({'name': 'fo'}, 0), ({'description': 'd'}, 0),
|
@ddt.data(({'display_name': 'fo'}, 0), ({'display_description': 'd'}, 0),
|
||||||
({'name': 'foo', 'description': 'd'}, 0),
|
({'display_name': 'foo', 'display_description': 'd'}, 0),
|
||||||
({'name': 'foo'}, 1), ({'description': 'ds'}, 1),
|
({'display_name': 'foo'}, 1), ({'display_description': 'ds'}, 1),
|
||||||
({'name~': 'foo', 'description~': 'ds'}, 2),
|
({'display_name~': 'foo', 'display_description~': 'ds'}, 2),
|
||||||
({'name': 'foo', 'description~': 'ds'}, 1),
|
({'display_name': 'foo', 'display_description~': 'ds'}, 1),
|
||||||
({'name~': 'foo', 'description': 'ds'}, 1))
|
({'display_name~': 'foo', 'display_description': 'ds'}, 1))
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_get_all_admin_filter_by_name_and_description(
|
def test_get_all_admin_filter_by_name_and_description(
|
||||||
self, search_opts, get_share_number):
|
self, search_opts, get_share_number):
|
||||||
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=True)
|
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=True)
|
||||||
|
|
||||||
|
expected_result = []
|
||||||
|
|
||||||
|
if get_share_number == 2:
|
||||||
|
expected_result = _FAKE_LIST_OF_ALL_SHARES[0::2]
|
||||||
|
elif get_share_number == 1:
|
||||||
|
expected_result = _FAKE_LIST_OF_ALL_SHARES[:1]
|
||||||
|
|
||||||
self.mock_object(db_api, 'share_get_all_by_project',
|
self.mock_object(db_api, 'share_get_all_by_project',
|
||||||
mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES))
|
mock.Mock(return_value=expected_result))
|
||||||
|
expected_filters = copy.copy(search_opts)
|
||||||
|
|
||||||
shares = self.api.get_all(ctx, search_opts)
|
shares = self.api.get_all(ctx, search_opts)
|
||||||
share_api.policy.check_policy.assert_has_calls([
|
share_api.policy.check_policy.assert_has_calls([
|
||||||
mock.call(ctx, 'share', 'get_all'),
|
mock.call(ctx, 'share', 'get_all'),
|
||||||
])
|
])
|
||||||
|
|
||||||
db_api.share_get_all_by_project.assert_called_once_with(
|
db_api.share_get_all_by_project.assert_called_once_with(
|
||||||
ctx, sort_dir='desc', sort_key='created_at',
|
ctx, sort_dir='desc', sort_key='created_at',
|
||||||
project_id='fake_pid_2',
|
project_id='fake_pid_2',
|
||||||
filters={}, is_public=False
|
filters=expected_filters, is_public=False
|
||||||
)
|
)
|
||||||
self.assertEqual(get_share_number, len(shares))
|
self.assertEqual(get_share_number, len(shares))
|
||||||
if get_share_number == 2:
|
self.assertEqual(expected_result, shares)
|
||||||
self.assertEqual(_FAKE_LIST_OF_ALL_SHARES[0::2], shares)
|
|
||||||
elif get_share_number == 1:
|
|
||||||
self.assertEqual(_FAKE_LIST_OF_ALL_SHARES[:1], shares)
|
|
||||||
|
|
||||||
@ddt.data('id', 'path')
|
@ddt.data('id', 'path')
|
||||||
def test_get_all_admin_filter_by_export_location(self, type):
|
def test_get_all_admin_filter_by_export_location(self, type):
|
||||||
@ -387,7 +397,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
def test_get_all_admin_filter_by_name_and_all_tenants(self):
|
def test_get_all_admin_filter_by_name_and_all_tenants(self):
|
||||||
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=True)
|
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=True)
|
||||||
self.mock_object(db_api, 'share_get_all',
|
self.mock_object(db_api, 'share_get_all',
|
||||||
mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES))
|
mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[:1]))
|
||||||
shares = self.api.get_all(ctx, {'name': 'foo', 'all_tenants': 1})
|
shares = self.api.get_all(ctx, {'name': 'foo', 'all_tenants': 1})
|
||||||
share_api.policy.check_policy.assert_has_calls([
|
share_api.policy.check_policy.assert_has_calls([
|
||||||
mock.call(ctx, 'share', 'get_all'),
|
mock.call(ctx, 'share', 'get_all'),
|
||||||
@ -446,8 +456,11 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_get_all_non_admin_with_name_and_status_filters(self):
|
def test_get_all_non_admin_with_name_and_status_filters(self):
|
||||||
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=False)
|
ctx = context.RequestContext('fake_uid', 'fake_pid_2', is_admin=False)
|
||||||
self.mock_object(db_api, 'share_get_all_by_project',
|
self.mock_object(
|
||||||
mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[1:]))
|
db_api, 'share_get_all_by_project',
|
||||||
|
mock.Mock(side_effect=[
|
||||||
|
_FAKE_LIST_OF_ALL_SHARES[1::2],
|
||||||
|
_FAKE_LIST_OF_ALL_SHARES[2::4]]))
|
||||||
shares = self.api.get_all(
|
shares = self.api.get_all(
|
||||||
ctx, {'name': 'bar', 'status': constants.STATUS_ERROR})
|
ctx, {'name': 'bar', 'status': constants.STATUS_ERROR})
|
||||||
share_api.policy.check_policy.assert_has_calls([
|
share_api.policy.check_policy.assert_has_calls([
|
||||||
|
@ -393,6 +393,16 @@ def is_valid_ip_address(ip_address, ip_version):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_bool_param(param_string, params, default=False):
|
||||||
|
param = params.get(param_string, default)
|
||||||
|
if not strutils.is_valid_boolstr(param):
|
||||||
|
msg = _("Value '%(param)s' for '%(param_string)s' is not "
|
||||||
|
"a boolean.") % {'param': param, 'param_string': param_string}
|
||||||
|
raise exception.InvalidParameterValue(err=msg)
|
||||||
|
|
||||||
|
return strutils.bool_from_string(param, strict=True)
|
||||||
|
|
||||||
|
|
||||||
def is_all_tenants(search_opts):
|
def is_all_tenants(search_opts):
|
||||||
"""Checks to see if the all_tenants flag is in search_opts
|
"""Checks to see if the all_tenants flag is in search_opts
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixed the issue that caused pagination queries to return erroneous
|
||||||
|
results when the argument `limit` was specified. Also improved the
|
||||||
|
queries performance by moving some filtering operations to the
|
||||||
|
database.
|
Loading…
Reference in New Issue
Block a user