Merge "Use conditional update for group update and delete"
This commit is contained in:
commit
6538682ecb
@ -516,6 +516,7 @@ class API(base.Base):
|
|||||||
|
|
||||||
@wrap_check_policy
|
@wrap_check_policy
|
||||||
def delete(self, context, group, delete_volumes=False):
|
def delete(self, context, group, delete_volumes=False):
|
||||||
|
|
||||||
if not group.host:
|
if not group.host:
|
||||||
self.update_quota(context, group, -1, group.project_id)
|
self.update_quota(context, group, -1, group.project_id)
|
||||||
|
|
||||||
@ -540,6 +541,9 @@ class API(base.Base):
|
|||||||
raise exception.InvalidGroup(
|
raise exception.InvalidGroup(
|
||||||
reason=_("Group has existing snapshots."))
|
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(),
|
volumes = self.db.volume_get_all_by_generic_group(context.elevated(),
|
||||||
group.id)
|
group.id)
|
||||||
if volumes and not delete_volumes:
|
if volumes and not delete_volumes:
|
||||||
@ -570,9 +574,31 @@ class API(base.Base):
|
|||||||
|
|
||||||
self.db.volumes_update(context, volumes_model_update)
|
self.db.volumes_update(context, volumes_model_update)
|
||||||
|
|
||||||
group.status = c_fields.GroupStatus.DELETING
|
if delete_volumes:
|
||||||
group.terminated_at = timeutils.utcnow()
|
# We're overloading the term "delete_volumes" somewhat to also
|
||||||
group.save()
|
# 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)
|
self.volume_rpcapi.delete_group(context, group)
|
||||||
|
|
||||||
@ -580,10 +606,13 @@ class API(base.Base):
|
|||||||
def update(self, context, group, name, description,
|
def update(self, context, group, name, description,
|
||||||
add_volumes, remove_volumes):
|
add_volumes, remove_volumes):
|
||||||
"""Update group."""
|
"""Update group."""
|
||||||
if group.status != c_fields.GroupStatus.AVAILABLE:
|
# Validate name.
|
||||||
msg = _("Group status must be available, "
|
if name == group.name:
|
||||||
"but current status is: %s.") % group.status
|
name = None
|
||||||
raise exception.InvalidGroup(reason=msg)
|
|
||||||
|
# Validate description.
|
||||||
|
if description == group.description:
|
||||||
|
description = None
|
||||||
|
|
||||||
add_volumes_list = []
|
add_volumes_list = []
|
||||||
remove_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)
|
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.
|
# Validate volumes in add_volumes and remove_volumes.
|
||||||
add_volumes_new = ""
|
add_volumes_new = ""
|
||||||
remove_volumes_new = ""
|
remove_volumes_new = ""
|
||||||
@ -631,6 +652,7 @@ class API(base.Base):
|
|||||||
{'group_id': group.id})
|
{'group_id': group.id})
|
||||||
raise exception.InvalidGroup(reason=msg)
|
raise exception.InvalidGroup(reason=msg)
|
||||||
|
|
||||||
|
expected = {}
|
||||||
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
|
||||||
@ -643,10 +665,12 @@ class API(base.Base):
|
|||||||
# Only update name or description. Set status to available.
|
# Only update name or description. Set status to available.
|
||||||
fields['status'] = c_fields.GroupStatus.AVAILABLE
|
fields['status'] = c_fields.GroupStatus.AVAILABLE
|
||||||
else:
|
else:
|
||||||
|
expected['status'] = c_fields.GroupStatus.AVAILABLE
|
||||||
fields['status'] = c_fields.GroupStatus.UPDATING
|
fields['status'] = c_fields.GroupStatus.UPDATING
|
||||||
|
|
||||||
group.update(fields)
|
if not group.conditional_update(fields, expected):
|
||||||
group.save()
|
msg = _("Group status must be available.")
|
||||||
|
raise exception.InvalidGroup(reason=msg)
|
||||||
|
|
||||||
# Do an RPC call only if the update request includes
|
# Do an RPC call only if the update request includes
|
||||||
# adding/removing volumes. add_volumes_new and remove_volumes_new
|
# adding/removing volumes. add_volumes_new and remove_volumes_new
|
||||||
|
@ -1111,16 +1111,23 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
|
|||||||
consistencygroup.destroy()
|
consistencygroup.destroy()
|
||||||
|
|
||||||
def test_update_consistencygroup_invalid_state(self):
|
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(
|
consistencygroup = self._create_consistencygroup(
|
||||||
status=fields.ConsistencyGroupStatus.CREATING,
|
status=fields.ConsistencyGroupStatus.CREATING,
|
||||||
|
volume_type_ids=[volume_type_id],
|
||||||
ctxt=self.ctxt)
|
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' %
|
req = webob.Request.blank('/v2/%s/consistencygroups/%s/update' %
|
||||||
(fake.PROJECT_ID, consistencygroup.id))
|
(fake.PROJECT_ID, consistencygroup.id))
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
req.headers['Content-Type'] = 'application/json'
|
req.headers['Content-Type'] = 'application/json'
|
||||||
body = {"consistencygroup": {"name": "new name",
|
body = {"consistencygroup": {"name": "new name",
|
||||||
"description": None,
|
"description": None,
|
||||||
"add_volumes": None,
|
"add_volumes": add_volume_id,
|
||||||
"remove_volumes": None, }}
|
"remove_volumes": None, }}
|
||||||
req.body = jsonutils.dump_as_bytes(body)
|
req.body = jsonutils.dump_as_bytes(body)
|
||||||
res = req.get_response(fakes.wsgi_app(
|
res = req.get_response(fakes.wsgi_app(
|
||||||
|
@ -846,6 +846,25 @@ class GroupsAPITestCase(test.TestCase):
|
|||||||
add_volume.destroy()
|
add_volume.destroy()
|
||||||
add_volume2.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):
|
def test_update_group_add_volume_not_found(self):
|
||||||
self.group1.status = fields.GroupStatus.AVAILABLE
|
self.group1.status = fields.GroupStatus.AVAILABLE
|
||||||
self.group1.save()
|
self.group1.save()
|
||||||
@ -959,18 +978,27 @@ class GroupsAPITestCase(test.TestCase):
|
|||||||
@ddt.data(fields.GroupStatus.CREATING, fields.GroupStatus.UPDATING)
|
@ddt.data(fields.GroupStatus.CREATING, fields.GroupStatus.UPDATING)
|
||||||
def test_update_group_invalid_state(self, status):
|
def test_update_group_invalid_state(self, status):
|
||||||
self.group1.status = 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' %
|
req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/update' %
|
||||||
(fake.PROJECT_ID, self.group1.id),
|
(fake.PROJECT_ID, self.group1.id),
|
||||||
version=GROUP_MICRO_VERSION)
|
version=GROUP_MICRO_VERSION)
|
||||||
|
|
||||||
body = {"group": {"name": "new name",
|
body = {"group": {"name": "new name",
|
||||||
"description": None,
|
"description": None,
|
||||||
"add_volumes": None,
|
"add_volumes": add_volume.id,
|
||||||
"remove_volumes": None, }}
|
"remove_volumes": None, }}
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
self.controller.update,
|
self.controller.update,
|
||||||
req, self.group1.id, body)
|
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',
|
@ddt.data(('3.11', 'fake_group_001',
|
||||||
fields.GroupStatus.AVAILABLE,
|
fields.GroupStatus.AVAILABLE,
|
||||||
exception.VersionNotFoundForAPIMethod),
|
exception.VersionNotFoundForAPIMethod),
|
||||||
|
6
releasenotes/notes/group-update-d423eaa18dbcecc1.yaml
Normal file
6
releasenotes/notes/group-update-d423eaa18dbcecc1.yaml
Normal 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.
|
Loading…
x
Reference in New Issue
Block a user