[1/4]Reset generic volume group status
Currently the administrator could only reset the generic group status by db operation,this change intends to add new admin actions to achieve these. The patch list: 1. group API(this). 2. group snapshot API(https://review.openstack.org/#/c/389577/). 3. cinder client(https://review.openstack.org/390169/). 4. documentation(https://review.openstack.org/#/c/395464). APIImpact DocImpact Partial-Implements: blueprint reset-cg-and-cgs-status Change-Id: Ib8bffb806f878c67bb12fd5ef7ed8cc15606d1c5
This commit is contained in:
parent
44ebdd2252
commit
15c555445b
@ -68,6 +68,8 @@ REST_API_VERSION_HISTORY = """
|
||||
* 3.17 - Getting manageable volumes and snapshots now accepts cluster.
|
||||
* 3.18 - Add backup project attribute.
|
||||
* 3.19 - Add API reset status actions 'reset_status' to group snapshot.
|
||||
* 3.20 - Add API reset status actions 'reset_status' to generic
|
||||
volume group.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -75,7 +77,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.19"
|
||||
_MAX_API_VERSION = "3.20"
|
||||
_LEGACY_API_VERSION1 = "1.0"
|
||||
_LEGACY_API_VERSION2 = "2.0"
|
||||
|
||||
|
@ -214,3 +214,7 @@ user documentation.
|
||||
3.19
|
||||
----
|
||||
Added reset status actions 'reset_status' to group snapshot.
|
||||
|
||||
3.20
|
||||
----
|
||||
Added reset status actions 'reset_status' to generic volume group.
|
||||
|
@ -26,6 +26,7 @@ from cinder.api.v3.views import groups as views_groups
|
||||
from cinder import exception
|
||||
from cinder import group as group_api
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder import rpc
|
||||
from cinder.volume import group_types
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -65,6 +66,47 @@ class GroupsController(wsgi.Controller):
|
||||
|
||||
return self._view_builder.detail(req, group)
|
||||
|
||||
@wsgi.Controller.api_version('3.20')
|
||||
@wsgi.action("reset_status")
|
||||
def reset_status(self, req, id, body):
|
||||
return self._reset_status(req, id, body)
|
||||
|
||||
def _reset_status(self, req, id, body):
|
||||
"""Reset status on generic group."""
|
||||
|
||||
context = req.environ['cinder.context']
|
||||
try:
|
||||
status = body['reset_status']['status'].lower()
|
||||
except (TypeError, KeyError):
|
||||
raise exc.HTTPBadRequest(explanation=_("Must specify 'status'"))
|
||||
|
||||
LOG.debug("Updating group '%(id)s' with "
|
||||
"'%(update)s'", {'id': id,
|
||||
'update': status})
|
||||
try:
|
||||
notifier = rpc.get_notifier('groupStatusUpdate')
|
||||
notifier.info(context, 'groups.reset_status.start',
|
||||
{'id': id,
|
||||
'update': status})
|
||||
group = self.group_api.get(context, id)
|
||||
|
||||
self.group_api.reset_status(context, group, status)
|
||||
notifier.info(context, 'groups.reset_status.end',
|
||||
{'id': id,
|
||||
'update': status})
|
||||
except exception.GroupNotFound as error:
|
||||
# Not found exception will be handled at the wsgi level
|
||||
notifier.error(context, 'groups.reset_status',
|
||||
{'error_message': error.msg,
|
||||
'id': id})
|
||||
raise
|
||||
except exception.InvalidGroupStatus as error:
|
||||
notifier.error(context, 'groups.reset_status',
|
||||
{'error_message': error.msg,
|
||||
'id': id})
|
||||
raise exc.HTTPBadRequest(explanation=error.msg)
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@wsgi.Controller.api_version(GROUP_API_VERSION)
|
||||
@wsgi.action("delete")
|
||||
def delete_group(self, req, id, body):
|
||||
|
@ -1061,6 +1061,10 @@ class InvalidGroup(Invalid):
|
||||
message = _("Invalid Group: %(reason)s")
|
||||
|
||||
|
||||
class InvalidGroupStatus(Invalid):
|
||||
message = _("Invalid Group Status: %(reason)s")
|
||||
|
||||
|
||||
# CgSnapshot
|
||||
class CgSnapshotNotFound(NotFound):
|
||||
message = _("CgSnapshot %(cgsnapshot_id)s could not be found.")
|
||||
|
@ -772,6 +772,20 @@ class API(base.Base):
|
||||
sort_dirs=sort_dirs)
|
||||
return groups
|
||||
|
||||
def reset_status(self, context, group, status):
|
||||
"""Reset status of generic group"""
|
||||
|
||||
check_policy(context, 'reset_status')
|
||||
if status not in c_fields.GroupStatus.ALL:
|
||||
msg = _("Group status: %(status)s is invalid, valid status "
|
||||
"are: %(valid)s.") % {'status': status,
|
||||
'valid': c_fields.GroupStatus.ALL}
|
||||
raise exception.InvalidGroupStatus(reason=msg)
|
||||
field = {'updated_at': timeutils.utcnow(),
|
||||
'status': status}
|
||||
group.update(field)
|
||||
group.save()
|
||||
|
||||
def create_group_snapshot(self, context, group, name, description):
|
||||
options = {'group_id': group.id,
|
||||
'user_id': context.user_id,
|
||||
|
@ -253,6 +253,9 @@ class TestOpenStackClient(object):
|
||||
def delete_group(self, group_id, params):
|
||||
return self.api_post('/groups/%s/action' % group_id, params)
|
||||
|
||||
def reset_group(self, group_id, params):
|
||||
return self.api_post('/groups/%s/action' % group_id, params)
|
||||
|
||||
def put_group(self, group_id, group):
|
||||
return self.api_put('/groups/%s' % group_id, group)['group']
|
||||
|
||||
|
@ -20,12 +20,15 @@ class GroupsTest(functional_helpers._FunctionalTestBase):
|
||||
_vol_type_name = 'functional_test_type'
|
||||
_grp_type_name = 'functional_grp_test_type'
|
||||
osapi_version_major = '3'
|
||||
osapi_version_minor = '13'
|
||||
osapi_version_minor = '20'
|
||||
|
||||
def setUp(self):
|
||||
super(GroupsTest, self).setUp()
|
||||
self.volume_type = self.api.create_type(self._vol_type_name)
|
||||
self.group_type = self.api.create_group_type(self._grp_type_name)
|
||||
self.group1 = self.api.post_group(
|
||||
{'group': {'group_type': self.group_type['id'],
|
||||
'volume_types': [self.volume_type['id']]}})
|
||||
|
||||
def _get_flags(self):
|
||||
f = super(GroupsTest, self)._get_flags()
|
||||
@ -45,6 +48,17 @@ class GroupsTest(functional_helpers._FunctionalTestBase):
|
||||
grps = self.api.get_groups()
|
||||
self.assertIsNotNone(grps)
|
||||
|
||||
def test_reset_group_status(self):
|
||||
"""Reset group status"""
|
||||
found_group = self._poll_group_while(self.group1['id'],
|
||||
['creating'])
|
||||
self.assertEqual('available', found_group['status'])
|
||||
self.api.reset_group(self.group1['id'],
|
||||
{"reset_status": {"status": "error"}})
|
||||
|
||||
group = self.api.get_group(self.group1['id'])
|
||||
self.assertEqual("error", group['status'])
|
||||
|
||||
def test_create_and_delete_group(self):
|
||||
"""Creates and deletes a group."""
|
||||
|
||||
|
@ -824,6 +824,47 @@ class GroupsAPITestCase(test.TestCase):
|
||||
self.controller.update,
|
||||
req, self.group1.id, body)
|
||||
|
||||
@ddt.data(('3.11', 'fake_group_001',
|
||||
fields.GroupStatus.AVAILABLE,
|
||||
exception.VersionNotFoundForAPIMethod),
|
||||
('3.19', 'fake_group_001',
|
||||
fields.GroupStatus.AVAILABLE,
|
||||
exception.VersionNotFoundForAPIMethod),
|
||||
('3.20', 'fake_group_001',
|
||||
fields.GroupStatus.AVAILABLE,
|
||||
exception.GroupNotFound),
|
||||
('3.20', None,
|
||||
'invalid_test_status',
|
||||
webob.exc.HTTPBadRequest),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_reset_group_status_illegal(self, version, group_id,
|
||||
status, exceptions):
|
||||
g_id = group_id or self.group2.id
|
||||
req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
|
||||
(fake.PROJECT_ID, g_id),
|
||||
version=version)
|
||||
body = {"reset_status": {
|
||||
"status": status
|
||||
}}
|
||||
self.assertRaises(exceptions,
|
||||
self.controller.reset_status,
|
||||
req, g_id, body)
|
||||
|
||||
def test_reset_group_status(self):
|
||||
req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' %
|
||||
(fake.PROJECT_ID, self.group2.id),
|
||||
version='3.20')
|
||||
body = {"reset_status": {
|
||||
"status": fields.GroupStatus.AVAILABLE
|
||||
}}
|
||||
response = self.controller.reset_status(req,
|
||||
self.group2.id, body)
|
||||
|
||||
group = objects.Group.get_by_id(self.ctxt, self.group2.id)
|
||||
self.assertEqual(202, response.status_int)
|
||||
self.assertEqual(fields.GroupStatus.AVAILABLE, group.status)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_group_from_src_snap(self, mock_validate):
|
||||
|
@ -173,6 +173,18 @@ class GroupAPITestCase(test.TestCase):
|
||||
mock_volume_types_get.assert_called_once_with(mock.ANY,
|
||||
volume_type_names)
|
||||
|
||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
||||
@mock.patch('cinder.objects.Group')
|
||||
def test_reset_status(self, mock_group, mock_time_util):
|
||||
mock_time_util.return_value = "time_now"
|
||||
self.group_api.reset_status(self.ctxt, mock_group,
|
||||
fields.GroupStatus.AVAILABLE)
|
||||
|
||||
update_field = {'updated_at': "time_now",
|
||||
'status': fields.GroupStatus.AVAILABLE}
|
||||
mock_group.update.assert_called_once_with(update_field)
|
||||
mock_group.save.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(GROUP_QUOTAS, "reserve")
|
||||
@mock.patch('cinder.objects.Group')
|
||||
@mock.patch('cinder.db.group_type_get_by_name')
|
||||
|
@ -132,6 +132,7 @@
|
||||
"group:get_group_snapshot": "",
|
||||
"group:get_all_group_snapshots": "",
|
||||
"group:reset_group_snapshot_status":"",
|
||||
"group:reset_status":"",
|
||||
|
||||
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
|
||||
|
||||
|
@ -127,6 +127,7 @@
|
||||
"group:get_group_snapshot": "rule:admin_or_owner",
|
||||
"group:get_all_group_snapshots": "rule:admin_or_owner",
|
||||
"group:reset_group_snapshot_status":"rule:admin_api",
|
||||
"group:reset_status":"rule:admin_api",
|
||||
|
||||
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
|
||||
"message:delete": "rule:admin_or_owner",
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added reset status API to generic volume group.
|
Loading…
x
Reference in New Issue
Block a user