Add volumes/summary API support

Add a new API to display volumes summary with total number of volumes
and total size in GB (total_size: 500 and total_count: 20).

DocImpact
APIImpact
Partially-Implements: blueprint display-volumes-summary-details

Co-Authored-By: MR Swami Reddy <swamireddy@gmail.com>

Change-Id: I0117a233afbd834a165ade9e1358f8dd38fbcfac
This commit is contained in:
Satish Venkatasubramanian 2016-08-15 00:24:16 +05:30
parent 8da1781c18
commit 3db21d003f
12 changed files with 159 additions and 3 deletions

View File

@ -59,6 +59,7 @@ REST_API_VERSION_HISTORY = """
* 3.9 - Add backup update interface.
* 3.10 - Add group_id filter to list/detail volumes in _get_volumes.
* 3.11 - Add group types and group specs API.
* 3.12 - Add volumes summary API.
"""
@ -67,7 +68,7 @@ REST_API_VERSION_HISTORY = """
# minimum version of the API supported.
# Explicitly using /v1 or /v2 enpoints will still work
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.11"
_MAX_API_VERSION = "3.12"
_LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0"

View File

@ -174,3 +174,7 @@ user documentation.
3.11
----
Added group types and group specs API.
3.12
----
Added volumes/summary API.

View File

@ -53,7 +53,7 @@ class APIRouter(cinder.api.openstack.APIRouter):
self.resources['volumes'] = volumes.create_resource(ext_mgr)
mapper.resource("volume", "volumes",
controller=self.resources['volumes'],
collection={'detail': 'GET'},
collection={'detail': 'GET', 'summary': 'GET'},
member={'action': 'POST'})
self.resources['messages'] = messages.create_resource(ext_mgr)

View File

@ -0,0 +1,24 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class ViewBuilder(object):
"""Model a server API response as a python dictionary."""
def quick_summary(self, volume_count, volume_size):
"""Number of volumes and size of volumes."""
return {
'volume-summary': {
'total_count': volume_count,
'total_size': volume_size
},
}

View File

@ -16,8 +16,11 @@
from cinder.api import common
from cinder.api.openstack import wsgi
from cinder.api.v2 import volumes as volumes_v2
from cinder.api.v3.views import volumes as volume_views_v3
from cinder import utils
SUMMARY_BASE_MICRO_VERSION = '3.12'
class VolumeController(volumes_v2.VolumeController):
"""The Volumes API controller for the OpenStack API V3."""
@ -72,6 +75,19 @@ class VolumeController(volumes_v2.VolumeController):
volumes = self._view_builder.summary_list(req, volumes)
return volumes
@wsgi.Controller.api_version(SUMMARY_BASE_MICRO_VERSION)
def summary(self, req):
"""Return summary of volumes."""
view_builder_v3 = volume_views_v3.ViewBuilder()
context = req.environ['cinder.context']
filters = req.params.copy()
utils.remove_invalid_filter_options(context, filters,
self._get_volume_filter_options())
volumes = self.volume_api.get_volume_summary(context, filters=filters)
return view_builder_v3.quick_summary(volumes[0], int(volumes[1]))
def create_resource(ext_mgr):
return wsgi.Resource(VolumeController(ext_mgr))

View File

@ -280,6 +280,16 @@ def volume_get_all_by_project(context, project_id, marker, limit,
offset=offset)
def get_volume_summary_all(context):
"""Get all volume summary."""
return IMPL.get_volume_summary_all(context)
def get_volume_summary_by_project(context, project_id):
"""Get all volume summary belonging to a project."""
return IMPL.get_volume_summary_by_project(context, project_id)
def volume_update(context, volume_id, values):
"""Set the given properties on a volume and update it.

View File

