Add count info in /shares and /shares/detail response

Added support for display count info
in share list&detail APIs:

1. /v2/{project_id}/shares?with_count=True
2. /v2/{project_id}/shares/detail?with_count=True

Partially-Implements bp add-amount-info-in-list-api
Change-Id: I12c41a46140b04f26565d8934e0326480477c612
This commit is contained in:
zhongjun 2017-11-02 16:29:49 +08:00 committed by zhongjun
parent db8b63c139
commit 6dac83660d
9 changed files with 62 additions and 11 deletions

View File

@ -112,13 +112,14 @@ REST_API_VERSION_HISTORY = """
* 2.39 - Added share-type quotas. * 2.39 - Added share-type quotas.
* 2.40 - Added share group and share group snapshot quotas. * 2.40 - Added share group and share group snapshot quotas.
* 2.41 - Added 'description' in share type create/list APIs. * 2.41 - Added 'description' in share type create/list APIs.
* 2.42 - Added ``with_count`` in share list API to get total count info.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
# The default api version request is defined to be the # The default api version request is defined to be the
# minimum version of the API supported. # minimum version of the API supported.
_MIN_API_VERSION = "2.0" _MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.41" _MAX_API_VERSION = "2.42"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -230,3 +230,7 @@ user documentation.
2.41 2.41
---- ----
Added 'description' in share type create/list APIs. Added 'description' in share type create/list APIs.
2.42
----
Added ``with_count`` in share list API to get total count info.

View File

@ -34,6 +34,7 @@ from manila import exception
from manila.i18n import _ from manila.i18n import _
from manila import share from manila import share
from manila.share import share_types from manila.share import share_types
from manila import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -105,6 +106,7 @@ class ShareMixin(object):
req.GET.pop('name~', None) req.GET.pop('name~', None)
req.GET.pop('description~', None) req.GET.pop('description~', None)
req.GET.pop('description', None) req.GET.pop('description', None)
req.GET.pop('with_count', None)
return self._get_shares(req, is_detail=False) return self._get_shares(req, is_detail=False)
def detail(self, req): def detail(self, req):
@ -114,6 +116,7 @@ class ShareMixin(object):
req.GET.pop('name~', None) req.GET.pop('name~', None)
req.GET.pop('description~', None) req.GET.pop('description~', None)
req.GET.pop('description', None) req.GET.pop('description', None)
req.GET.pop('with_count', None)
return self._get_shares(req, is_detail=True) return self._get_shares(req, is_detail=True)
def _get_shares(self, req, is_detail): def _get_shares(self, req, is_detail):
@ -129,6 +132,12 @@ class ShareMixin(object):
sort_key = search_opts.pop('sort_key', 'created_at') sort_key = search_opts.pop('sort_key', 'created_at')
sort_dir = search_opts.pop('sort_dir', 'desc') sort_dir = search_opts.pop('sort_dir', 'desc')
show_count = False
if 'with_count' in search_opts:
show_count = utils.get_bool_from_api_params(
'with_count', search_opts)
search_opts.pop('with_count')
# Deserialize dicts # Deserialize dicts
if 'metadata' in search_opts: if 'metadata' in search_opts:
search_opts['metadata'] = ast.literal_eval(search_opts['metadata']) search_opts['metadata'] = ast.literal_eval(search_opts['metadata'])
@ -160,13 +169,18 @@ class ShareMixin(object):
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)
limited_list = common.limited(shares, req) limited_list = common.limited(shares, req)
if is_detail: if is_detail:
shares = self._view_builder.detail_list(req, limited_list) shares = self._view_builder.detail_list(req, limited_list,
total_count)
else: else:
shares = self._view_builder.summary_list(req, limited_list) shares = self._view_builder.summary_list(req, limited_list,
total_count)
return shares return shares
def _get_share_search_options(self): def _get_share_search_options(self):

View File

@ -429,6 +429,9 @@ class ShareController(shares.ShareMixin,
req.GET.pop('description~', None) req.GET.pop('description~', None)
req.GET.pop('description', None) req.GET.pop('description', None)
if req.api_version_request < api_version.APIVersionRequest("2.42"):
req.GET.pop('with_count', None)
return self._get_shares(req, is_detail=False) return self._get_shares(req, is_detail=False)
@wsgi.Controller.api_version("2.0") @wsgi.Controller.api_version("2.0")

View File

@ -36,13 +36,13 @@ class ViewBuilder(common.ViewBuilder):
"add_mount_snapshot_support_field", "add_mount_snapshot_support_field",
] ]
def summary_list(self, request, shares): def summary_list(self, request, shares, count=None):
"""Show a list of shares without many details.""" """Show a list of shares without many details."""
return self._list_view(self.summary, request, shares) return self._list_view(self.summary, request, shares, count)
def detail_list(self, request, shares): def detail_list(self, request, shares, count=None):
"""Detailed view of a list of shares.""" """Detailed view of a list of shares."""
return self._list_view(self.detail, request, shares) return self._list_view(self.detail, request, shares, count)
def summary(self, request, share): def summary(self, request, share):
"""Generic, non-detailed view of a share.""" """Generic, non-detailed view of a share."""
@ -170,7 +170,7 @@ class ViewBuilder(common.ViewBuilder):
share_dict['mount_snapshot_support'] = share.get( share_dict['mount_snapshot_support'] = share.get(
'mount_snapshot_support') 'mount_snapshot_support')
def _list_view(self, func, request, shares): def _list_view(self, func, request, shares, count=None):
"""Provide a view for a list of shares.""" """Provide a view for a list of shares."""
shares_list = [func(request, share)['share'] for share in shares] shares_list = [func(request, share)['share'] for share in shares]
shares_links = self._get_collection_links(request, shares_links = self._get_collection_links(request,
@ -178,6 +178,8 @@ class ViewBuilder(common.ViewBuilder):
self._collection_name) self._collection_name)
shares_dict = dict(shares=shares_list) shares_dict = dict(shares=shares_list)
if count:
shares_dict['count'] = count
if shares_links: if shares_links:
shares_dict['shares_links'] = shares_links shares_dict['shares_links'] = shares_links

View File

@ -1502,7 +1502,9 @@ class ShareAPITest(test.TestCase):
{'use_admin_context': True, 'version': '2.35'}, {'use_admin_context': True, 'version': '2.35'},
{'use_admin_context': False, 'version': '2.35'}, {'use_admin_context': False, 'version': '2.35'},
{'use_admin_context': True, 'version': '2.36'}, {'use_admin_context': True, 'version': '2.36'},
{'use_admin_context': False, 'version': '2.36'}) {'use_admin_context': False, 'version': '2.36'},
{'use_admin_context': True, 'version': '2.42'},
{'use_admin_context': False, 'version': '2.42'})
@ddt.unpack @ddt.unpack
def test_share_list_summary_with_search_opts(self, use_admin_context, def test_share_list_summary_with_search_opts(self, use_admin_context,
version): version):
@ -1528,6 +1530,9 @@ class ShareAPITest(test.TestCase):
search_opts.update( search_opts.update(
{'display_name~': 'fake', {'display_name~': 'fake',
'display_description~': 'fake'}) 'display_description~': 'fake'})
if (api_version.APIVersionRequest(version) >=
api_version.APIVersionRequest('2.42')):
search_opts.update({'with_count': 'true'})
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
@ -1583,6 +1588,9 @@ class ShareAPITest(test.TestCase):
self.assertEqual(shares[1]['id'], result['shares'][0]['id']) self.assertEqual(shares[1]['id'], result['shares'][0]['id'])
self.assertEqual( self.assertEqual(
shares[1]['display_name'], result['shares'][0]['name']) shares[1]['display_name'], result['shares'][0]['name'])
if (api_version.APIVersionRequest(version) >=
api_version.APIVersionRequest('2.42')):
self.assertEqual(3, result['count'])
def test_share_list_summary(self): def test_share_list_summary(self):
self.mock_object(share_api.API, 'get_all', self.mock_object(share_api.API, 'get_all',
@ -1612,7 +1620,9 @@ class ShareAPITest(test.TestCase):
@ddt.data({'use_admin_context': False, 'version': '2.4'}, @ddt.data({'use_admin_context': False, 'version': '2.4'},
{'use_admin_context': True, 'version': '2.4'}, {'use_admin_context': True, 'version': '2.4'},
{'use_admin_context': True, 'version': '2.35'}, {'use_admin_context': True, 'version': '2.35'},
{'use_admin_context': False, 'version': '2.35'}) {'use_admin_context': False, 'version': '2.35'},
{'use_admin_context': True, 'version': '2.42'},
{'use_admin_context': False, 'version': '2.42'})
@ddt.unpack @ddt.unpack
def test_share_list_detail_with_search_opts(self, use_admin_context, def test_share_list_detail_with_search_opts(self, use_admin_context,
version): version):
@ -1633,6 +1643,9 @@ 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: 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
@ -1710,6 +1723,9 @@ class ShareAPITest(test.TestCase):
self.assertEqual( self.assertEqual(
shares[1]['instance']['share_network_id'], shares[1]['instance']['share_network_id'],
result['shares'][0]['share_network_id']) result['shares'][0]['share_network_id'])
if (api_version.APIVersionRequest(version) >=
api_version.APIVersionRequest('2.42')):
self.assertEqual(3, result['count'])
def _list_detail_common_expected(self, admin=False): def _list_detail_common_expected(self, admin=False):
share_dict = { share_dict = {

View File

@ -30,7 +30,7 @@ ShareGroup = [
help="The minimum api microversion is configured to be the " help="The minimum api microversion is configured to be the "
"value of the minimum microversion supported by Manila."), "value of the minimum microversion supported by Manila."),
cfg.StrOpt("max_api_microversion", cfg.StrOpt("max_api_microversion",
default="2.41", default="2.42",
help="The maximum api microversion is configured to be the " help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."), "value of the latest microversion supported by Manila."),
cfg.StrOpt("region", cfg.StrOpt("region",

View File

@ -389,6 +389,14 @@ class SharesActionsTest(base.BaseSharesTest):
for share in shares: for share in shares:
self.assertEqual(project_id, share["project_id"]) self.assertEqual(project_id, share["project_id"])
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@base.skip_if_microversion_lt("2.42")
def test_list_shares_with_detail_with_count(self):
# list shares by name, at least one share is expected
params = {"with_count": 'true'}
shares = self.shares_v2_client.list_shares_with_detail(params)
self.assertGreater(shares["count"], 0)
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
def test_list_shares_public_with_detail(self): def test_list_shares_public_with_detail(self):
public_share = self.create_share( public_share = self.create_share(

View File

@ -0,0 +1,3 @@
---
features:
- Added total count info in Manila's /shares and /shares/detail APIs.