Support count info in List&Detail APIs
This patch adds support for display count info
in volume, backup and snapshot's list&detail APIs
since microversion 3.45, for instance:
1. /v3/{project_id}/volumes?with_count=True
2. /v3/{project_id}/volumes/detail?with_count=True
Depends-On: 1c8fe0ade4
Change-Id: I2e92b27c36357120fcf0ec5917c6484441c946a8
Implements: bp add-amount-info-in-list-api
This commit is contained in:
parent
859d3ac945
commit
23b7463984
@ -62,6 +62,7 @@ Request
|
||||
- limit: limit
|
||||
- offset: offset
|
||||
- marker: marker
|
||||
- with_count: with_count
|
||||
|
||||
|
||||
Response Parameters
|
||||
@ -88,6 +89,7 @@ Response Parameters
|
||||
- data_timestamp: data_timestamp
|
||||
- snapshot_id: snapshot_id_2
|
||||
- os-backup-project-attr:project_id: os-backup-project-attr:project_id
|
||||
- count: count
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
@ -329,6 +331,7 @@ Request
|
||||
- sort: sort
|
||||
- limit: limit
|
||||
- marker: marker
|
||||
- with_count: with_count
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
@ -339,6 +342,7 @@ Response Parameters
|
||||
- id: id_1
|
||||
- links: links_1
|
||||
- name: name_1
|
||||
- count: count
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
@ -398,6 +398,13 @@ vol_type_id_query:
|
||||
in: query
|
||||
required: true
|
||||
type: string
|
||||
with_count:
|
||||
description: |
|
||||
Whether to show ``count`` in API response or not, default is ``False``.
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
min_version: 3.45
|
||||
|
||||
# variables in body
|
||||
absolute:
|
||||
@ -740,6 +747,13 @@ control_location:
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
count:
|
||||
description: |
|
||||
The total count of requested resource before pagination is applied.
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
min_version: 3.45
|
||||
create-from-src:
|
||||
description: |
|
||||
The create from source action.
|
||||
|
@ -54,5 +54,6 @@
|
||||
"is_incremental": true,
|
||||
"has_dependent_backups": false
|
||||
}
|
||||
]
|
||||
],
|
||||
"count": 10
|
||||
}
|
||||
|
@ -56,5 +56,6 @@
|
||||
"id": "4dbf0ec2-0b57-4669-9823-9f7c76f2b4f8",
|
||||
"size": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"count": 10
|
||||
}
|
||||
|
@ -15,5 +15,6 @@
|
||||
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
|
||||
"description": "volume snapshot"
|
||||
}
|
||||
]
|
||||
],
|
||||
"count": 10
|
||||
}
|
||||
|
@ -16,5 +16,6 @@
|
||||
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
|
||||
"description": "volume snapshot"
|
||||
}
|
||||
]
|
||||
],
|
||||
"count": 10
|
||||
}
|
||||
|
@ -98,5 +98,6 @@
|
||||
"created_at": "2015-11-29T02:25:18.000000",
|
||||
"volume_type": "lvmdriver-1"
|
||||
}
|
||||
]
|
||||
],
|
||||
"count": 10
|
||||
}
|
||||
|
@ -28,5 +28,6 @@
|
||||
],
|
||||
"name": "vol-003"
|
||||
}
|
||||
]
|
||||
],
|
||||
"count": 10
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ Request
|
||||
- limit: limit
|
||||
- offset: offset
|
||||
- marker: marker
|
||||
- with_count: with_count
|
||||
|
||||
|
||||
Response Parameters
|
||||
@ -77,6 +78,7 @@ Response Parameters
|
||||
- size: size
|
||||
- id: id
|
||||
- metadata: metadata
|
||||
- count: count
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
@ -164,6 +166,7 @@ Request
|
||||
- limit: limit
|
||||
- offset: offset
|
||||
- marker: marker
|
||||
- with_count: with_count
|
||||
|
||||
|
||||
Response Parameters
|
||||
@ -180,6 +183,7 @@ Response Parameters
|
||||
- metadata: metadata
|
||||
- id: id
|
||||
- size: size
|
||||
- count: count
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
@ -86,6 +86,7 @@ Request
|
||||
- limit: limit
|
||||
- offset: offset
|
||||
- marker: marker
|
||||
- with_count: with_count
|
||||
|
||||
|
||||
Response Parameters
|
||||
@ -121,6 +122,7 @@ Response Parameters
|
||||
- os-volume-replication:driver_data: os-volume-replication:driver_data
|
||||
- volumes: volumes
|
||||
- volume_type: volume_type
|
||||
- count: count
|
||||
|
||||
|
||||
|
||||
@ -260,6 +262,7 @@ Request
|
||||
- limit: limit
|
||||
- offset: offset
|
||||
- marker: marker
|
||||
- with_count: with_count
|
||||
|
||||
|
||||
Response Parameters
|
||||
@ -271,6 +274,7 @@ Response Parameters
|
||||
- id: id_5
|
||||
- links: links_3
|
||||
- name: name_13
|
||||
- count: count
|
||||
|
||||
|
||||
|
||||
|
@ -30,6 +30,7 @@ from cinder import backup as backupAPI
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import utils
|
||||
from cinder import volume as volumeAPI
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -41,6 +42,7 @@ class BackupsController(wsgi.Controller):
|
||||
|
||||
def __init__(self):
|
||||
self.backup_api = backupAPI.API()
|
||||
self.volume_api = volumeAPI.API()
|
||||
super(BackupsController, self).__init__()
|
||||
|
||||
def show(self, req, id):
|
||||
@ -100,6 +102,10 @@ class BackupsController(wsgi.Controller):
|
||||
marker, limit, offset = common.get_pagination_params(filters)
|
||||
sort_keys, sort_dirs = common.get_sort_params(filters)
|
||||
|
||||
show_count = False
|
||||
if req_version.matches(mv.SUPPORT_COUNT_INFO):
|
||||
show_count = utils.get_bool_param('with_count', filters)
|
||||
filters.pop('with_count')
|
||||
self._convert_sort_name(req_version, sort_keys)
|
||||
self._process_backup_filtering(context=context, filters=filters,
|
||||
req_version=req_version)
|
||||
@ -107,7 +113,7 @@ class BackupsController(wsgi.Controller):
|
||||
if 'name' in filters:
|
||||
filters['display_name'] = filters.pop('name')
|
||||
|
||||
backups = self.backup_api.get_all(context, search_opts=filters,
|
||||
backups = self.backup_api.get_all(context, search_opts=filters.copy(),
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
@ -115,12 +121,18 @@ class BackupsController(wsgi.Controller):
|
||||
sort_dirs=sort_dirs,
|
||||
)
|
||||
|
||||
total_count = None
|
||||
if show_count:
|
||||
total_count = self.volume_api.calculate_resource_count(
|
||||
context, 'backup', filters)
|
||||
req.cache_db_backups(backups.objects)
|
||||
|
||||
if is_detail:
|
||||
backups = self._view_builder.detail_list(req, backups.objects)
|
||||
backups = self._view_builder.detail_list(req, backups.objects,
|
||||
total_count)
|
||||
else:
|
||||
backups = self._view_builder.summary_list(req, backups.objects)
|
||||
backups = self._view_builder.summary_list(req, backups.objects,
|
||||
total_count)
|
||||
return backups
|
||||
|
||||
# TODO(frankm): Add some checks here including
|
||||
|
@ -127,6 +127,8 @@ BACKUP_METADATA = '3.43'
|
||||
|
||||
NEW_ATTACH_COMPLETION = '3.44'
|
||||
|
||||
SUPPORT_COUNT_INFO = '3.45'
|
||||
|
||||
|
||||
def get_mv_header(version):
|
||||
"""Gets a formatted HTTP microversion header.
|
||||
|
@ -107,6 +107,8 @@ REST_API_VERSION_HISTORY = """
|
||||
state is intentionally NOT allowed.
|
||||
* 3.43 - Support backup CRUD with metadata.
|
||||
* 3.44 - Add attachment-complete.
|
||||
* 3.45 - Add ``count`` field to volume, backup and snapshot list and
|
||||
detail APIs.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -114,7 +116,7 @@ REST_API_VERSION_HISTORY = """
|
||||
# minimum version of the API supported.
|
||||
# Explicitly using /v2 endpoints will still work
|
||||
_MIN_API_VERSION = "3.0"
|
||||
_MAX_API_VERSION = "3.44"
|
||||
_MAX_API_VERSION = "3.45"
|
||||
_LEGACY_API_VERSION2 = "2.0"
|
||||
UPDATED = "2017-09-19T20:18:14Z"
|
||||
|
||||
|
@ -373,3 +373,7 @@ user documentation.
|
||||
Support attachment completion. See the
|
||||
`API reference <https://developer.openstack.org/api-ref/block-storage/v3/index.html#complete-attachment>`__
|
||||
for details.
|
||||
|
||||
3.45
|
||||
----
|
||||
Add ``count`` field to volume, backup and snapshot list and detail APIs.
|
||||
|
@ -78,6 +78,12 @@ class SnapshotsController(snapshots_v2.SnapshotsController):
|
||||
sort_keys, sort_dirs = common.get_sort_params(search_opts)
|
||||
marker, limit, offset = common.get_pagination_params(search_opts)
|
||||
|
||||
req_version = req.api_version_request
|
||||
show_count = False
|
||||
if req_version.matches(mv.SUPPORT_COUNT_INFO):
|
||||
show_count = utils.get_bool_param('with_count', search_opts)
|
||||
search_opts.pop('with_count')
|
||||
|
||||
# process filters
|
||||
self._process_snapshot_filtering(context=context,
|
||||
filters=search_opts,
|
||||
@ -93,20 +99,27 @@ class SnapshotsController(snapshots_v2.SnapshotsController):
|
||||
if 'name' in search_opts:
|
||||
search_opts['display_name'] = search_opts.pop('name')
|
||||
|
||||
snapshots = self.volume_api.get_all_snapshots(context,
|
||||
search_opts=search_opts,
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
offset=offset)
|
||||
snapshots = self.volume_api.get_all_snapshots(
|
||||
context,
|
||||
search_opts=search_opts.copy(),
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
offset=offset)
|
||||
total_count = None
|
||||
if show_count:
|
||||
total_count = self.volume_api.calculate_resource_count(
|
||||
context, 'snapshot', search_opts)
|
||||
|
||||
req.cache_db_snapshots(snapshots.objects)
|
||||
|
||||
if is_detail:
|
||||
snapshots = self._view_builder.detail_list(req, snapshots.objects)
|
||||
snapshots = self._view_builder.detail_list(req, snapshots.objects,
|
||||
total_count)
|
||||
else:
|
||||
snapshots = self._view_builder.summary_list(req, snapshots.objects)
|
||||
snapshots = self._view_builder.summary_list(req, snapshots.objects,
|
||||
total_count)
|
||||
return snapshots
|
||||
|
||||
|
||||
|
@ -20,6 +20,8 @@ from cinder.api.v2.views import volumes as views_v2
|
||||
class ViewBuilder(views_v2.ViewBuilder):
|
||||
"""Model a volumes API V3 response as a python dictionary."""
|
||||
|
||||
_collection_name = "volumes"
|
||||
|
||||
def quick_summary(self, volume_count, volume_size,
|
||||
all_distinct_metadata=None):
|
||||
"""View of volumes summary.
|
||||
@ -53,3 +55,32 @@ class ViewBuilder(views_v2.ViewBuilder):
|
||||
volume_ref['volume']['provider_id'] = volume.get('provider_id')
|
||||
|
||||
return volume_ref
|
||||
|
||||
def _list_view(self, func, request, volumes, volume_count,
|
||||
coll_name=_collection_name):
|
||||
"""Provide a view for a list of volumes.
|
||||
|
||||
:param func: Function used to format the volume data
|
||||
:param request: API request
|
||||
:param volumes: List of volumes in dictionary format
|
||||
:param volume_count: Length of the original list of volumes
|
||||
:param coll_name: Name of collection, used to generate the next link
|
||||
for a pagination query
|
||||
:returns: Volume data in dictionary format
|
||||
"""
|
||||
volumes_list = [func(request, volume)['volume'] for volume in volumes]
|
||||
volumes_links = self._get_collection_links(request,
|
||||
volumes,
|
||||
coll_name,
|
||||
volume_count)
|
||||
volumes_dict = {"volumes": volumes_list}
|
||||
|
||||
if volumes_links:
|
||||
volumes_dict['volumes_links'] = volumes_links
|
||||
|
||||
req_version = request.api_version_request
|
||||
if req_version.matches(
|
||||
mv.SUPPORT_COUNT_INFO, None) and volume_count is not None:
|
||||
volumes_dict['count'] = volume_count
|
||||
|
||||
return volumes_dict
|
||||
|
@ -97,6 +97,11 @@ class VolumeController(volumes_v2.VolumeController):
|
||||
sort_keys, sort_dirs = common.get_sort_params(params)
|
||||
filters = params
|
||||
|
||||
show_count = False
|
||||
if req_version.matches(mv.SUPPORT_COUNT_INFO):
|
||||
show_count = utils.get_bool_param('with_count', filters)
|
||||
filters.pop('with_count')
|
||||
|
||||
self._process_volume_filtering(context=context, filters=filters,
|
||||
req_version=req_version)
|
||||
|
||||
@ -114,9 +119,13 @@ class VolumeController(volumes_v2.VolumeController):
|
||||
volumes = self.volume_api.get_all(context, marker, limit,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
filters=filters,
|
||||
filters=filters.copy(),
|
||||
viewable_admin_meta=True,
|
||||
offset=offset)
|
||||
total_count = None
|
||||
if show_count:
|
||||
total_count = self.volume_api.calculate_resource_count(
|
||||
context, 'volume', filters)
|
||||
|
||||
for volume in volumes:
|
||||
utils.add_visible_admin_metadata(volume)
|
||||
@ -124,9 +133,11 @@ class VolumeController(volumes_v2.VolumeController):
|
||||
req.cache_db_volumes(volumes.objects)
|
||||
|
||||
if is_detail:
|
||||
volumes = self._view_builder.detail_list(req, volumes)
|
||||
volumes = self._view_builder.detail_list(
|
||||
req, volumes, total_count)
|
||||
else:
|
||||
volumes = self._view_builder.summary_list(req, volumes)
|
||||
volumes = self._view_builder.summary_list(
|
||||
req, volumes, total_count)
|
||||
return volumes
|
||||
|
||||
@wsgi.Controller.api_version(mv.VOLUME_SUMMARY)
|
||||
|
@ -92,6 +92,9 @@ class ViewBuilder(common.ViewBuilder):
|
||||
if backups_links:
|
||||
backups_dict['backups_links'] = backups_links
|
||||
|
||||
if backup_count is not None:
|
||||
backups_dict['count'] = backup_count
|
||||
|
||||
return backups_dict
|
||||
|
||||
def export_summary(self, request, export):
|
||||
|
@ -75,4 +75,7 @@ class ViewBuilder(common.ViewBuilder):
|
||||
if snapshots_links:
|
||||
snapshots_dict[self._collection_name + '_links'] = snapshots_links
|
||||
|
||||
if snapshot_count is not None:
|
||||
snapshots_dict['count'] = snapshot_count
|
||||
|
||||
return snapshots_dict
|
||||
|
@ -281,6 +281,10 @@ def volume_get_all(context, marker=None, limit=None, sort_keys=None,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def calculate_resource_count(context, resource_type, filters):
|
||||
return IMPL.calculate_resource_count(context, resource_type, filters)
|
||||
|
||||
|
||||
def volume_get_all_by_host(context, host, filters=None):
|
||||
"""Get all volumes belonging to a host."""
|
||||
return IMPL.volume_get_all_by_host(context, host, filters=filters)
|
||||
|
@ -2364,6 +2364,23 @@ def _generate_paginate_query(context, session, marker, limit, sort_keys,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def calculate_resource_count(context, resource_type, filters):
|
||||
"""Calculate total count with filters applied"""
|
||||
|
||||
session = get_session()
|
||||
if resource_type not in CALCULATE_COUNT_HELPERS.keys():
|
||||
raise exception.InvalidInput(
|
||||
reason=_("Model %s doesn't support "
|
||||
"counting resource.") % resource_type)
|
||||
get_query, process_filters = CALCULATE_COUNT_HELPERS[resource_type]
|
||||
query = get_query(context, session=session)
|
||||
if filters:
|
||||
query = process_filters(query, filters)
|
||||
if query is None:
|
||||
return 0
|
||||
return query.with_entities(func.count()).scalar()
|
||||
|
||||
|
||||
@apply_like_filters(model=models.Volume)
|
||||
def _process_volume_filters(query, filters):
|
||||
"""Common filter processing for Volume queries.
|
||||
@ -6589,6 +6606,13 @@ PAGINATION_HELPERS = {
|
||||
}
|
||||
|
||||
|
||||
CALCULATE_COUNT_HELPERS = {
|
||||
'volume': (_volume_get_query, _process_volume_filters),
|
||||
'snapshot': (_snaps_get_query, _process_snaps_filters),
|
||||
'backup': (_backups_get_query, _process_backups_filters),
|
||||
}
|
||||
|
||||
|
||||
###############################
|
||||
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_utils import strutils
|
||||
import webob
|
||||
|
||||
from cinder.api import microversions as mv
|
||||
@ -88,6 +89,90 @@ class BackupsControllerAPITestCase(test.TestCase):
|
||||
self.controller.update,
|
||||
req, fake.BACKUP_ID, body)
|
||||
|
||||
def _create_multiple_backups_with_different_project(self):
|
||||
test_utils.create_backup(
|
||||
context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True))
|
||||
test_utils.create_backup(
|
||||
context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True))
|
||||
test_utils.create_backup(
|
||||
context.RequestContext(fake.USER_ID, fake.PROJECT2_ID, True))
|
||||
|
||||
@ddt.data('backups', 'backups/detail')
|
||||
def test_list_backup_with_count_param_version_not_matched(self, action):
|
||||
self._create_multiple_backups_with_different_project()
|
||||
|
||||
is_detail = True if 'detail' in action else False
|
||||
req = fakes.HTTPRequest.blank("/v3/%s?with_count=True" % action)
|
||||
req.headers = mv.get_mv_header(
|
||||
mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
|
||||
req.api_version_request = mv.get_api_version(
|
||||
mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_backups(req, is_detail=is_detail)
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
@ddt.data({'method': 'backups',
|
||||
'display_param': 'True'},
|
||||
{'method': 'backups',
|
||||
'display_param': 'False'},
|
||||
{'method': 'backups',
|
||||
'display_param': '1'},
|
||||
{'method': 'backups/detail',
|
||||
'display_param': 'True'},
|
||||
{'method': 'backups/detail',
|
||||
'display_param': 'False'},
|
||||
{'method': 'backups/detail',
|
||||
'display_param': '1'}
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_list_backups_with_count_param(self, method, display_param):
|
||||
self._create_multiple_backups_with_different_project()
|
||||
|
||||
is_detail = True if 'detail' in method else False
|
||||
show_count = strutils.bool_from_string(display_param, strict=True)
|
||||
# Request with 'with_count' and 'limit'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s&limit=1" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_backups(req, is_detail=is_detail)
|
||||
self.assertEqual(1, len(res_dict['backups']))
|
||||
if show_count:
|
||||
self.assertEqual(2, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
# Request with 'with_count'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_backups(req, is_detail=is_detail)
|
||||
self.assertEqual(2, len(res_dict['backups']))
|
||||
if show_count:
|
||||
self.assertEqual(2, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
# Request with admin context and 'all_tenants'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s&all_tenants=1" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_backups(req, is_detail=is_detail)
|
||||
self.assertEqual(3, len(res_dict['backups']))
|
||||
if show_count:
|
||||
self.assertEqual(3, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
@ddt.data(mv.get_prior_version(mv.RESOURCE_FILTER),
|
||||
mv.RESOURCE_FILTER,
|
||||
mv.LIKE_FILTER)
|
||||
|
@ -14,8 +14,8 @@
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
|
||||
import mock
|
||||
from oslo_utils import strutils
|
||||
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.api.v3 import snapshots
|
||||
@ -151,6 +151,97 @@ class SnapshotApiTest(test.TestCase):
|
||||
self.assertEqual(1, len(res_dict['snapshots']))
|
||||
self.assertEqual(snapshot1.id, res_dict['snapshots'][0]['id'])
|
||||
|
||||
def _create_multiple_snapshots_with_different_project(self):
|
||||
volume1 = test_utils.create_volume(self.ctx,
|
||||
project=fake.PROJECT_ID)
|
||||
volume2 = test_utils.create_volume(self.ctx,
|
||||
project=fake.PROJECT2_ID)
|
||||
test_utils.create_snapshot(
|
||||
context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True),
|
||||
volume1.id)
|
||||
test_utils.create_snapshot(
|
||||
context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True),
|
||||
volume1.id)
|
||||
test_utils.create_snapshot(
|
||||
context.RequestContext(fake.USER_ID, fake.PROJECT2_ID, True),
|
||||
volume2.id)
|
||||
|
||||
@ddt.data('snapshots', 'snapshots/detail')
|
||||
def test_list_snapshot_with_count_param_version_not_matched(self, action):
|
||||
self._create_multiple_snapshots_with_different_project()
|
||||
|
||||
is_detail = True if 'detail' in action else False
|
||||
req = fakes.HTTPRequest.blank("/v3/%s?with_count=True" % action)
|
||||
req.headers = mv.get_mv_header(
|
||||
mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
|
||||
req.api_version_request = mv.get_api_version(
|
||||
mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._items(req, is_detail=is_detail)
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
@ddt.data({'method': 'snapshots',
|
||||
'display_param': 'True'},
|
||||
{'method': 'snapshots',
|
||||
'display_param': 'False'},
|
||||
{'method': 'snapshots',
|
||||
'display_param': '1'},
|
||||
{'method': 'snapshots/detail',
|
||||
'display_param': 'True'},
|
||||
{'method': 'snapshots/detail',
|
||||
'display_param': 'False'},
|
||||
{'method': 'snapshots/detail',
|
||||
'display_param': '1'}
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_list_snapshot_with_count_param(self, method, display_param):
|
||||
self._create_multiple_snapshots_with_different_project()
|
||||
|
||||
is_detail = True if 'detail' in method else False
|
||||
show_count = strutils.bool_from_string(display_param, strict=True)
|
||||
# Request with 'with_count' and 'limit'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s&limit=1" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._items(req, is_detail=is_detail)
|
||||
self.assertEqual(1, len(res_dict['snapshots']))
|
||||
if show_count:
|
||||
self.assertEqual(2, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
# Request with 'with_count'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._items(req, is_detail=is_detail)
|
||||
self.assertEqual(2, len(res_dict['snapshots']))
|
||||
if show_count:
|
||||
self.assertEqual(2, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
# Request with admin context and 'all_tenants'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s&all_tenants=1" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._items(req, is_detail=is_detail)
|
||||
self.assertEqual(3, len(res_dict['snapshots']))
|
||||
if show_count:
|
||||
self.assertEqual(3, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
def test_snapshot_list_with_sort_name(self):
|
||||
self._create_snapshot(name='test1')
|
||||
self._create_snapshot(name='test2')
|
||||
|
@ -16,6 +16,7 @@ import ddt
|
||||
import iso8601
|
||||
|
||||
import mock
|
||||
from oslo_utils import strutils
|
||||
import webob
|
||||
|
||||
from cinder.api import extensions
|
||||
@ -113,6 +114,16 @@ class VolumeApiTest(test.TestCase):
|
||||
fake.GROUP2_ID})
|
||||
return [vol1, vol2]
|
||||
|
||||
def _create_multiple_volumes_with_different_project(self):
|
||||
# Create volumes in project 1
|
||||
db.volume_create(self.ctxt, {'display_name': 'test1',
|
||||
'project_id': fake.PROJECT_ID})
|
||||
db.volume_create(self.ctxt, {'display_name': 'test2',
|
||||
'project_id': fake.PROJECT_ID})
|
||||
# Create volume in project 2
|
||||
db.volume_create(self.ctxt, {'display_name': 'test3',
|
||||
'project_id': fake.PROJECT2_ID})
|
||||
|
||||
def test_volume_index_filter_by_glance_metadata(self):
|
||||
vols = self._create_volume_with_glance_metadata()
|
||||
req = fakes.HTTPRequest.blank("/v3/volumes?glance_metadata="
|
||||
@ -149,6 +160,82 @@ class VolumeApiTest(test.TestCase):
|
||||
self.assertEqual(1, len(volumes))
|
||||
self.assertEqual(vols[0].id, volumes[0]['id'])
|
||||
|
||||
@ddt.data('volumes', 'volumes/detail')
|
||||
def test_list_volume_with_count_param_version_not_matched(self, action):
|
||||
self._create_multiple_volumes_with_different_project()
|
||||
|
||||
is_detail = True if 'detail' in action else False
|
||||
req = fakes.HTTPRequest.blank("/v3/%s?with_count=True" % action)
|
||||
req.headers = mv.get_mv_header(
|
||||
mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
|
||||
req.api_version_request = mv.get_api_version(
|
||||
mv.get_prior_version(mv.SUPPORT_COUNT_INFO))
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_volumes(req, is_detail=is_detail)
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
@ddt.data({'method': 'volumes',
|
||||
'display_param': 'True'},
|
||||
{'method': 'volumes',
|
||||
'display_param': 'False'},
|
||||
{'method': 'volumes',
|
||||
'display_param': '1'},
|
||||
{'method': 'volumes/detail',
|
||||
'display_param': 'True'},
|
||||
{'method': 'volumes/detail',
|
||||
'display_param': 'False'},
|
||||
{'method': 'volumes/detail',
|
||||
'display_param': '1'}
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_list_volume_with_count_param(self, method, display_param):
|
||||
self._create_multiple_volumes_with_different_project()
|
||||
|
||||
is_detail = True if 'detail' in method else False
|
||||
show_count = strutils.bool_from_string(display_param, strict=True)
|
||||
# Request with 'with_count' and 'limit'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s&limit=1" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_volumes(req, is_detail=is_detail)
|
||||
self.assertEqual(1, len(res_dict['volumes']))
|
||||
if show_count:
|
||||
self.assertEqual(2, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
# Request with 'with_count'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, False)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_volumes(req, is_detail=is_detail)
|
||||
self.assertEqual(2, len(res_dict['volumes']))
|
||||
if show_count:
|
||||
self.assertEqual(2, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
# Request with admin context and 'all_tenants'
|
||||
req = fakes.HTTPRequest.blank(
|
||||
"/v3/%s?with_count=%s&all_tenants=1" % (method, display_param))
|
||||
req.headers = mv.get_mv_header(mv.SUPPORT_COUNT_INFO)
|
||||
req.api_version_request = mv.get_api_version(mv.SUPPORT_COUNT_INFO)
|
||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||
req.environ['cinder.context'] = ctxt
|
||||
res_dict = self.controller._get_volumes(req, is_detail=is_detail)
|
||||
self.assertEqual(3, len(res_dict['volumes']))
|
||||
if show_count:
|
||||
self.assertEqual(3, res_dict['count'])
|
||||
else:
|
||||
self.assertNotIn('count', res_dict)
|
||||
|
||||
def test_volume_index_filter_by_group_id_in_unsupport_version(self):
|
||||
self._create_volume_with_group()
|
||||
req = fakes.HTTPRequest.blank(("/v3/volumes?group_id=%s") %
|
||||
|
@ -535,6 +535,15 @@ class API(base.Base):
|
||||
LOG.info("Volume info retrieved successfully.", resource=volume)
|
||||
return volume
|
||||
|
||||
def calculate_resource_count(self, context, resource_type, filters):
|
||||
filters = filters if filters else {}
|
||||
allTenants = utils.get_bool_param('all_tenants', filters)
|
||||
if context.is_admin and allTenants:
|
||||
del filters['all_tenants']
|
||||
else:
|
||||
filters['project_id'] = context.project_id
|
||||
return db.calculate_resource_count(context, resource_type, filters)
|
||||
|
||||
def get_all(self, context, marker=None, limit=None, sort_keys=None,
|
||||
sort_dirs=None, filters=None, viewable_admin_meta=False,
|
||||
offset=None):
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added count info in volume, snapshot and backup's list APIs since 3.45.
|
Loading…
Reference in New Issue
Block a user