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.9 - Add backup update interface.
* 3.10 - Add group_id filter to list/detail volumes in _get_volumes. * 3.10 - Add group_id filter to list/detail volumes in _get_volumes.
* 3.11 - Add group types and group specs API. * 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. # minimum version of the API supported.
# Explicitly using /v1 or /v2 enpoints will still work # Explicitly using /v1 or /v2 enpoints will still work
_MIN_API_VERSION = "3.0" _MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.11" _MAX_API_VERSION = "3.12"
_LEGACY_API_VERSION1 = "1.0" _LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0" _LEGACY_API_VERSION2 = "2.0"

View File

@ -174,3 +174,7 @@ user documentation.
3.11 3.11
---- ----
Added group types and group specs API. 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) self.resources['volumes'] = volumes.create_resource(ext_mgr)
mapper.resource("volume", "volumes", mapper.resource("volume", "volumes",
controller=self.resources['volumes'], controller=self.resources['volumes'],
collection={'detail': 'GET'}, collection={'detail': 'GET', 'summary': 'GET'},
member={'action': 'POST'}) member={'action': 'POST'})
self.resources['messages'] = messages.create_resource(ext_mgr) 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 import common
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.api.v2 import volumes as volumes_v2 from cinder.api.v2 import volumes as volumes_v2
from cinder.api.v3.views import volumes as volume_views_v3
from cinder import utils from cinder import utils
SUMMARY_BASE_MICRO_VERSION = '3.12'
class VolumeController(volumes_v2.VolumeController): class VolumeController(volumes_v2.VolumeController):
"""The Volumes API controller for the OpenStack API V3.""" """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) volumes = self._view_builder.summary_list(req, volumes)
return 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): def create_resource(ext_mgr):
return wsgi.Resource(VolumeController(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) 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): def volume_update(context, volume_id, values):
"""Set the given properties on a volume and update it. """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() 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 @require_admin_context
def volume_get_all_by_host(context, host, filters=None): def volume_get_all_by_host(context, host, filters=None):
"""Retrieves all volumes hosted on a host. """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 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 @handle_db_data_error
@require_context @require_context
def volume_update(context, volume_id, values): 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) expected_attrs = cls._get_expected_attrs(context)
return base.obj_make_list(context, cls(context), objects.Volume, return base.obj_make_list(context, cls(context), objects.Volume,
volumes, expected_attrs=expected_attrs) 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) res_dict = self.controller.detail(req)
self.assertTrue(mock_validate.called) self.assertTrue(mock_validate.called)
def _vol_in_request_body(self, @classmethod
def _vol_in_request_body(cls,
size=stubs.DEFAULT_VOL_SIZE, size=stubs.DEFAULT_VOL_SIZE,
name=stubs.DEFAULT_VOL_NAME, name=stubs.DEFAULT_VOL_NAME,
description=stubs.DEFAULT_VOL_DESCRIPTION, 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.api.v3 import volumes
from cinder import context from cinder import context
from cinder import db from cinder import db
from cinder import exception
from cinder import test from cinder import test
from cinder.tests.unit.api import fakes 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.tests.unit import fake_constants as fake
from cinder.volume.api import API as vol_get from cinder.volume.api import API as vol_get
@ -145,3 +147,33 @@ class VolumeApiTest(test.TestCase):
res_dict = self.controller.index(req) res_dict = self.controller.index(req)
volumes = res_dict['volumes'] volumes = res_dict['volumes']
self.assertEqual(2, len(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.")) LOG.info(_LI("Get all volumes completed successfully."))
return volumes 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): def get_snapshot(self, context, snapshot_id):
check_policy(context, 'get_snapshot') check_policy(context, 'get_snapshot')
snapshot = objects.Snapshot.get_by_id(context, snapshot_id) 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.