Return metadata in volume summary

Cinder supports filter volumes with metadata, but in some case,
users don't know what metadata the volumes contain or what metadata
is valid to filter volumes.

This patch updated volumes/summary API to return all valid distinct
metadata to users. Then users could use these metadatas to filter
volumes easily.

This function is useful for dashboard, such as Horizon, as well.

APIImpact

Implements: blueprint metadata-for-volume-summary
Change-Id: I33c77d9db88f70d8d3b8ea86c86c01220dcc537c
This commit is contained in:
wangxiyuan 2017-04-24 10:06:07 +08:00
parent 1f7adde05b
commit bf40945dcc
8 changed files with 116 additions and 11 deletions

View File

@ -89,7 +89,7 @@ REST_API_VERSION_HISTORY = """
``message``, ``attachment``, ``group`` and ``group-snapshot``
list APIs.
* 3.35 - Add ``volume-type`` filter to Get-Pools API.
* 3.36 - Add metadata to volumes/summary response body.
"""
# The minimum and maximum versions of the API supported
@ -97,7 +97,7 @@ REST_API_VERSION_HISTORY = """
# minimum version of the API supported.
# Explicitly using /v1 or /v2 endpoints will still work
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.35"
_MAX_API_VERSION = "3.36"
_LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0"

View File

@ -317,3 +317,7 @@ user documentation.
3.35
----
Add ``volume-type`` filter to Get-Pools API.
3.36
----
Add metadata to volumes/summary response body.

View File

@ -19,14 +19,22 @@ 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."""
def quick_summary(self, volume_count, volume_size):
"""Number of volumes and size of volumes."""
return {
def quick_summary(self, volume_count, volume_size,
all_distinct_metadata=None):
"""View of volumes summary.
It includes number of volumes, size of volumes and all distinct
metadata of volumes.
"""
summary = {
'volume-summary': {
'total_count': volume_count,
'total_size': volume_size
},
}
}
if all_distinct_metadata is not None:
summary['volume-summary']['metadata'] = all_distinct_metadata
return summary
def detail(self, request, volume):
"""Detailed view of a single volume."""

View File

@ -148,8 +148,17 @@ class VolumeController(volumes_v2.VolumeController):
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]))
num_vols, sum_size, metadata = self.volume_api.get_volume_summary(
context, filters=filters)
req_version = req.api_version_request
if req_version.matches("3.36"):
all_distinct_metadata = metadata
else:
all_distinct_metadata = None
return view_builder_v3.quick_summary(num_vols, int(sum_size),
all_distinct_metadata)
@wsgi.response(http_client.ACCEPTED)
def create(self, req, body):

View File

@ -2100,7 +2100,22 @@ def get_volume_summary(context, project_only):
return []
result = query.first()
return (result[0] or 0, result[1] or 0)
query_metadata = model_query(
context, models.VolumeMetadata.key, models.VolumeMetadata.value,
read_deleted="no")
if project_only:
query_metadata = query_metadata.join(
models.Volume,
models.Volume.id == models.VolumeMetadata.volume_id).filter_by(
project_id=context.project_id)
result_metadata = query_metadata.distinct().all()
result_metadata_list = collections.defaultdict(list)
for key, value in result_metadata:
result_metadata_list[key].append(value)
return (result[0] or 0, result[1] or 0, result_metadata_list)
@require_admin_context

View File

@ -30,6 +30,7 @@ from cinder.tests.unit.api import fakes
from cinder.tests.unit.api.v2 import fakes as v2_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 utils as test_utils
from cinder import utils
from cinder.volume import api as volume_api
from cinder.volume import api as vol_get
@ -156,8 +157,12 @@ class VolumeApiTest(test.TestCase):
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')
def _fake_volumes_summary_request(self, version='3.12', all_tenant=False,
is_admin=False):
req_url = '/v3/volumes/summary'
if all_tenant:
req_url += '?all_tenants=True'
req = fakes.HTTPRequest.blank(req_url, use_admin_context=is_admin)
req.headers = {'OpenStack-API-Version': 'volume ' + version}
req.api_version_request = api_version.APIVersionRequest(version)
return req
@ -186,6 +191,63 @@ class VolumeApiTest(test.TestCase):
expected = {'volume-summary': {'total_size': 1.0, 'total_count': 1}}
self.assertEqual(expected, res_dict)
@ddt.data(
('3.35', {'volume-summary': {'total_size': 0.0,
'total_count': 0}}),
('3.36', {'volume-summary': {'total_size': 0.0,
'total_count': 0,
'metadata': {}}}))
@ddt.unpack
def test_volume_summary_empty(self, summary_api_version, expect_result):
req = self._fake_volumes_summary_request(version=summary_api_version)
res_dict = self.controller.summary(req)
self.assertEqual(expect_result, res_dict)
@ddt.data(
('3.35', {'volume-summary': {'total_size': 2,
'total_count': 2}}),
('3.36', {'volume-summary': {'total_size': 2,
'total_count': 2,
'metadata': {
'name': ['test_name1', 'test_name2'],
'age': ['test_age']}}}))
@ddt.unpack
def test_volume_summary_return_metadata(self, summary_api_version,
expect_result):
test_utils.create_volume(self.ctxt, metadata={'name': 'test_name1',
'age': 'test_age'})
test_utils.create_volume(self.ctxt, metadata={'name': 'test_name2',
'age': 'test_age'})
ctxt2 = context.RequestContext(fake.USER_ID, fake.PROJECT2_ID, True)
test_utils.create_volume(ctxt2, metadata={'name': 'test_name3'})
req = self._fake_volumes_summary_request(version=summary_api_version)
res_dict = self.controller.summary(req)
self.assertEqual(expect_result, res_dict)
@ddt.data(
('3.35', {'volume-summary': {'total_size': 2,
'total_count': 2}}),
('3.36', {'volume-summary': {'total_size': 2,
'total_count': 2,
'metadata': {
'name': ['test_name1', 'test_name2'],
'age': ['test_age']}}}))
@ddt.unpack
def test_volume_summary_return_metadata_all_tenant(
self, summary_api_version, expect_result):
test_utils.create_volume(self.ctxt, metadata={'name': 'test_name1',
'age': 'test_age'})
ctxt2 = context.RequestContext(fake.USER_ID, fake.PROJECT2_ID, True)
test_utils.create_volume(ctxt2, metadata={'name': 'test_name2',
'age': 'test_age'})
req = self._fake_volumes_summary_request(version=summary_api_version,
all_tenant=True,
is_admin=True)
res_dict = self.controller.summary(req)
self.assertEqual(expect_result, res_dict)
def _vol_in_request_body(self,
size=v2_fakes.DEFAULT_VOL_SIZE,
name=v2_fakes.DEFAULT_VOL_NAME,

View File

@ -69,6 +69,7 @@ def create_volume(ctxt,
previous_status=None,
testcase_instance=None,
id=None,
metadata=None,
**kwargs):
"""Create a volume object in the DB."""
vol = {}
@ -89,6 +90,8 @@ def create_volume(ctxt,
vol['group_id'] = group_id
if volume_type_id:
vol['volume_type_id'] = volume_type_id
if metadata:
vol['metadata'] = metadata
for key in kwargs:
vol[key] = kwargs[key]
vol['replication_status'] = replication_status

View File

@ -0,0 +1,4 @@
---
features:
- Added support for get all distinct volumes' metadata from
volume-summary API.