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:
parent
db8b63c139
commit
6dac83660d
manila
api
tests/api/v2
manila_tempest_tests
releasenotes/notes
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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):
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 = {
|
||||||
|
@ -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",
|
||||||
|
@ -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(
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added total count info in Manila's /shares and /shares/detail APIs.
|
Loading…
Reference in New Issue
Block a user