Merge "Allow setting CG name or description to empty value"

This commit is contained in:
Jenkins 2016-06-20 13:39:14 +00:00 committed by Gerrit Code Review
commit b0252b3420
8 changed files with 277 additions and 43 deletions

View File

@ -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)

View File

@ -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"

View File

@ -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.

View File

@ -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())

View File

@ -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'})

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,3 @@
---
features:
- Allow API user to remove the consistency group name or description information.