Merge "Allow setting CG name or description to empty value"
This commit is contained in:
commit
b0252b3420
@ -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)
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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.
|
||||
|
89
cinder/api/v3/consistencygroups.py
Normal file
89
cinder/api/v3/consistencygroups.py
Normal 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())
|
@ -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'})
|
||||
|
@ -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
|
||||
|
107
cinder/tests/unit/api/v3/test_consistencygroups.py
Normal file
107
cinder/tests/unit/api/v3/test_consistencygroups.py
Normal 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()
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Allow API user to remove the consistency group name or description information.
|
Loading…
x
Reference in New Issue
Block a user