@ -1775,6 +1775,23 @@ def volume_get_all(context, marker, limit, sort_keys=None, sort_dirs=None,
return query.all()
@require_admin_context
def get_volume_summary_all(context):
"""Retrieves all volumes summary.
:param context: context to query under
:returns: volume summary of all projects
"""
query = model_query(context, func.count(models.Volume.id),
func.sum(models.Volume.size), read_deleted="no")
if query is None:
return []
result = query.first()
return (result[0] or 0, result[1] or 0)
@require_admin_context
def volume_get_all_by_host(context, host, filters=None):
"""Retrieves all volumes hosted on a host.
@ -2077,6 +2094,25 @@ def process_sort_params(sort_keys, sort_dirs, default_keys=None,
return result_keys, result_dirs
@require_context
def get_volume_summary_by_project(context, project_id):
"""Retrieves all volumes summary in a project.
:param context: context to query under
:param project_id: project for all volumes being retrieved
:returns: volume summary of a project
"""
query = model_query(context, func.count(models.Volume.id),
func.sum(models.Volume.size), read_deleted="no").\
filter_by(project_id=project_id)
if query is None:
return []
result = query.first()
return (result[0] or 0, result[1] or 0)
@handle_db_data_error
@require_context
def volume_update(context, volume_id, values):

View File

@ -537,3 +537,13 @@ class VolumeList(base.ObjectListBase, base.CinderObject):
expected_attrs = cls._get_expected_attrs(context)
return base.obj_make_list(context, cls(context), objects.Volume,
volumes, expected_attrs=expected_attrs)
@classmethod
def get_volume_summary_all(cls, context):
volumes = db.get_volume_summary_all(context)
return volumes
@classmethod
def get_volume_summary_by_project(cls, context, project_id):
volumes = db.get_volume_summary_by_project(context, project_id)
return volumes

View File

@ -128,7 +128,8 @@ class VolumeApiTest(test.TestCase):
res_dict = self.controller.detail(req)
self.assertTrue(mock_validate.called)
def _vol_in_request_body(self,
@classmethod
def _vol_in_request_body(cls,
size=stubs.DEFAULT_VOL_SIZE,
name=stubs.DEFAULT_VOL_NAME,
description=stubs.DEFAULT_VOL_DESCRIPTION,

View File

@ -20,8 +20,10 @@ from cinder.api.openstack import api_version_request as api_version
from cinder.api.v3 import volumes
from cinder import context
from cinder import db
from cinder import exception
from cinder import test
from cinder.tests.unit.api import fakes
from cinder.tests.unit.api.v2 import test_volumes as v2_test_volumes
from cinder.tests.unit import fake_constants as fake
from cinder.volume.api import API as vol_get
@ -145,3 +147,33 @@ class VolumeApiTest(test.TestCase):
res_dict = self.controller.index(req)
volumes = res_dict['volumes']
self.assertEqual(2, len(volumes))
def _fake_volumes_summary_request(self, version='3.12'):
req = fakes.HTTPRequest.blank('/v3/volumes/summary')
req.headers = {'OpenStack-API-Version': 'volume ' + version}
req.api_version_request = api_version.APIVersionRequest(version)
return req
def test_volumes_summary_in_unsupport_version(self):
"""Function call to test summary volumes API in unsupported version"""
req = self._fake_volumes_summary_request(version='3.7')
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.summary, req)
def test_volumes_summary_in_supported_version(self):
"""Function call to test the summary volumes API for version v3."""
req = self._fake_volumes_summary_request()
res_dict = self.controller.summary(req)
expected = {'volume-summary': {'total_size': 0.0, 'total_count': 0}}
self.assertEqual(expected, res_dict)
vol = v2_test_volumes.VolumeApiTest._vol_in_request_body(
availability_zone="nova")
body = {"volume": vol}
req = fakes.HTTPRequest.blank('/v3/volumes')
res_dict = self.controller.create(req, body)
req = self._fake_volumes_summary_request()
res_dict = self.controller.summary(req)
expected = {'volume-summary': {'total_size': 1.0, 'total_count': 1}}
self.assertEqual(expected, res_dict)

View File

@ -523,6 +523,24 @@ class API(base.Base):
LOG.info(_LI("Get all volumes completed successfully."))
return volumes
def get_volume_summary(self, context, filters=None):
check_policy(context, 'get_all')
if filters is None:
filters = {}
allTenants = utils.get_bool_param('all_tenants', filters)
if context.is_admin and allTenants:
del filters['all_tenants']
volumes = objects.VolumeList.get_volume_summary_all(context)
else:
volumes = objects.VolumeList.get_volume_summary_by_project(
context, context.project_id)
LOG.info(_LI("Get summary completed successfully."))
return volumes
def get_snapshot(self, context, snapshot_id):
check_policy(context, 'get_snapshot')
snapshot = objects.Snapshot.get_by_id(context, snapshot_id)

View File

@ -0,0 +1,4 @@
---
features:
- A new API to display the volumes summary. This summary API displays the
total number of volumes and total volume's size in GB.