diff --git a/cinder/api/openstack/api_version_request.py b/cinder/api/openstack/api_version_request.py index f70c0bc081b..7bc492d971c 100644 --- a/cinder/api/openstack/api_version_request.py +++ b/cinder/api/openstack/api_version_request.py @@ -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" diff --git a/cinder/api/openstack/rest_api_version_history.rst b/cinder/api/openstack/rest_api_version_history.rst index a541c876b87..3de8d56b4de 100644 --- a/cinder/api/openstack/rest_api_version_history.rst +++ b/cinder/api/openstack/rest_api_version_history.rst @@ -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. diff --git a/cinder/api/v3/views/volumes.py b/cinder/api/v3/views/volumes.py index ba126a76d8f..eb9b4058702 100644 --- a/cinder/api/v3/views/volumes.py +++ b/cinder/api/v3/views/volumes.py @@ -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.""" diff --git a/cinder/api/v3/volumes.py b/cinder/api/v3/volumes.py index c69328bf700..aa483b03cb2 100644 --- a/cinder/api/v3/volumes.py +++ b/cinder/api/v3/volumes.py @@ -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): diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index dda3084536c..c0d3ee1eaf8 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -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 diff --git a/cinder/tests/unit/api/v3/test_volumes.py b/cinder/tests/unit/api/v3/test_volumes.py index 673d32af5b7..0d0062c7a68 100644 --- a/cinder/tests/unit/api/v3/test_volumes.py +++ b/cinder/tests/unit/api/v3/test_volumes.py @@ -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, diff --git a/cinder/tests/unit/utils.py b/cinder/tests/unit/utils.py index d060e202341..d2ae3f88912 100644 --- a/cinder/tests/unit/utils.py +++ b/cinder/tests/unit/utils.py @@ -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 diff --git a/releasenotes/notes/metadata-for-volume-summary-729ba648db4e4e54.yaml b/releasenotes/notes/metadata-for-volume-summary-729ba648db4e4e54.yaml new file mode 100644 index 00000000000..2c4f7b2f352 --- /dev/null +++ b/releasenotes/notes/metadata-for-volume-summary-729ba648db4e4e54.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added support for get all distinct volumes' metadata from + volume-summary API. \ No newline at end of file