Merge "[1/4]Reset generic volume group status"

This commit is contained in:
Jenkins
2016-12-27 03:26:17 +00:00
committed by Gerrit Code Review
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.17 - Getting manageable volumes and snapshots now accepts cluster.
* 3.18 - Add backup project attribute. * 3.18 - Add backup project attribute.
* 3.19 - Add API reset status actions 'reset_status' to group snapshot. * 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 # The minimum and maximum versions of the API supported
@@ -75,7 +77,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.19" _MAX_API_VERSION = "3.20"
_LEGACY_API_VERSION1 = "1.0" _LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0" _LEGACY_API_VERSION2 = "2.0"

View File

@@ -214,3 +214,7 @@ user documentation.
3.19 3.19
---- ----
Added reset status actions 'reset_status' to group snapshot. 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 exception
from cinder import group as group_api from cinder import group as group_api
from cinder.i18n import _, _LI from cinder.i18n import _, _LI
from cinder import rpc
from cinder.volume import group_types from cinder.volume import group_types
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@@ -65,6 +66,47 @@ class GroupsController(wsgi.Controller):
return self._view_builder.detail(req, group) 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.Controller.api_version(GROUP_API_VERSION)
@wsgi.action("delete") @wsgi.action("delete")
def delete_group(self, req, id, body): def delete_group(self, req, id, body):

View File

@@ -1061,6 +1061,10 @@ class InvalidGroup(Invalid):
message = _("Invalid Group: %(reason)s") message = _("Invalid Group: %(reason)s")
class InvalidGroupStatus(Invalid):
message = _("Invalid Group Status: %(reason)s")
# CgSnapshot # CgSnapshot
class CgSnapshotNotFound(NotFound): class CgSnapshotNotFound(NotFound):
message = _("CgSnapshot %(cgsnapshot_id)s could not be found.") message = _("CgSnapshot %(cgsnapshot_id)s could not be found.")

View File

@@ -772,6 +772,20 @@ class API(base.Base):
sort_dirs=sort_dirs) sort_dirs=sort_dirs)
return groups 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): def create_group_snapshot(self, context, group, name, description):
options = {'group_id': group.id, options = {'group_id': group.id,
'user_id': context.user_id, 'user_id': context.user_id,

View File

@@ -253,6 +253,9 @@ class TestOpenStackClient(object):
def delete_group(self, group_id, params): def delete_group(self, group_id, params):
return self.api_post('/groups/%s/action' % 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): def put_group(self, group_id, group):
return self.api_put('/groups/%s' % group_id, group)['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' _vol_type_name = 'functional_test_type'
_grp_type_name = 'functional_grp_test_type' _grp_type_name = 'functional_grp_test_type'
osapi_version_major = '3' osapi_version_major = '3'
osapi_version_minor = '13' osapi_version_minor = '20'
def setUp(self): def setUp(self):
super(GroupsTest, self).setUp() super(GroupsTest, self).setUp()
self.volume_type = self.api.create_type(self._vol_type_name) self.volume_type = self.api.create_type(self._vol_type_name)
self.group_type = self.api.create_group_type(self._grp_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): def _get_flags(self):
f = super(GroupsTest, self)._get_flags() f = super(GroupsTest, self)._get_flags()
@@ -45,6 +48,17 @@ class GroupsTest(functional_helpers._FunctionalTestBase):
grps = self.api.get_groups() grps = self.api.get_groups()
self.assertIsNotNone(grps) 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): def test_create_and_delete_group(self):
"""Creates and deletes a group.""" """Creates and deletes a group."""

View File

@@ -824,6 +824,47 @@ class GroupsAPITestCase(test.TestCase):
self.controller.update, self.controller.update,
req, self.group1.id, body) 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( @mock.patch(
'cinder.api.openstack.wsgi.Controller.validate_name_and_description') 'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
def test_create_group_from_src_snap(self, mock_validate): 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, mock_volume_types_get.assert_called_once_with(mock.ANY,
volume_type_names) 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.object(GROUP_QUOTAS, "reserve")
@mock.patch('cinder.objects.Group') @mock.patch('cinder.objects.Group')
@mock.patch('cinder.db.group_type_get_by_name') @mock.patch('cinder.db.group_type_get_by_name')

View File

@@ -132,6 +132,7 @@
"group:get_group_snapshot": "", "group:get_group_snapshot": "",
"group:get_all_group_snapshots": "", "group:get_all_group_snapshots": "",
"group:reset_group_snapshot_status":"", "group:reset_group_snapshot_status":"",
"group:reset_status":"",
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api", "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_group_snapshot": "rule:admin_or_owner",
"group:get_all_group_snapshots": "rule:admin_or_owner", "group:get_all_group_snapshots": "rule:admin_or_owner",
"group:reset_group_snapshot_status":"rule:admin_api", "group:reset_group_snapshot_status":"rule:admin_api",
"group:reset_status":"rule:admin_api",
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api", "scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
"message:delete": "rule:admin_or_owner", "message:delete": "rule:admin_or_owner",

View File

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