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: 1c8fe0ade43da925c5b810ef0cd27817f1c11c7b 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
|
- limit: limit
|
||||||
- offset: offset
|
- offset: offset
|
||||||
- marker: marker
|
- marker: marker
|
||||||
|
- with_count: with_count
|
||||||
|
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
@ -88,6 +89,7 @@ Response Parameters
|
|||||||
- data_timestamp: data_timestamp
|
- data_timestamp: data_timestamp
|
||||||
- snapshot_id: snapshot_id_2
|
- snapshot_id: snapshot_id_2
|
||||||
- os-backup-project-attr:project_id: os-backup-project-attr:project_id
|
- os-backup-project-attr:project_id: os-backup-project-attr:project_id
|
||||||
|
- count: count
|
||||||
|
|
||||||
Response Example
|
Response Example
|
||||||
----------------
|
----------------
|
||||||
@ -329,6 +331,7 @@ Request
|
|||||||
- sort: sort
|
- sort: sort
|
||||||
- limit: limit
|
- limit: limit
|
||||||
- marker: marker
|
- marker: marker
|
||||||
|
- with_count: with_count
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
-------------------
|
-------------------
|
||||||
@ -339,6 +342,7 @@ Response Parameters
|
|||||||
- id: id_1
|
- id: id_1
|
||||||
- links: links_1
|
- links: links_1
|
||||||
- name: name_1
|
- name: name_1
|
||||||
|
- count: count
|
||||||
|
|
||||||
Response Example
|
Response Example
|
||||||
----------------
|
----------------
|
||||||
|
@ -398,6 +398,13 @@ vol_type_id_query:
|
|||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
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
|
# variables in body
|
||||||
absolute:
|
absolute:
|
||||||
@ -740,6 +747,13 @@ control_location:
|
|||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
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:
|
create-from-src:
|
||||||
description: |
|
description: |
|
||||||
The create from source action.
|
The create from source action.
|
||||||
|
@ -54,5 +54,6 @@
|
|||||||
"is_incremental": true,
|
"is_incremental": true,
|
||||||
"has_dependent_backups": false
|
"has_dependent_backups": false
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"count": 10
|
||||||
}
|
}
|
||||||
|
@ -56,5 +56,6 @@
|
|||||||
"id": "4dbf0ec2-0b57-4669-9823-9f7c76f2b4f8",
|
"id": "4dbf0ec2-0b57-4669-9823-9f7c76f2b4f8",
|
||||||
"size": 1
|
"size": 1
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"count": 10
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,6 @@
|
|||||||
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
|
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
|
||||||
"description": "volume snapshot"
|
"description": "volume snapshot"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"count": 10
|
||||||
}
|
}
|
||||||
|
@ -16,5 +16,6 @@
|
|||||||
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
|
"id": "b1323cda-8e4b-41c1-afc5-2fc791809c8c",
|
||||||
"description": "volume snapshot"
|
"description": "volume snapshot"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"count": 10
|
||||||
}
|
}
|
||||||
|
@ -98,5 +98,6 @@
|
|||||||
"created_at": "2015-11-29T02:25:18.000000",
|
"created_at": "2015-11-29T02:25:18.000000",
|
||||||
"volume_type": "lvmdriver-1"
|
"volume_type": "lvmdriver-1"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"count": 10
|
||||||
}
|
}
|
||||||
|
@ -28,5 +28,6 @@
|
|||||||
],
|
],
|
||||||
"name": "vol-003"
|
"name": "vol-003"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"count": 10
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ Request
|
|||||||
- limit: limit
|
- limit: limit
|
||||||
- offset: offset
|
- offset: offset
|
||||||
- marker: marker
|
- marker: marker
|
||||||
|
- with_count: with_count
|
||||||
|
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
@ -77,6 +78,7 @@ Response Parameters
|
|||||||
- size: size
|
- size: size
|
||||||
- id: id
|
- id: id
|
||||||
- metadata: metadata
|
- metadata: metadata
|
||||||
|
- count: count
|
||||||
|
|
||||||
Response Example
|
Response Example
|
||||||
----------------
|
----------------
|
||||||
@ -164,6 +166,7 @@ Request
|
|||||||
- limit: limit
|
- limit: limit
|
||||||
- offset: offset
|
- offset: offset
|
||||||
- marker: marker
|
- marker: marker
|
||||||
|
- with_count: with_count
|
||||||
|
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
@ -180,6 +183,7 @@ Response Parameters
|
|||||||
- metadata: metadata
|
- metadata: metadata
|
||||||
- id: id
|
- id: id
|
||||||
- size: size
|
- size: size
|
||||||
|
- count: count
|
||||||
|
|
||||||
Response Example
|
Response Example
|
||||||
----------------
|
----------------
|
||||||
|
@ -86,6 +86,7 @@ Request
|
|||||||
- limit: limit
|
- limit: limit
|
||||||
- offset: offset
|
- offset: offset
|
||||||
- marker: marker
|
- marker: marker
|
||||||
|
- with_count: with_count
|
||||||
|
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
@ -121,6 +122,7 @@ Response Parameters
|
|||||||
- os-volume-replication:driver_data: os-volume-replication:driver_data
|
- os-volume-replication:driver_data: os-volume-replication:driver_data
|
||||||
- volumes: volumes
|
- volumes: volumes
|
||||||
- volume_type: volume_type
|
- volume_type: volume_type
|
||||||
|
- count: count
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -260,6 +262,7 @@ Request
|
|||||||
- limit: limit
|
- limit: limit
|
||||||
- offset: offset
|
- offset: offset
|
||||||
- marker: marker
|
- marker: marker
|
||||||
|
- with_count: with_count
|
||||||
|
|
||||||
|
|
||||||
Response Parameters
|
Response Parameters
|
||||||
@ -271,6 +274,7 @@ Response Parameters
|
|||||||
- id: id_5
|
- id: id_5
|
||||||
- links: links_3
|
- links: links_3
|
||||||
- name: name_13
|
- name: name_13
|
||||||
|
- count: count
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ from cinder import backup as backupAPI
|
|||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
|
from cinder import volume as volumeAPI
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ class BackupsController(wsgi.Controller):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.backup_api = backupAPI.API()
|
self.backup_api = backupAPI.API()
|
||||||
|
self.volume_api = volumeAPI.API()
|
||||||
super(BackupsController, self).__init__()
|
super(BackupsController, self).__init__()
|
||||||
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
@ -100,6 +102,10 @@ class BackupsController(wsgi.Controller):
|
|||||||
marker, limit, offset = common.get_pagination_params(filters)
|
marker, limit, offset = common.get_pagination_params(filters)
|
||||||
sort_keys, sort_dirs = common.get_sort_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._convert_sort_name(req_version, sort_keys)
|
||||||
self._process_backup_filtering(context=context, filters=filters,
|
self._process_backup_filtering(context=context, filters=filters,
|
||||||
req_version=req_version)
|
req_version=req_version)
|
||||||
@ -107,7 +113,7 @@ class BackupsController(wsgi.Controller):
|
|||||||
if 'name' in filters:
|
if 'name' in filters:
|
||||||
filters['display_name'] = filters.pop('name')
|
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,
|
marker=marker,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
@ -115,12 +121,18 @@ class BackupsController(wsgi.Controller):
|
|||||||
sort_dirs=sort_dirs,
|
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)
|
req.cache_db_backups(backups.objects)
|
||||||
|
|
||||||
if is_detail:
|
if is_detail:
|
||||||
backups = self._view_builder.detail_list(req, backups.objects)
|
backups = self._view_builder.detail_list(req, backups.objects,
|
||||||
|
total_count)
|
||||||
else:
|
else:
|
||||||
backups = self._view_builder.summary_list(req, backups.objects)
|
backups = self._view_builder.summary_list(req, backups.objects,
|
||||||
|
total_count)
|
||||||
return backups
|
return backups
|
||||||
|
|
||||||
# TODO(frankm): Add some checks here including
|
# TODO(frankm): Add some checks here including
|
||||||
|
@ -127,6 +127,8 @@ BACKUP_METADATA = '3.43'
|
|||||||
|
|
||||||
NEW_ATTACH_COMPLETION = '3.44'
|
NEW_ATTACH_COMPLETION = '3.44'
|
||||||
|
|
||||||
|
SUPPORT_COUNT_INFO = '3.45'
|
||||||
|
|
||||||
|
|
||||||
def get_mv_header(version):
|
def get_mv_header(version):
|
||||||
"""Gets a formatted HTTP microversion header.
|
"""Gets a formatted HTTP microversion header.
|
||||||
|
@ -107,6 +107,8 @@ REST_API_VERSION_HISTORY = """
|
|||||||
state is intentionally NOT allowed.
|
state is intentionally NOT allowed.
|
||||||
* 3.43 - Support backup CRUD with metadata.
|
* 3.43 - Support backup CRUD with metadata.
|
||||||
* 3.44 - Add attachment-complete.
|
* 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
|
# The minimum and maximum versions of the API supported
|
||||||
@ -114,7 +116,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
# minimum version of the API supported.
|
# minimum version of the API supported.
|
||||||
# Explicitly using /v2 endpoints will still work
|
# Explicitly using /v2 endpoints will still work
|
||||||
_MIN_API_VERSION = "3.0"
|
_MIN_API_VERSION = "3.0"
|
||||||
_MAX_API_VERSION = "3.44"
|
_MAX_API_VERSION = "3.45"
|
||||||
_LEGACY_API_VERSION2 = "2.0"
|
_LEGACY_API_VERSION2 = "2.0"
|
||||||
UPDATED = "2017-09-19T20:18:14Z"
|
UPDATED = "2017-09-19T20:18:14Z"
|
||||||
|
|
||||||
|
@ -373,3 +373,7 @@ user documentation.
|
|||||||
Support attachment completion. See the
|
Support attachment completion. See the
|
||||||
`API reference <https://developer.openstack.org/api-ref/block-storage/v3/index.html#complete-attachment>`__
|
`API reference <https://developer.openstack.org/api-ref/block-storage/v3/index.html#complete-attachment>`__
|
||||||
for details.
|
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)
|
sort_keys, sort_dirs = common.get_sort_params(search_opts)
|
||||||
marker, limit, offset = common.get_pagination_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
|
# process filters
|
||||||
self._process_snapshot_filtering(context=context,
|
self._process_snapshot_filtering(context=context,
|
||||||
filters=search_opts,
|
filters=search_opts,
|
||||||
@ -93,20 +99,27 @@ class SnapshotsController(snapshots_v2.SnapshotsController):
|
|||||||
if 'name' in search_opts:
|
if 'name' in search_opts:
|
||||||
search_opts['display_name'] = search_opts.pop('name')
|
search_opts['display_name'] = search_opts.pop('name')
|
||||||
|
|
||||||
snapshots = self.volume_api.get_all_snapshots(context,
|
snapshots = self.volume_api.get_all_snapshots(
|
||||||
search_opts=search_opts,
|
context,
|
||||||
marker=marker,
|
search_opts=search_opts.copy(),
|
||||||
limit=limit,
|
marker=marker,
|
||||||
sort_keys=sort_keys,
|
limit=limit,
|
||||||
sort_dirs=sort_dirs,
|
sort_keys=sort_keys,
|
||||||
offset=offset)
|
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)
|
req.cache_db_snapshots(snapshots.objects)
|
||||||
|
|
||||||
if is_detail:
|
if is_detail:
|
||||||
snapshots = self._view_builder.detail_list(req, snapshots.objects)
|
snapshots = self._view_builder.detail_list(req, snapshots.objects,
|
||||||
|
total_count)
|
||||||
else:
|
else:
|
||||||
snapshots = self._view_builder.summary_list(req, snapshots.objects)
|
snapshots = self._view_builder.summary_list(req, snapshots.objects,
|
||||||
|
total_count)
|
||||||
return snapshots
|
return snapshots
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ from cinder.api.v2.views import volumes as views_v2
|
|||||||
class ViewBuilder(views_v2.ViewBuilder):
|
class ViewBuilder(views_v2.ViewBuilder):
|
||||||
"""Model a volumes API V3 response as a python dictionary."""
|
"""Model a volumes API V3 response as a python dictionary."""
|
||||||
|
|
||||||
|
_collection_name = "volumes"
|
||||||
|
|
||||||
def quick_summary(self, volume_count, volume_size,
|
def quick_summary(self, volume_count, volume_size,
|
||||||
all_distinct_metadata=None):
|
all_distinct_metadata=None):
|
||||||
"""View of volumes summary.
|
"""View of volumes summary.
|
||||||
@ -53,3 +55,32 @@ class ViewBuilder(views_v2.ViewBuilder):
|
|||||||
volume_ref['volume']['provider_id'] = volume.get('provider_id')
|
volume_ref['volume']['provider_id'] = volume.get('provider_id')
|
||||||
|
|
||||||
return volume_ref
|
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)
|
sort_keys, sort_dirs = common.get_sort_params(params)
|
||||||
filters = 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,
|
self._process_volume_filtering(context=context, filters=filters,
|
||||||
req_version=req_version)
|
req_version=req_version)
|
||||||
|
|
||||||
@ -114,9 +119,13 @@ class VolumeController(volumes_v2.VolumeController):
|
|||||||
volumes = self.volume_api.get_all(context, marker, limit,
|
volumes = self.volume_api.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.copy(),
|
||||||
viewable_admin_meta=True,
|
viewable_admin_meta=True,
|
||||||
offset=offset)
|
offset=offset)
|
||||||
|
total_count = None
|
||||||
|
if show_count:
|
||||||
|
total_count = self.volume_api.calculate_resource_count(
|
||||||
|
context, 'volume', filters)
|
||||||
|
|
||||||
for volume in volumes:
|
for volume in volumes:
|
||||||
utils.add_visible_admin_metadata(volume)
|
utils.add_visible_admin_metadata(volume)
|
||||||
@ -124,9 +133,11 @@ class VolumeController(volumes_v2.VolumeController):
|
|||||||
req.cache_db_volumes(volumes.objects)
|
req.cache_db_volumes(volumes.objects)
|
||||||
|
|
||||||
if is_detail:
|
if is_detail:
|
||||||
volumes = self._view_builder.detail_list(req, volumes)
|
volumes = self._view_builder.detail_list(
|
||||||
|
req, volumes, total_count)
|
||||||
else:
|
else:
|
||||||
volumes = self._view_builder.summary_list(req, volumes)
|
volumes = self._view_builder.summary_list(
|
||||||
|
req, volumes, total_count)
|
||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
@wsgi.Controller.api_version(mv.VOLUME_SUMMARY)
|
@wsgi.Controller.api_version(mv.VOLUME_SUMMARY)
|
||||||
|
@ -92,6 +92,9 @@ class ViewBuilder(common.ViewBuilder):
|
|||||||
if backups_links:
|
if backups_links:
|
||||||
backups_dict['backups_links'] = backups_links
|
backups_dict['backups_links'] = backups_links
|
||||||
|
|
||||||
|
if backup_count is not None:
|
||||||
|
backups_dict['count'] = backup_count
|
||||||
|
|
||||||
return backups_dict
|
return backups_dict
|
||||||
|
|
||||||
def export_summary(self, request, export):
|
def export_summary(self, request, export):
|
||||||
|
@ -75,4 +75,7 @@ class ViewBuilder(common.ViewBuilder):
|
|||||||
if snapshots_links:
|
if snapshots_links:
|
||||||
snapshots_dict[self._collection_name + '_links'] = snapshots_links
|
snapshots_dict[self._collection_name + '_links'] = snapshots_links
|
||||||
|
|
||||||
|
if snapshot_count is not None:
|
||||||
|
snapshots_dict['count'] = snapshot_count
|
||||||
|
|
||||||
return snapshots_dict
|
return snapshots_dict
|
||||||
|
@ -281,6 +281,10 @@ def volume_get_all(context, marker=None, limit=None, sort_keys=None,
|
|||||||
offset=offset)
|
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):
|
def volume_get_all_by_host(context, host, filters=None):
|
||||||
"""Get all volumes belonging to a host."""
|
"""Get all volumes belonging to a host."""
|
||||||
return IMPL.volume_get_all_by_host(context, host, filters=filters)
|
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)
|
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)
|
@apply_like_filters(model=models.Volume)
|
||||||
def _process_volume_filters(query, filters):
|
def _process_volume_filters(query, filters):
|
||||||
"""Common filter processing for Volume queries.
|
"""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 ddt
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_utils import strutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api import microversions as mv
|
from cinder.api import microversions as mv
|
||||||
@ -88,6 +89,90 @@ class BackupsControllerAPITestCase(test.TestCase):
|
|||||||
self.controller.update,
|
self.controller.update,
|
||||||
req, fake.BACKUP_ID, body)
|
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),
|
@ddt.data(mv.get_prior_version(mv.RESOURCE_FILTER),
|
||||||
mv.RESOURCE_FILTER,
|
mv.RESOURCE_FILTER,
|
||||||
mv.LIKE_FILTER)
|
mv.LIKE_FILTER)
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
from cinder.api import microversions as mv
|
from cinder.api import microversions as mv
|
||||||
from cinder.api.v3 import snapshots
|
from cinder.api.v3 import snapshots
|
||||||
@ -151,6 +151,97 @@ class SnapshotApiTest(test.TestCase):
|
|||||||
self.assertEqual(1, len(res_dict['snapshots']))
|
self.assertEqual(1, len(res_dict['snapshots']))
|
||||||
self.assertEqual(snapshot1.id, res_dict['snapshots'][0]['id'])
|
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):
|
def test_snapshot_list_with_sort_name(self):
|
||||||
self._create_snapshot(name='test1')
|
self._create_snapshot(name='test1')
|
||||||
self._create_snapshot(name='test2')
|
self._create_snapshot(name='test2')
|
||||||
|
@ -16,6 +16,7 @@ import ddt
|
|||||||
import iso8601
|
import iso8601
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_utils import strutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder.api import extensions
|
from cinder.api import extensions
|
||||||
@ -113,6 +114,16 @@ class VolumeApiTest(test.TestCase):
|
|||||||
fake.GROUP2_ID})
|
fake.GROUP2_ID})
|
||||||
return [vol1, vol2]
|
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):
|
def test_volume_index_filter_by_glance_metadata(self):
|
||||||
vols = self._create_volume_with_glance_metadata()
|
vols = self._create_volume_with_glance_metadata()
|
||||||
req = fakes.HTTPRequest.blank("/v3/volumes?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(1, len(volumes))
|
||||||
self.assertEqual(vols[0].id, volumes[0]['id'])
|
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):
|
def test_volume_index_filter_by_group_id_in_unsupport_version(self):
|
||||||
self._create_volume_with_group()
|
self._create_volume_with_group()
|
||||||
req = fakes.HTTPRequest.blank(("/v3/volumes?group_id=%s") %
|
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)
|
LOG.info("Volume info retrieved successfully.", resource=volume)
|
||||||
return 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,
|
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):
|
offset=None):
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added count info in volume, snapshot and backup's list APIs since 3.45.
|
Loading…
x
Reference in New Issue
Block a user