[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:
TommyLike 2016-10-20 17:30:31 +08:00
parent 44ebdd2252
commit 15c555445b
12 changed files with 143 additions and 2 deletions

View File

@ -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"

View File

@ -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.

View File

@ -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):

View File

@ -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.")

View File

@ -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,

View File

@ -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']

View File

@ -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."""

View File

@ -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):

View File

@ -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')

View File

@ -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",

View File

@ -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",

View File

@ -0,0 +1,3 @@
---
features:
- Added reset status API to generic volume group.