Merge "Use conditional update for group update and delete"

This commit is contained in:
Jenkins 2017-09-15 18:02:25 +00:00 committed by Gerrit Code Review
commit 6538682ecb
4 changed files with 84 additions and 19 deletions

View File

@ -516,6 +516,7 @@ class API(base.Base):
@wrap_check_policy
def delete(self, context, group, delete_volumes=False):
if not group.host:
self.update_quota(context, group, -1, group.project_id)
@ -540,6 +541,9 @@ class API(base.Base):
raise exception.InvalidGroup(
reason=_("Group has existing snapshots."))
# TODO(smcginnis): Add conditional update handling for volumes
# Should probably utilize the volume_api.delete code to handle
# cascade snapshot deletion and force delete.
volumes = self.db.volume_get_all_by_generic_group(context.elevated(),
group.id)
if volumes and not delete_volumes:
@ -570,9 +574,31 @@ class API(base.Base):
self.db.volumes_update(context, volumes_model_update)
group.status = c_fields.GroupStatus.DELETING
group.terminated_at = timeutils.utcnow()
group.save()
if delete_volumes:
# We're overloading the term "delete_volumes" somewhat to also
# mean to delete the group regardless of the state.
expected = {}
else:
expected = {'status': (c_fields.GroupStatus.AVAILABLE,
c_fields.GroupStatus.ERROR)}
filters = [~db.group_has_group_snapshot_filter(),
~db.group_has_volumes_filter(
attached_or_with_snapshots=delete_volumes),
~db.group_creating_from_src(group_id=group.id)]
values = {'status': c_fields.GroupStatus.DELETING}
if not group.conditional_update(values, expected, filters):
if delete_volumes:
reason = _('Group status must be available or error and must '
'not have dependent group snapshots')
else:
reason = _('Group must not have attached volumes, volumes '
'with snapshots, or dependent group snapshots')
msg = _('Cannot delete group %(id)s. %(reason)s, and '
'it cannot be the source for an ongoing group or group '
'snapshot creation.') % {
'id': group.id, 'reason': reason}
raise exception.InvalidGroup(reason=msg)
self.volume_rpcapi.delete_group(context, group)
@ -580,10 +606,13 @@ class API(base.Base):
def update(self, context, group, name, description,
add_volumes, remove_volumes):
"""Update group."""
if group.status != c_fields.GroupStatus.AVAILABLE:
msg = _("Group status must be available, "
"but current status is: %s.") % group.status
raise exception.InvalidGroup(reason=msg)
# Validate name.
if name == group.name:
name = None
# Validate description.
if description == group.description:
description = None
add_volumes_list = []
remove_volumes_list = []
@ -605,14 +634,6 @@ class API(base.Base):
volumes = self.db.volume_get_all_by_generic_group(context, group.id)
# Validate name.
if name == group.name:
name = None
# Validate description.
if description == group.description:
description = None
# Validate volumes in add_volumes and remove_volumes.
add_volumes_new = ""
remove_volumes_new = ""
@ -631,6 +652,7 @@ class API(base.Base):
{'group_id': group.id})
raise exception.InvalidGroup(reason=msg)
expected = {}
fields = {'updated_at': timeutils.utcnow()}
# Update name and description in db now. No need to
@ -643,10 +665,12 @@ class API(base.Base):
# Only update name or description. Set status to available.
fields['status'] = c_fields.GroupStatus.AVAILABLE
else:
expected['status'] = c_fields.GroupStatus.AVAILABLE
fields['status'] = c_fields.GroupStatus.UPDATING
group.update(fields)
group.save()
if not group.conditional_update(fields, expected):
msg = _("Group status must be available.")
raise exception.InvalidGroup(reason=msg)
# Do an RPC call only if the update request includes
# adding/removing volumes. add_volumes_new and remove_volumes_new

View File

@ -1111,16 +1111,23 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
consistencygroup.destroy()
def test_update_consistencygroup_invalid_state(self):
volume_type_id = utils.create_volume_type(
context.get_admin_context(), self, name='my_vol_type')['id']
consistencygroup = self._create_consistencygroup(
status=fields.ConsistencyGroupStatus.CREATING,
volume_type_ids=[volume_type_id],
ctxt=self.ctxt)
add_volume_id = utils.create_volume(
self.ctxt,
testcase_instance=self,
volume_type_id=volume_type_id)['id']
req = webob.Request.blank('/v2/%s/consistencygroups/%s/update' %
(fake.PROJECT_ID, consistencygroup.id))
req.method = 'PUT'
req.headers['Content-Type'] = 'application/json'
body = {"consistencygroup": {"name": "new name",
"description": None,
"add_volumes": None,
"add_volumes": add_volume_id,
"remove_volumes": None, }}
req.body = jsonutils.dump_as_bytes(body)
res = req.get_response(fakes.wsgi_app(

View File

@ -846,6 +846,25 @@ class GroupsAPITestCase(test.TestCase):
add_volume.destroy()
add_volume2.destroy()
@ddt.data(fields.GroupStatus.CREATING, fields.GroupStatus.UPDATING)
def test_update_group_any_state(self, status):
self.group1.status = status
req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
(fake.PROJECT_ID, self.group1.id),
version=GROUP_MICRO_VERSION)
body = {"group": {"name": "new name",
"description": "new description",
"add_volumes": None,
"remove_volumes": None, }}
res_dict = self.controller.update(
req, self.group1.id, body)
self.assertEqual(http_client.ACCEPTED, res_dict.status_int)
group = objects.Group.get_by_id(self.ctxt, self.group1.id)
self.assertEqual("new name", group.name)
self.assertEqual("new description", group.description)
def test_update_group_add_volume_not_found(self):
self.group1.status = fields.GroupStatus.AVAILABLE
self.group1.save()
@ -959,18 +978,27 @@ class GroupsAPITestCase(test.TestCase):
@ddt.data(fields.GroupStatus.CREATING, fields.GroupStatus.UPDATING)
def test_update_group_invalid_state(self, status):
self.group1.status = status
add_volume = utils.create_volume(
self.ctxt,
volume_type_id=self.volume_type1.id,
host=self.group1.host)
req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
(fake.PROJECT_ID, self.group1.id),
version=GROUP_MICRO_VERSION)
body = {"group": {"name": "new name",
"description": None,
"add_volumes": None,
"add_volumes": add_volume.id,
"remove_volumes": None, }}
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update,
req, self.group1.id, body)
vol = objects.Volume.get_by_id(self.ctxt, add_volume.id)
self.assertEqual(add_volume.status, vol.status)
add_volume.destroy()
@ddt.data(('3.11', 'fake_group_001',
fields.GroupStatus.AVAILABLE,
exception.VersionNotFoundForAPIMethod),

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Volume group updates of any kind had previously required the group to be
in ``Available`` status. Updates to the group name or
description will now work regardless of the volume group status.