From e22c24410631824e417bb35da370f10b08025e2c Mon Sep 17 00:00:00 2001 From: haobing1 Date: Thu, 2 Jun 2016 20:55:14 +0800 Subject: [PATCH] Allow setting CG name or description to empty value This change allow API user to remove the consistency group name or description information. If user use the `cinder consisgroup-update consisgroup-id --name ''` or `cinder consisgroup-update consisgroup-id --description ''` to update the consisgroup's name or description information, the consisgroup's name or description information will be removed. APIImpact Change-Id: I0d661775994d6de580f8788397f0e91e90148edd Closes-Bug: #1572986 --- cinder/api/contrib/consistencygroups.py | 61 +++++----- cinder/api/openstack/api_version_request.py | 4 +- .../openstack/rest_api_version_history.rst | 5 + cinder/api/v3/consistencygroups.py | 89 +++++++++++++++ cinder/api/v3/router.py | 7 ++ cinder/consistencygroup/api.py | 44 ++++--- .../unit/api/v3/test_consistencygroups.py | 107 ++++++++++++++++++ ...tion-for-consisgroup-408257a0a18bd530.yaml | 3 + 8 files changed, 277 insertions(+), 43 deletions(-) create mode 100644 cinder/api/v3/consistencygroups.py create mode 100644 cinder/tests/unit/api/v3/test_consistencygroups.py create mode 100644 releasenotes/notes/allow-remove-name-and-description-for-consisgroup-408257a0a18bd530.yaml diff --git a/cinder/api/contrib/consistencygroups.py b/cinder/api/contrib/consistencygroups.py index 7c462784c47..a9abc3aef86 100644 --- a/cinder/api/contrib/consistencygroups.py +++ b/cinder/api/contrib/consistencygroups.py @@ -205,6 +205,36 @@ class ConsistencyGroupsController(wsgi.Controller): retval = self._view_builder.summary(req, new_consistencygroup) return retval + def _check_update_parameters(self, name, description, add_volumes, + remove_volumes): + if not (name or description or add_volumes or remove_volumes): + msg = _("Name, description, add_volumes, and remove_volumes " + "can not be all empty in the request body.") + raise exc.HTTPBadRequest(explanation=msg) + + def _update(self, context, id, name, description, add_volumes, + remove_volumes, + allow_empty=False): + LOG.info(_LI("Updating consistency group %(id)s with name %(name)s " + "description: %(description)s add_volumes: " + "%(add_volumes)s remove_volumes: %(remove_volumes)s."), + {'id': id, + 'name': name, + 'description': description, + 'add_volumes': add_volumes, + 'remove_volumes': remove_volumes}, + context=context) + + try: + group = self.consistencygroup_api.get(context, id) + self.consistencygroup_api.update( + context, group, name, description, + add_volumes, remove_volumes, allow_empty) + except exception.ConsistencyGroupNotFound as error: + raise exc.HTTPNotFound(explanation=error.msg) + except exception.InvalidConsistencyGroup as error: + raise exc.HTTPBadRequest(explanation=error.msg) + def update(self, req, id, body): """Update the consistency group. @@ -224,14 +254,12 @@ class ConsistencyGroupsController(wsgi.Controller): """ LOG.debug('Update called for consistency group %s.', id) - if not body: msg = _("Missing request body.") raise exc.HTTPBadRequest(explanation=msg) self.assert_valid_body(body, 'consistencygroup') context = req.environ['cinder.context'] - consistencygroup = body.get('consistencygroup', None) self.validate_name_and_description(consistencygroup) name = consistencygroup.get('name', None) @@ -239,31 +267,10 @@ class ConsistencyGroupsController(wsgi.Controller): add_volumes = consistencygroup.get('add_volumes', None) remove_volumes = consistencygroup.get('remove_volumes', None) - if (not name and not description and not add_volumes - and not remove_volumes): - msg = _("Name, description, add_volumes, and remove_volumes " - "can not be all empty in the request body.") - raise exc.HTTPBadRequest(explanation=msg) - - LOG.info(_LI("Updating consistency group %(id)s with name %(name)s " - "description: %(description)s add_volumes: " - "%(add_volumes)s remove_volumes: %(remove_volumes)s."), - {'id': id, 'name': name, - 'description': description, - 'add_volumes': add_volumes, - 'remove_volumes': remove_volumes}, - context=context) - - try: - group = self.consistencygroup_api.get(context, id) - self.consistencygroup_api.update( - context, group, name, description, - add_volumes, remove_volumes) - except exception.ConsistencyGroupNotFound as error: - raise exc.HTTPNotFound(explanation=error.msg) - except exception.InvalidConsistencyGroup as error: - raise exc.HTTPBadRequest(explanation=error.msg) - + self._check_update_parameters(name, description, add_volumes, + remove_volumes) + self._update(context, id, name, description, add_volumes, + remove_volumes) return webob.Response(status_int=202) diff --git a/cinder/api/openstack/api_version_request.py b/cinder/api/openstack/api_version_request.py index 23239ae6ae9..157c5f0da9f 100644 --- a/cinder/api/openstack/api_version_request.py +++ b/cinder/api/openstack/api_version_request.py @@ -52,6 +52,8 @@ REST_API_VERSION_HISTORY = """ * 3.3 - Add user messages APIs. * 3.4 - Adds glance_metadata filter to list/detail volumes in _get_volumes. * 3.5 - Add pagination support to messages API. + * 3.6 - Allows to set empty description and empty name for consistency + group in consisgroup-update operation. """ @@ -60,7 +62,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.5" +_MAX_API_VERSION = "3.6" _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 78f2f189340..7efbfd96b52 100644 --- a/cinder/api/openstack/rest_api_version_history.rst +++ b/cinder/api/openstack/rest_api_version_history.rst @@ -64,3 +64,8 @@ user documentation. 3.5 --- Added pagination support to /messages API + +3.6 +--- + Allowed to set empty description and empty name for consistency + group in consisgroup-update operation. diff --git a/cinder/api/v3/consistencygroups.py b/cinder/api/v3/consistencygroups.py new file mode 100644 index 00000000000..b74d290aab1 --- /dev/null +++ b/cinder/api/v3/consistencygroups.py @@ -0,0 +1,89 @@ +# +# 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. + +"""The consistencygroups V3 api.""" + +from oslo_log import log as logging +import webob +from webob import exc + +from cinder.api.contrib import consistencygroups as cg_v2 +from cinder.api.openstack import wsgi +from cinder.i18n import _ + +LOG = logging.getLogger(__name__) + + +class ConsistencyGroupsController(cg_v2.ConsistencyGroupsController): + """The ConsistencyGroups API controller for the OpenStack API V3.""" + + def _check_update_parameters_v3(self, req, name, description, add_volumes, + remove_volumes): + allow_empty = req.api_version_request.matches('3.6', None) + if allow_empty: + if (name is None and description is None + and not add_volumes and not remove_volumes): + msg = _("Must specify one or more of the following keys to " + "update: name, description, " + "add_volumes, remove_volumes.") + raise exc.HTTPBadRequest(explanation=msg) + else: + if not (name or description or add_volumes or remove_volumes): + msg = _("Name, description, add_volumes, and remove_volumes " + "can not be all empty in the request body.") + raise exc.HTTPBadRequest(explanation=msg) + return allow_empty + + def update(self, req, id, body): + """Update the consistency group. + + Expected format of the input parameter 'body': + + .. code-block:: json + + { + "consistencygroup": + { + "name": "my_cg", + "description": "My consistency group", + "add_volumes": "volume-uuid-1,volume-uuid-2,...", + "remove_volumes": "volume-uuid-8,volume-uuid-9,..." + } + } + + """ + LOG.debug('Update called for consistency group %s.', id) + if not body: + msg = _("Missing request body.") + raise exc.HTTPBadRequest(explanation=msg) + + self.assert_valid_body(body, 'consistencygroup') + context = req.environ['cinder.context'] + consistencygroup = body.get('consistencygroup', None) + self.validate_name_and_description(consistencygroup) + name = consistencygroup.get('name', None) + description = consistencygroup.get('description', None) + add_volumes = consistencygroup.get('add_volumes', None) + remove_volumes = consistencygroup.get('remove_volumes', None) + + allow_empty = self._check_update_parameters_v3(req, name, + description, + add_volumes, + remove_volumes) + self._update(context, id, name, description, add_volumes, + remove_volumes, allow_empty) + return webob.Response(status_int=202) + + +def create_resource(): + return wsgi.Resource(ConsistencyGroupsController()) diff --git a/cinder/api/v3/router.py b/cinder/api/v3/router.py index ebecc09a4a9..d9e379a01bc 100644 --- a/cinder/api/v3/router.py +++ b/cinder/api/v3/router.py @@ -26,6 +26,7 @@ from cinder.api.v2 import snapshot_metadata from cinder.api.v2 import snapshots from cinder.api.v2 import types from cinder.api.v2 import volume_metadata +from cinder.api.v3 import consistencygroups from cinder.api.v3 import messages from cinder.api.v3 import volumes from cinder.api import versions @@ -98,3 +99,9 @@ class APIRouter(cinder.api.openstack.APIRouter): controller=volume_metadata_controller, action='update_all', conditions={"method": ['PUT']}) + + self.resources['consistencygroups'] = ( + consistencygroups.create_resource()) + mapper.resource("consistencygroup", "consistencygroups", + controller=self.resources['consistencygroups'], + member={'update': 'PUT'}) diff --git a/cinder/consistencygroup/api.py b/cinder/consistencygroup/api.py index 5796076276b..e51552acf4f 100644 --- a/cinder/consistencygroup/api.py +++ b/cinder/consistencygroup/api.py @@ -453,16 +453,25 @@ class API(base.Base): self.volume_rpcapi.delete_consistencygroup(context, group) def _check_update(self, group, name, description, add_volumes, - remove_volumes): - if not (name or description or add_volumes or remove_volumes): - msg = (_("Cannot update consistency group %(group_id)s " - "because no valid name, description, add_volumes, " - "or remove_volumes were provided.") % - {'group_id': group.id}) - raise exception.InvalidConsistencyGroup(reason=msg) + remove_volumes, allow_empty=False): + if allow_empty: + if (name is None and description is None + and not add_volumes and not remove_volumes): + msg = (_("Cannot update consistency group %(group_id)s " + "because no valid name, description, add_volumes, " + "or remove_volumes were provided.") % + {'group_id': group.id}) + raise exception.InvalidConsistencyGroup(reason=msg) + else: + if not (name or description or add_volumes or remove_volumes): + msg = (_("Cannot update consistency group %(group_id)s " + "because no valid name, description, add_volumes, " + "or remove_volumes were provided.") % + {'group_id': group.id}) + raise exception.InvalidConsistencyGroup(reason=msg) def update(self, context, group, name, description, - add_volumes, remove_volumes): + add_volumes, remove_volumes, allow_empty=False): """Update consistency group.""" add_volumes_list = [] remove_volumes_list = [] @@ -489,18 +498,23 @@ class API(base.Base): # Validate description. if description == group.description: description = None - self._check_update(group, name, description, add_volumes, - remove_volumes) + remove_volumes, allow_empty) fields = {'updated_at': timeutils.utcnow()} # Update name and description in db now. No need to # to send them over through an RPC call. - if name: - fields['name'] = name - if description: - fields['description'] = description + if allow_empty: + if name is not None: + fields['name'] = name + if description is not None: + fields['description'] = description + else: + if name: + fields['name'] = name + if description: + fields['description'] = description # NOTE(geguileo): We will use the updating status in the CG as a lock # mechanism to prevent volume add/remove races with other API, while we @@ -531,7 +545,7 @@ class API(base.Base): group.volumes, remove_volumes_list, group) self._check_update(group, name, description, add_volumes_new, - remove_volumes_new) + remove_volumes_new, allow_empty) except Exception: # If we have an error on the volume_lists we must return status to # available as we were doing before removing API races diff --git a/cinder/tests/unit/api/v3/test_consistencygroups.py b/cinder/tests/unit/api/v3/test_consistencygroups.py new file mode 100644 index 00000000000..93f6b61cdb0 --- /dev/null +++ b/cinder/tests/unit/api/v3/test_consistencygroups.py @@ -0,0 +1,107 @@ +# +# 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. + +import ddt +import webob + +from cinder.api.openstack import api_version_request as api_version +from cinder.api.v3 import consistencygroups +import cinder.consistencygroup +from cinder import context +from cinder import objects +from cinder.objects import fields +from cinder import test +from cinder.tests.unit.api import fakes +from cinder.tests.unit import fake_constants as fake + + +@ddt.ddt +class ConsistencyGroupsAPITestCase(test.TestCase): + """Test Case for consistency groups API.""" + + def setUp(self): + super(ConsistencyGroupsAPITestCase, self).setUp() + self.cg_api = cinder.consistencygroup.API() + self.ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, + auth_token=True, + is_admin=True) + self.user_ctxt = context.RequestContext( + fake.USER_ID, fake.PROJECT_ID, auth_token=True) + self.controller = consistencygroups.ConsistencyGroupsController() + + def _create_consistencygroup( + self, + ctxt=None, + name='test_consistencygroup', + description='this is a test consistency group', + volume_type_id=fake.VOLUME_TYPE_ID, + availability_zone='az1', + host='fakehost', + status=fields.ConsistencyGroupStatus.CREATING, + **kwargs): + """Create a consistency group object.""" + ctxt = ctxt or self.ctxt + consistencygroup = objects.ConsistencyGroup(ctxt) + consistencygroup.user_id = fake.USER_ID + consistencygroup.project_id = fake.PROJECT_ID + consistencygroup.availability_zone = availability_zone + consistencygroup.name = name + consistencygroup.description = description + consistencygroup.volume_type_id = volume_type_id + consistencygroup.host = host + consistencygroup.status = status + consistencygroup.update(kwargs) + consistencygroup.create() + return consistencygroup + + def test_update_consistencygroup_empty_parameters(self): + consistencygroup = self._create_consistencygroup( + ctxt=self.ctxt, + status=fields.ConsistencyGroupStatus.AVAILABLE) + req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % + (fake.PROJECT_ID, consistencygroup.id)) + req.environ['cinder.context'].is_admin = True + req.headers['Content-Type'] = 'application/json' + req.headers['OpenStack-API-Version'] = 'volume 3.6' + req.api_version_request = api_version.APIVersionRequest('3.6') + body = {"consistencygroup": {"name": "", + "description": "", + "add_volumes": None, + "remove_volumes": None, }} + res_dict = self.controller.update(req, + consistencygroup.id, + body) + consistencygroup = objects.ConsistencyGroup.get_by_id( + self.ctxt, consistencygroup.id) + self.assertEqual(202, res_dict.status_int) + self.assertEqual("", consistencygroup.name) + self.assertEqual("", consistencygroup.description) + consistencygroup.destroy() + + def test_update_consistencygroup_empty_parameters_unsupport_version(self): + consistencygroup = self._create_consistencygroup( + ctxt=self.ctxt, + status=fields.ConsistencyGroupStatus.AVAILABLE) + req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % + (fake.PROJECT_ID, consistencygroup.id)) + req.environ['cinder.context'].is_admin = True + req.headers['Content-Type'] = 'application/json' + req.headers['OpenStack-API-Version'] = 'volume 3.5' + req.api_version_request = api_version.APIVersionRequest('3.5') + body = {"consistencygroup": {"name": "", + "description": "", + "add_volumes": None, + "remove_volumes": None, }} + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + req, consistencygroup.id, body) + consistencygroup.destroy() diff --git a/releasenotes/notes/allow-remove-name-and-description-for-consisgroup-408257a0a18bd530.yaml b/releasenotes/notes/allow-remove-name-and-description-for-consisgroup-408257a0a18bd530.yaml new file mode 100644 index 00000000000..bf22398e184 --- /dev/null +++ b/releasenotes/notes/allow-remove-name-and-description-for-consisgroup-408257a0a18bd530.yaml @@ -0,0 +1,3 @@ +--- +features: + - Allow API user to remove the consistency group name or description information.