From 4a028e3f3307ef4cfdc4fe0cd6be50f408a84fef Mon Sep 17 00:00:00 2001 From: Zhiteng Huang Date: Fri, 30 Aug 2013 22:28:52 +0800 Subject: [PATCH] Add view builder to QoS specs API extension Add view builder for qos_specs.create(), index(), show() and associations() to in order to make the response more easier to be consumed by client. This patch also: fixed circular reference error when raising HTTP exception. fixed some typo in debug message and removed unused imports. fix bug: # 1219016 Change-Id: I107888e6b4dac8eb5f1b45a87721a7b5efc45632 --- cinder/api/contrib/qos_specs_manage.py | 58 +++++++------ cinder/api/views/qos_specs.py | 64 ++++++++++++++ cinder/db/sqlalchemy/api.py | 62 ++++++++------ .../api/contrib/test_qos_specs_manage.py | 48 ++++++----- cinder/tests/db/test_qos_specs.py | 36 +++++--- cinder/tests/test_qos_specs.py | 83 +++++++++++-------- cinder/volume/qos_specs.py | 9 +- 7 files changed, 237 insertions(+), 123 deletions(-) create mode 100644 cinder/api/views/qos_specs.py diff --git a/cinder/api/contrib/qos_specs_manage.py b/cinder/api/contrib/qos_specs_manage.py index 425b46d66..3a45d39c0 100644 --- a/cinder/api/contrib/qos_specs_manage.py +++ b/cinder/api/contrib/qos_specs_manage.py @@ -21,13 +21,12 @@ import webob from cinder.api import extensions from cinder.api.openstack import wsgi +from cinder.api.views import qos_specs as view_qos_specs from cinder.api import xmlutil -from cinder import db from cinder import exception from cinder.openstack.common import log as logging from cinder.openstack.common.notifier import api as notifier_api from cinder.volume import qos_specs -from cinder.volume import volume_types LOG = logging.getLogger(__name__) @@ -66,6 +65,8 @@ def _check_specs(context, specs_id): class QoSSpecsController(wsgi.Controller): """The volume type extra specs API controller for the OpenStack API.""" + _view_builder_class = view_qos_specs.ViewBuilder + @staticmethod def _notify_qos_specs_error(context, method, payload): notifier_api.notify(context, @@ -80,7 +81,7 @@ class QoSSpecsController(wsgi.Controller): context = req.environ['cinder.context'] authorize(context) specs = qos_specs.get_all_specs(context) - return specs + return self._view_builder.summary_list(req, specs) @wsgi.serializers(xml=QoSSpecsTemplate) def create(self, req, body=None): @@ -97,8 +98,8 @@ class QoSSpecsController(wsgi.Controller): raise webob.exc.HTTPBadRequest(explanation=msg) try: - specs_ref = qos_specs.create(context, name, specs) - qos_specs.get_qos_specs_by_name(context, name) + qos_specs.create(context, name, specs) + spec = qos_specs.get_qos_specs_by_name(context, name) notifier_info = dict(name=name, specs=specs) notifier_api.notify(context, 'QoSSpecs', 'QoSSpecs.create', @@ -122,7 +123,7 @@ class QoSSpecsController(wsgi.Controller): notifier_err) raise webob.exc.HTTPInternalServerError(explanation=str(err)) - return body + return self._view_builder.detail(req, spec) @wsgi.serializers(xml=QoSSpecsTemplate) def update(self, req, id, body=None): @@ -166,10 +167,10 @@ class QoSSpecsController(wsgi.Controller): try: spec = qos_specs.get_qos_specs(context, id) - except exception.NotFound: - raise webob.exc.HTTPNotFound() + except exception.QoSSpecsNotFound as err: + raise webob.exc.HTTPNotFound(explanation=str(err)) - return spec + return self._view_builder.detail(req, spec) def delete(self, req, id): """Deletes an existing qos specs.""" @@ -178,7 +179,8 @@ class QoSSpecsController(wsgi.Controller): force = req.params.get('force', None) - LOG.debug("qos_specs_manage.delete(): id: %s, force: %s" % (id, force)) + LOG.debug("Delete qos_spec: %(id)s, force: %(force)s" % + {'id': id, 'force': force}) try: qos_specs.get_qos_specs(context, id) @@ -187,12 +189,12 @@ class QoSSpecsController(wsgi.Controller): notifier_api.notify(context, 'QoSSpecs', 'qos_specs.delete', notifier_api.INFO, notifier_info) - except exception.NotFound as err: + except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) - raise webob.exc.HTTPNotFound() + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.QoSSpecsInUse as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, @@ -212,7 +214,7 @@ class QoSSpecsController(wsgi.Controller): context = req.environ['cinder.context'] authorize(context) - LOG.debug("assocications(): id: %s" % id) + LOG.debug("Get associations for qos_spec id: %s" % id) try: associates = qos_specs.get_associations(context, id) @@ -225,15 +227,15 @@ class QoSSpecsController(wsgi.Controller): self._notify_qos_specs_error(context, 'qos_specs.associations', notifier_err) - raise webob.exc.HTTPNotFound(explanation=err) + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.CinderException as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.associations', notifier_err) - raise webob.exc.HTTPInternalServerError(explanation=err) + raise webob.exc.HTTPInternalServerError(explanation=str(err)) - return associates + return self._view_builder.associations(req, associates) def associate(self, req, id): """Associate a qos specs with a volume type.""" @@ -249,7 +251,8 @@ class QoSSpecsController(wsgi.Controller): 'qos_specs.delete', notifier_err) raise webob.exc.HTTPBadRequest(explanation=msg) - LOG.debug("associcate(): id: %s, type_id: %s" % (id, type_id)) + LOG.debug("Associate qos_spec: %(id)s with type: %(type_id)s" % + {'id': id, 'type_id': type_id}) try: qos_specs.get_qos_specs(context, id) @@ -263,19 +266,19 @@ class QoSSpecsController(wsgi.Controller): self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) - raise webob.exc.HTTPNotFound(explanation=err) + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) - raise webob.exc.HTTPNotFound(explanation=err) + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.QoSSpecsAssociateFailed as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.associate', notifier_err) - raise webob.exc.HTTPInternalServerError(explanation=err) + raise webob.exc.HTTPInternalServerError(explanation=str(err)) return webob.Response(status_int=202) @@ -293,7 +296,8 @@ class QoSSpecsController(wsgi.Controller): 'qos_specs.delete', notifier_err) raise webob.exc.HTTPBadRequest(explanation=msg) - LOG.debug("disassocicate(): id: %s, type_id: %s" % (id, type_id)) + LOG.debug("Disassociate qos_spec: %(id)s from type: %(type_id)s" % + {'id': id, 'type_id': type_id}) try: qos_specs.get_qos_specs(context, id) @@ -307,19 +311,19 @@ class QoSSpecsController(wsgi.Controller): self._notify_qos_specs_error(context, 'qos_specs.disassociate', notifier_err) - raise webob.exc.HTTPNotFound(explanation=err) + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.disassociate', notifier_err) - raise webob.exc.HTTPNotFound(explanation=err) + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.QoSSpecsDisassociateFailed as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.disassociate', notifier_err) - raise webob.exc.HTTPInternalServerError(explanation=err) + raise webob.exc.HTTPInternalServerError(explanation=str(err)) return webob.Response(status_int=202) @@ -328,7 +332,7 @@ class QoSSpecsController(wsgi.Controller): context = req.environ['cinder.context'] authorize(context) - LOG.debug("disassocicate_all(): id: %s" % id) + LOG.debug("Disassociate qos_spec: %s from all." % id) try: qos_specs.get_qos_specs(context, id) @@ -342,13 +346,13 @@ class QoSSpecsController(wsgi.Controller): self._notify_qos_specs_error(context, 'qos_specs.disassociate_all', notifier_err) - raise webob.exc.HTTPNotFound(explanation=err) + raise webob.exc.HTTPNotFound(explanation=str(err)) except exception.QoSSpecsDisassociateFailed as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_qos_specs_error(context, 'qos_specs.disassociate_all', notifier_err) - raise webob.exc.HTTPInternalServerError(explanation=err) + raise webob.exc.HTTPInternalServerError(explanation=str(err)) return webob.Response(status_int=202) diff --git a/cinder/api/views/qos_specs.py b/cinder/api/views/qos_specs.py new file mode 100644 index 000000000..cd83862dd --- /dev/null +++ b/cinder/api/views/qos_specs.py @@ -0,0 +1,64 @@ +# Copyright (C) 2013 eBay Inc. +# All Rights Reserved. +# +# 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. + +from cinder.api import common +from cinder.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +class ViewBuilder(common.ViewBuilder): + """Model QoS specs API responses as a python dictionary.""" + + _collection_name = "qos_specs" + + def __init__(self): + """Initialize view builder.""" + super(ViewBuilder, self).__init__() + + def summary_list(self, request, qos_specs): + """Show a list of qos_specs without many details.""" + return self._list_view(self.detail, request, qos_specs) + + def summary(self, request, qos_spec): + """Generic, non-detailed view of a qos_specs.""" + return { + 'qos_specs': qos_spec, + 'links': self._get_links(request, + qos_spec['id']), + } + + def detail(self, request, qos_spec): + """Detailed view of a single qos_spec.""" + #TODO(zhiteng) Add associations to detailed view + return { + 'qos_specs': qos_spec, + 'links': self._get_links(request, + qos_spec['id']), + } + + def associations(self, request, associates): + """View of qos specs associations.""" + return { + 'qos_associations': associates + } + + def _list_view(self, func, request, qos_specs): + """Provide a view for a list of qos_specs.""" + specs_list = [func(request, specs)['qos_specs'] for specs in qos_specs] + specs_dict = dict(qos_specs=specs_list) + + return specs_dict diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index d8a40eb5d..79f5579ee 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -2022,7 +2022,7 @@ def qos_specs_create(context, values): # the name of QoS specs root['key'] = 'QoS_Specs_Name' root['value'] = values['name'] - LOG.debug("qos_specs_create(): root %s", root) + LOG.debug("DB qos_specs_create(): root %s", root) specs_root.update(root) specs_root.save(session=session) @@ -2036,7 +2036,7 @@ def qos_specs_create(context, values): except Exception as e: raise db_exc.DBError(e) - return specs_root + return dict(id=specs_root.id, name=specs_root.value) @require_admin_context @@ -2078,21 +2078,25 @@ def _dict_with_children_specs(specs): def _dict_with_qos_specs(rows): - """Convert qos specs query results to dict with name as key. + """Convert qos specs query results to list. Qos specs query results are a list of quality_of_service_specs refs, some are root entry of a qos specs (key == 'QoS_Specs_Name') and the rest are children entry, a.k.a detailed specs for a qos specs. This - funtion converts query results to a dict using spec name as key. + function converts query results to a dict using spec name as key. """ - result = {} + result = [] for row in rows: if row['key'] == 'QoS_Specs_Name': - result[row['value']] = dict(id=row['id']) + member = {} + member['name'] = row['value'] + member.update(dict(id=row['id'])) if row.specs: spec_dict = _dict_with_children_specs(row.specs) - result[row['value']].update(spec_dict) - + member.update(dict(consumer=spec_dict['consumer'])) + del spec_dict['consumer'] + member.update(dict(specs=spec_dict)) + result.append(member) return result @@ -2100,25 +2104,35 @@ def _dict_with_qos_specs(rows): def qos_specs_get(context, qos_specs_id, inactive=False): rows = _qos_specs_get_ref(context, qos_specs_id, None, inactive) - return _dict_with_qos_specs(rows) + return _dict_with_qos_specs(rows)[0] @require_admin_context def qos_specs_get_all(context, inactive=False, filters=None): - """Returns dicts describing all qos_specs. + """Returns a list of all qos_specs. Results is like: - {'qos-spec-1': {'id': SPECS-UUID, - 'key1': 'value1', - 'key2': 'value2', - ... - 'consumer': 'back-end'} - 'qos-spec-2': {'id': SPECS-UUID, - 'key1': 'value1', - 'key2': 'value2', - ... - 'consumer': 'back-end'} - } + [{ + 'id': SPECS-UUID, + 'name': 'qos_spec-1', + 'consumer': 'back-end', + 'specs': { + 'key1': 'value1', + 'key2': 'value2', + ... + } + }, + { + 'id': SPECS-UUID, + 'name': 'qos_spec-2', + 'consumer': 'front-end', + 'specs': { + 'key1': 'value1', + 'key2': 'value2', + ... + } + }, + ] """ filters = filters or {} #TODO(zhiteng) Add filters for 'consumer' @@ -2135,7 +2149,7 @@ def qos_specs_get_all(context, inactive=False, filters=None): def qos_specs_get_by_name(context, name, inactive=False): rows = _qos_specs_get_by_name(context, name, None, inactive) - return _dict_with_qos_specs(rows) + return _dict_with_qos_specs(rows)[0] @require_admin_context @@ -2148,9 +2162,7 @@ def qos_specs_associations_get(context, qos_specs_id): extend qos specs association to other entities, such as volumes, sometime in future. """ - rows = _qos_specs_get_ref(context, qos_specs_id, None) - if not rows: - raise exception.QoSSpecsNotFound(specs_id=qos_specs_id) + _qos_specs_get_ref(context, qos_specs_id, None) return volume_type_qos_associations_get(context, qos_specs_id) diff --git a/cinder/tests/api/contrib/test_qos_specs_manage.py b/cinder/tests/api/contrib/test_qos_specs_manage.py index 0bf8e588d..a283a1682 100644 --- a/cinder/tests/api/contrib/test_qos_specs_manage.py +++ b/cinder/tests/api/contrib/test_qos_specs_manage.py @@ -26,32 +26,37 @@ from cinder.volume import qos_specs def stub_qos_specs(id): + res = dict(name='qos_specs_' + str(id)) + res.update(dict(consumer='back-end')) + res.update(dict(id=str(id))) specs = {"key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4", "key5": "value5"} - specs.update(dict(id=str(id))) - return specs + res.update(dict(specs=specs)) + return res def stub_qos_associates(id): - return {str(id): {'FakeVolTypeName': 'FakeVolTypeID'}} + return [{ + 'association_type': 'volume_type', + 'name': 'FakeVolTypeName', + 'id': 'FakeVolTypeID'}] def return_qos_specs_get_all(context): - return dict( - qos_specs_1=stub_qos_specs(1), - qos_specs_2=stub_qos_specs(2), - qos_specs_3=stub_qos_specs(3) - ) + return [ + stub_qos_specs(1), + stub_qos_specs(2), + stub_qos_specs(3), + ] def return_qos_specs_get_qos_specs(context, id): if id == "777": raise exception.QoSSpecsNotFound(specs_id=id) - name = 'qos_specs_%s' % id - return {name: stub_qos_specs(int(id))} + return stub_qos_specs(int(id)) def return_qos_specs_delete(context, id, force): @@ -142,14 +147,16 @@ class QoSSpecManageApiTest(test.TestCase): return_qos_specs_get_all) req = fakes.HTTPRequest.blank('/v2/fake/qos-specs') - res_dict = self.controller.index(req) + res = self.controller.index(req) - self.assertEqual(3, len(res_dict.keys())) + self.assertEqual(3, len(res['qos_specs'])) + names = set() + for item in res['qos_specs']: + self.assertEqual('value1', item['specs']['key1']) + names.add(item['name']) expected_names = ['qos_specs_1', 'qos_specs_2', 'qos_specs_3'] - self.assertEqual(set(res_dict.keys()), set(expected_names)) - for key in res_dict.keys(): - self.assertEqual('value1', res_dict[key]['key1']) + self.assertEqual(names, set(expected_names)) def test_qos_specs_delete(self): self.stubs.Set(qos_specs, 'get_qos_specs', @@ -212,7 +219,6 @@ class QoSSpecManageApiTest(test.TestCase): res_dict = self.controller.create(req, body) self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) - self.assertEqual(1, len(res_dict)) self.assertEqual('qos_specs_1', res_dict['qos_specs']['name']) def test_create_conflict(self): @@ -318,8 +324,8 @@ class QoSSpecManageApiTest(test.TestCase): req = fakes.HTTPRequest.blank('/v2/fake/qos-specs/1') res_dict = self.controller.show(req, '1') - self.assertEqual(1, len(res_dict)) - self.assertEqual('1', res_dict['qos_specs_1']['id']) + self.assertEqual('1', res_dict['qos_specs']['id']) + self.assertEqual('qos_specs_1', res_dict['qos_specs']['name']) def test_get_associations(self): self.stubs.Set(qos_specs, 'get_associations', @@ -329,10 +335,10 @@ class QoSSpecManageApiTest(test.TestCase): '/v2/fake/qos-specs/1/associations') res = self.controller.associations(req, '1') - self.assertEqual('1', res.keys()[0]) - self.assertEqual('FakeVolTypeName', res['1'].keys()[0]) + self.assertEqual('FakeVolTypeName', + res['qos_associations'][0]['name']) self.assertEqual('FakeVolTypeID', - res['1']['FakeVolTypeName']) + res['qos_associations'][0]['id']) def test_get_associations_not_found(self): self.stubs.Set(qos_specs, 'get_associations', diff --git a/cinder/tests/db/test_qos_specs.py b/cinder/tests/db/test_qos_specs.py index 7d8f1d43f..3b1be4f07 100644 --- a/cinder/tests/db/test_qos_specs.py +++ b/cinder/tests/db/test_qos_specs.py @@ -68,7 +68,7 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase): specs_id = self._create_qos_specs('NewName') query_id = db.qos_specs_get_by_name( - self.ctxt, 'NewName')['NewName']['id'] + self.ctxt, 'NewName')['id'] self.assertEquals(specs_id, query_id) def test_qos_specs_get(self): @@ -81,8 +81,9 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase): db.qos_specs_get, self.ctxt, fake_id) specs = db.qos_specs_get(self.ctxt, specs_id) - value.update(dict(id=specs_id)) - expected = dict(Name1=value) + expected = dict(name='Name1', id=specs_id, consumer='front-end') + del value['consumer'] + expected.update(dict(specs=value)) self.assertDictMatch(specs, expected) def test_qos_specs_get_all(self): @@ -101,12 +102,18 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase): self.assertEquals(len(specs), 3, "Unexpected number of qos specs records") - value1.update({'id': spec_id1}) - value2.update({'id': spec_id2}) - value3.update({'id': spec_id3}) - self.assertDictMatch(specs['Name1'], value1) - self.assertDictMatch(specs['Name2'], value2) - self.assertDictMatch(specs['Name3'], value3) + expected1 = dict(name='Name1', id=spec_id1, consumer='front-end') + expected2 = dict(name='Name2', id=spec_id2, consumer='back-end') + expected3 = dict(name='Name3', id=spec_id3, consumer='back-end') + del value1['consumer'] + del value2['consumer'] + del value3['consumer'] + expected1.update(dict(specs=value1)) + expected2.update(dict(specs=value2)) + expected3.update(dict(specs=value3)) + self.assertIn(expected1, specs) + self.assertIn(expected2, specs) + self.assertIn(expected3, specs) def test_qos_specs_get_by_name(self): name = str(int(time.time())) @@ -114,8 +121,11 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase): foo='Foo', bar='Bar') specs_id = self._create_qos_specs(name, value) specs = db.qos_specs_get_by_name(self.ctxt, name) - value.update(dict(id=specs_id)) - expected = {name: value} + del value['consumer'] + expected = {'name': name, + 'id': specs_id, + 'consumer': 'front-end', + 'specs': value} self.assertDictMatch(specs, expected) def test_qos_specs_delete(self): @@ -200,5 +210,5 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase): self.ctxt, 'Fake-UUID', value) db.qos_specs_update(self.ctxt, specs_id, value) specs = db.qos_specs_get(self.ctxt, specs_id) - self.assertEqual(specs[name]['key2'], 'new_value2') - self.assertEqual(specs[name]['key3'], 'value3') + self.assertEqual(specs['specs']['key2'], 'new_value2') + self.assertEqual(specs['specs']['key3'], 'value3') diff --git a/cinder/tests/test_qos_specs.py b/cinder/tests/test_qos_specs.py index 067fa3193..dbd4b9c28 100644 --- a/cinder/tests/test_qos_specs.py +++ b/cinder/tests/test_qos_specs.py @@ -66,17 +66,16 @@ class QoSSpecsTestCase(test.TestCase): 'key3': 'value3'} ref = qos_specs.create(self.ctxt, 'FakeName', input) specs = qos_specs.get_qos_specs(self.ctxt, ref['id']) - input.update(dict(consumer='back-end')) - input.update(dict(id=ref['id'])) - expected = {'FakeName': input} + expected = (dict(consumer='back-end')) + expected.update(dict(id=ref['id'])) + expected.update(dict(name='FakeName')) + del input['consumer'] + expected.update(dict(specs=input)) self.assertDictMatch(specs, expected) self.stubs.Set(db, 'qos_specs_create', fake_db_qos_specs_create) - # Restore input back to original state - del input['id'] - del input['consumer'] # qos specs must have unique name self.assertRaises(exception.QoSSpecsExists, qos_specs.create, self.ctxt, 'DupQoSName', input) @@ -101,7 +100,7 @@ class QoSSpecsTestCase(test.TestCase): self.assertRaises(exception.InvalidQoSSpecs, qos_specs.update, self.ctxt, 'fake_id', input) - del input['consumer'] + input['consumer'] = 'front-end' # qos specs must exists self.assertRaises(exception.QoSSpecsNotFound, qos_specs.update, self.ctxt, 'fake_id', input) @@ -111,8 +110,8 @@ class QoSSpecsTestCase(test.TestCase): {'key1': 'newvalue1', 'key2': 'value2'}) specs = qos_specs.get_qos_specs(self.ctxt, specs_id) - self.assertEqual(specs['Name']['key1'], 'newvalue1') - self.assertEqual(specs['Name']['key2'], 'value2') + self.assertEqual(specs['specs']['key1'], 'newvalue1') + self.assertEqual(specs['specs']['key2'], 'value2') self.stubs.Set(db, 'qos_specs_update', fake_db_update) self.assertRaises(exception.QoSSpecsUpdateFailed, @@ -155,10 +154,15 @@ class QoSSpecsTestCase(test.TestCase): self.stubs.Set(db, 'qos_specs_associations_get', fake_db_associate_get) - expected = {'specs-id': {'type-1': 'id-1', - 'type-2': 'id-2'}} + expected1 = {'association_type': 'volume_type', + 'name': 'type-1', + 'id': 'id-1'} + expected2 = {'association_type': 'volume_type', + 'name': 'type-2', + 'id': 'id-2'} res = qos_specs.get_associations(self.ctxt, 'specs-id') - self.assertDictMatch(res, expected) + self.assertIn(expected1, res) + self.assertIn(expected2, res) self.assertRaises(exception.CinderException, qos_specs.get_associations, self.ctxt, @@ -178,9 +182,9 @@ class QoSSpecsTestCase(test.TestCase): qos_specs.associate_qos_with_type(self.ctxt, specs_id, type_ref['id']) res = qos_specs.get_associations(self.ctxt, specs_id) - self.assertEquals(len(res[specs_id].keys()), 1) - self.assertIn('TypeName', res[specs_id].keys()) - self.assertIn(type_ref['id'], res[specs_id].values()) + self.assertEquals(len(res), 1) + self.assertEquals('TypeName', res[0]['name']) + self.assertEquals(type_ref['id'], res[0]['id']) self.stubs.Set(db, 'qos_specs_associate', fake_db_associate) @@ -205,11 +209,11 @@ class QoSSpecsTestCase(test.TestCase): qos_specs.associate_qos_with_type(self.ctxt, specs_id, type_ref['id']) res = qos_specs.get_associations(self.ctxt, specs_id) - self.assertEquals(len(res[specs_id].keys()), 1) + self.assertEquals(len(res), 1) qos_specs.disassociate_qos_specs(self.ctxt, specs_id, type_ref['id']) res = qos_specs.get_associations(self.ctxt, specs_id) - self.assertEquals(len(res[specs_id].keys()), 0) + self.assertEquals(len(res), 0) self.stubs.Set(db, 'qos_specs_disassociate', fake_db_disassociate) @@ -235,11 +239,11 @@ class QoSSpecsTestCase(test.TestCase): qos_specs.associate_qos_with_type(self.ctxt, specs_id, type2_ref['id']) res = qos_specs.get_associations(self.ctxt, specs_id) - self.assertEquals(len(res[specs_id].keys()), 2) + self.assertEquals(len(res), 2) qos_specs.disassociate_all(self.ctxt, specs_id) res = qos_specs.get_associations(self.ctxt, specs_id) - self.assertEquals(len(res[specs_id].keys()), 0) + self.assertEquals(len(res), 0) self.stubs.Set(db, 'qos_specs_disassociate_all', fake_db_disassociate_all) @@ -250,31 +254,41 @@ class QoSSpecsTestCase(test.TestCase): def test_get_all_specs(self): input = {'key1': 'value1', 'key2': 'value2', - 'key3': 'value3'} + 'key3': 'value3', + 'consumer': 'both'} specs_id1 = self._create_qos_specs('Specs1', input) input.update({'key4': 'value4'}) specs_id2 = self._create_qos_specs('Specs2', input) - expected = {'Specs1': {'key1': 'value1', - 'id': specs_id1, - 'key2': 'value2', - 'key3': 'value3'}, - 'Specs2': {'key1': 'value1', - 'id': specs_id2, - 'key2': 'value2', - 'key3': 'value3', - 'key4': 'value4'}} + expected1 = { + 'id': specs_id1, + 'name': 'Specs1', + 'consumer': 'both', + 'specs': {'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3'}} + expected2 = { + 'id': specs_id2, + 'name': 'Specs2', + 'consumer': 'both', + 'specs': {'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3', + 'key4': 'value4'}} res = qos_specs.get_all_specs(self.ctxt) - self.assertDictMatch(expected, res) + self.assertEqual(len(res), 2) + self.assertIn(expected1, res) + self.assertIn(expected2, res) def test_get_qos_specs(self): one_time_value = str(int(time.time())) input = {'key1': one_time_value, 'key2': 'value2', - 'key3': 'value3'} + 'key3': 'value3', + 'consumer': 'both'} id = self._create_qos_specs('Specs1', input) specs = qos_specs.get_qos_specs(self.ctxt, id) - self.assertEquals(specs['Specs1']['key1'], one_time_value) + self.assertEquals(specs['specs']['key1'], one_time_value) self.assertRaises(exception.InvalidQoSSpecs, qos_specs.get_qos_specs, self.ctxt, None) @@ -283,11 +297,12 @@ class QoSSpecsTestCase(test.TestCase): one_time_value = str(int(time.time())) input = {'key1': one_time_value, 'key2': 'value2', - 'key3': 'value3'} + 'key3': 'value3', + 'consumer': 'back-end'} id = self._create_qos_specs(one_time_value, input) specs = qos_specs.get_qos_specs_by_name(self.ctxt, one_time_value) - self.assertEquals(specs[one_time_value]['key1'], one_time_value) + self.assertEquals(specs['specs']['key1'], one_time_value) self.assertRaises(exception.InvalidQoSSpecs, qos_specs.get_qos_specs_by_name, self.ctxt, None) diff --git a/cinder/volume/qos_specs.py b/cinder/volume/qos_specs.py index 2b933cc40..436c48205 100644 --- a/cinder/volume/qos_specs.py +++ b/cinder/volume/qos_specs.py @@ -147,11 +147,14 @@ def get_associations(context, specs_id): LOG.warn(msg) raise exception.CinderException(message=msg) - result = {} + result = [] for vol_type in associates: - result[vol_type['name']] = vol_type['id'] + member = dict(association_type='volume_type') + member.update(dict(name=vol_type['name'])) + member.update(dict(id=vol_type['id'])) + result.append(member) - return {specs_id: result} + return result def associate_qos_with_type(context, specs_id, type_id): -- 2.45.2