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
This commit is contained in:
@@ -205,6 +205,36 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
retval = self._view_builder.summary(req, new_consistencygroup)
|
retval = self._view_builder.summary(req, new_consistencygroup)
|
||||||
return retval
|
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):
|
def update(self, req, id, body):
|
||||||
"""Update the consistency group.
|
"""Update the consistency group.
|
||||||
|
|
||||||
@@ -224,14 +254,12 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
LOG.debug('Update called for consistency group %s.', id)
|
LOG.debug('Update called for consistency group %s.', id)
|
||||||
|
|
||||||
if not body:
|
if not body:
|
||||||
msg = _("Missing request body.")
|
msg = _("Missing request body.")
|
||||||
raise exc.HTTPBadRequest(explanation=msg)
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
self.assert_valid_body(body, 'consistencygroup')
|
self.assert_valid_body(body, 'consistencygroup')
|
||||||
context = req.environ['cinder.context']
|
context = req.environ['cinder.context']
|
||||||
|
|
||||||
consistencygroup = body.get('consistencygroup', None)
|
consistencygroup = body.get('consistencygroup', None)
|
||||||
self.validate_name_and_description(consistencygroup)
|
self.validate_name_and_description(consistencygroup)
|
||||||
name = consistencygroup.get('name', None)
|
name = consistencygroup.get('name', None)
|
||||||
@@ -239,31 +267,10 @@ class ConsistencyGroupsController(wsgi.Controller):
|
|||||||
add_volumes = consistencygroup.get('add_volumes', None)
|
add_volumes = consistencygroup.get('add_volumes', None)
|
||||||
remove_volumes = consistencygroup.get('remove_volumes', None)
|
remove_volumes = consistencygroup.get('remove_volumes', None)
|
||||||
|
|
||||||
if (not name and not description and not add_volumes
|
self._check_update_parameters(name, description, add_volumes,
|
||||||
and not remove_volumes):
|
remove_volumes)
|
||||||
msg = _("Name, description, add_volumes, and remove_volumes "
|
self._update(context, id, name, description, add_volumes,
|
||||||
"can not be all empty in the request body.")
|
remove_volumes)
|
||||||
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)
|
|
||||||
|
|
||||||
return webob.Response(status_int=202)
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ REST_API_VERSION_HISTORY = """
|
|||||||
* 3.3 - Add user messages APIs.
|
* 3.3 - Add user messages APIs.
|
||||||
* 3.4 - Adds glance_metadata filter to list/detail volumes in _get_volumes.
|
* 3.4 - Adds glance_metadata filter to list/detail volumes in _get_volumes.
|
||||||
* 3.5 - Add pagination support to messages API.
|
* 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.
|
# minimum version of the API supported.
|
||||||
# Explicitly using /v1 or /v2 enpoints will still work
|
# Explicitly using /v1 or /v2 enpoints will still work
|
||||||
_MIN_API_VERSION = "3.0"
|
_MIN_API_VERSION = "3.0"
|
||||||
_MAX_API_VERSION = "3.5"
|
_MAX_API_VERSION = "3.6"
|
||||||
_LEGACY_API_VERSION1 = "1.0"
|
_LEGACY_API_VERSION1 = "1.0"
|
||||||
_LEGACY_API_VERSION2 = "2.0"
|
_LEGACY_API_VERSION2 = "2.0"
|
||||||
|
|
||||||
|
|||||||
@@ -64,3 +64,8 @@ user documentation.
|
|||||||
3.5
|
3.5
|
||||||
---
|
---
|
||||||
Added pagination support to /messages API
|
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 snapshots
|
||||||
from cinder.api.v2 import types
|
from cinder.api.v2 import types
|
||||||
from cinder.api.v2 import volume_metadata
|
from cinder.api.v2 import volume_metadata
|
||||||
|
from cinder.api.v3 import consistencygroups
|
||||||
from cinder.api.v3 import messages
|
from cinder.api.v3 import messages
|
||||||
from cinder.api.v3 import volumes
|
from cinder.api.v3 import volumes
|
||||||
from cinder.api import versions
|
from cinder.api import versions
|
||||||
@@ -98,3 +99,9 @@ class APIRouter(cinder.api.openstack.APIRouter):
|
|||||||
controller=volume_metadata_controller,
|
controller=volume_metadata_controller,
|
||||||
action='update_all',
|
action='update_all',
|
||||||
conditions={"method": ['PUT']})
|
conditions={"method": ['PUT']})
|
||||||
|
|
||||||
|
self.resources['consistencygroups'] = (
|
||||||
|
consistencygroups.create_resource())
|
||||||
|
mapper.resource("consistencygroup", "consistencygroups",
|
||||||
|
controller=self.resources['consistencygroups'],
|
||||||
|
member={'update': 'PUT'})
|
||||||
|
|||||||
@@ -453,7 +453,16 @@ class API(base.Base):
|
|||||||
self.volume_rpcapi.delete_consistencygroup(context, group)
|
self.volume_rpcapi.delete_consistencygroup(context, group)
|
||||||
|
|
||||||
def _check_update(self, group, name, description, add_volumes,
|
def _check_update(self, group, name, description, add_volumes,
|
||||||
remove_volumes):
|
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):
|
if not (name or description or add_volumes or remove_volumes):
|
||||||
msg = (_("Cannot update consistency group %(group_id)s "
|
msg = (_("Cannot update consistency group %(group_id)s "
|
||||||
"because no valid name, description, add_volumes, "
|
"because no valid name, description, add_volumes, "
|
||||||
@@ -462,7 +471,7 @@ class API(base.Base):
|
|||||||
raise exception.InvalidConsistencyGroup(reason=msg)
|
raise exception.InvalidConsistencyGroup(reason=msg)
|
||||||
|
|
||||||
def update(self, context, group, name, description,
|
def update(self, context, group, name, description,
|
||||||
add_volumes, remove_volumes):
|
add_volumes, remove_volumes, allow_empty=False):
|
||||||
"""Update consistency group."""
|
"""Update consistency group."""
|
||||||
add_volumes_list = []
|
add_volumes_list = []
|
||||||
remove_volumes_list = []
|
remove_volumes_list = []
|
||||||
@@ -489,14 +498,19 @@ class API(base.Base):
|
|||||||
# Validate description.
|
# Validate description.
|
||||||
if description == group.description:
|
if description == group.description:
|
||||||
description = None
|
description = None
|
||||||
|
|
||||||
self._check_update(group, name, description, add_volumes,
|
self._check_update(group, name, description, add_volumes,
|
||||||
remove_volumes)
|
remove_volumes, allow_empty)
|
||||||
|
|
||||||
fields = {'updated_at': timeutils.utcnow()}
|
fields = {'updated_at': timeutils.utcnow()}
|
||||||
|
|
||||||
# Update name and description in db now. No need to
|
# Update name and description in db now. No need to
|
||||||
# to send them over through an RPC call.
|
# to send them over through an RPC call.
|
||||||
|
if allow_empty:
|
||||||
|
if name is not None:
|
||||||
|
fields['name'] = name
|
||||||
|
if description is not None:
|
||||||
|
fields['description'] = description
|
||||||
|
else:
|
||||||
if name:
|
if name:
|
||||||
fields['name'] = name
|
fields['name'] = name
|
||||||
if description:
|
if description:
|
||||||
@@ -531,7 +545,7 @@ class API(base.Base):
|
|||||||
group.volumes, remove_volumes_list, group)
|
group.volumes, remove_volumes_list, group)
|
||||||
|
|
||||||
self._check_update(group, name, description, add_volumes_new,
|
self._check_update(group, name, description, add_volumes_new,
|
||||||
remove_volumes_new)
|
remove_volumes_new, allow_empty)
|
||||||
except Exception:
|
except Exception:
|
||||||
# If we have an error on the volume_lists we must return status to
|
# If we have an error on the volume_lists we must return status to
|
||||||
# available as we were doing before removing API races
|
# 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.
|
||||||
Reference in New Issue
Block a user