[2/4]Reset group snapshot status

Currently the administrator could only reset the group snapshot
status by db operation, this change intends to add new admin
action to achieve this.

The patch list:
    1. group API(https://review.openstack.org/#/c/389091/).
    2. group snapshot API(this).
    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: I9e3a26950c435038cf40bea4b27aea1bd5049e95
This commit is contained in:
TommyLike 2016-10-21 15:47:03 +08:00
parent 152138b13b
commit 304ff4c23d
14 changed files with 236 additions and 5 deletions

View File

@ -67,6 +67,7 @@ REST_API_VERSION_HISTORY = """
* 3.16 - Migrate volume now supports cluster
* 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.
"""
# The minimum and maximum versions of the API supported
@ -74,7 +75,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.18"
_MAX_API_VERSION = "3.19"
_LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0"

View File

@ -210,3 +210,7 @@ user documentation.
3.18
----
Added backup project attribute.
3.19
----
Added reset status actions 'reset_status' to group snapshot.

View File

@ -26,6 +26,7 @@ from cinder.api.v3.views import group_snapshots as group_snapshot_views
from cinder import exception
from cinder import group as group_api
from cinder.i18n import _, _LI
from cinder import rpc
LOG = logging.getLogger(__name__)
@ -141,6 +142,49 @@ class GroupSnapshotsController(wsgi.Controller):
return retval
@wsgi.Controller.api_version('3.19')
@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 group snapshots"""
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('groupSnapshotStatusUpdate')
notifier.info(context, 'groupsnapshots.reset_status.start',
{'id': id,
'update': status})
gsnapshot = self.group_snapshot_api.get_group_snapshot(context, id)
self.group_snapshot_api.reset_group_snapshot_status(context,
gsnapshot,
status)
notifier.info(context, 'groupsnapshots.reset_status.end',
{'id': id,
'update': status})
except exception.GroupSnapshotNotFound as error:
# Not found exception will be handled at the wsgi level
notifier.error(context, 'groupsnapshots.reset_status',
{'error_message': error.msg,
'id': id})
raise
except exception.InvalidGroupSnapshotStatus as error:
notifier.error(context, 'groupsnapshots.reset_status',
{'error_message': error.msg,
'id': id})
raise exc.HTTPBadRequest(explanation=error.msg)
return webob.Response(status_int=202)
def create_resource():
return wsgi.Resource(GroupSnapshotsController())

View File

@ -100,12 +100,16 @@ class APIRouter(cinder.api.openstack.APIRouter):
action="action",
conditions={"method": ["POST"]})
self.resources['group_snapshots'] = (group_snapshots.create_resource())
self.resources['group_snapshots'] = group_snapshots.create_resource()
mapper.resource("group_snapshot", "group_snapshots",
controller=self.resources['group_snapshots'],
collection={'detail': 'GET'},
member={'action': 'POST'})
mapper.connect("group_snapshots",
"/{project_id}/group_snapshots/{id}/action",
controller=self.resources["group_snapshots"],
action="action",
conditions={"method": ["POST"]})
self.resources['snapshots'] = snapshots.create_resource(ext_mgr)
mapper.resource("snapshot", "snapshots",
controller=self.resources['snapshots'],

View File

@ -1079,6 +1079,10 @@ class InvalidGroupSnapshot(Invalid):
message = _("Invalid GroupSnapshot: %(reason)s")
class InvalidGroupSnapshotStatus(Invalid):
message = _("Invalid GroupSnapshot Status: %(reason)s")
# Hitachi Block Storage Driver
class HBSDError(VolumeDriverException):
message = _("HBSD error occurs.")

View File

@ -844,3 +844,18 @@ class API(base.Base):
group_snapshots = objects.GroupSnapshotList.get_all_by_project(
context.elevated(), context.project_id, search_opts)
return group_snapshots
def reset_group_snapshot_status(self, context, gsnapshot, status):
"""Reset status of group snapshot"""
check_policy(context, 'reset_group_snapshot_status')
if status not in c_fields.GroupSnapshotStatus.ALL:
msg = _("Group snapshot status: %(status)s is invalid, "
"valid statuses are: "
"%(valid)s.") % {'status': status,
'valid': c_fields.GroupSnapshotStatus.ALL}
raise exception.InvalidGroupSnapshotStatus(reason=msg)
field = {'updated_at': timeutils.utcnow(),
'status': status}
gsnapshot.update(field)
gsnapshot.save()

View File

@ -80,6 +80,23 @@ class GroupStatusField(BaseEnumField):
AUTO_TYPE = GroupStatus()
class GroupSnapshotStatus(BaseCinderEnum):
ERROR = 'error'
AVAILABLE = 'available'
CREATING = 'creating'
DELETING = 'deleting'
DELETED = 'deleted'
UPDATING = 'updating'
ERROR_DELETING = 'error_deleting'
ALL = (ERROR, AVAILABLE, CREATING, DELETING, DELETED,
UPDATING, ERROR_DELETING)
class GroupSnapshotStatusField(BaseEnumField):
AUTO_TYPE = GroupSnapshotStatus()
class ReplicationStatus(BaseCinderEnum):
ERROR = 'error'
ENABLED = 'enabled'

View File

@ -270,3 +270,7 @@ class TestOpenStackClient(object):
def delete_group_snapshot(self, group_snapshot_id):
return self.api_delete('/group_snapshots/%s' % group_snapshot_id)
def reset_group_snapshot(self, group_snapshot_id, params):
return self.api_post('/group_snapshots/%s/action' % group_snapshot_id,
params)

View File

@ -20,7 +20,7 @@ class GroupSnapshotsTest(functional_helpers._FunctionalTestBase):
_vol_type_name = 'functional_test_type'
_grp_type_name = 'functional_grp_test_type'
osapi_version_major = '3'
osapi_version_minor = '14'
osapi_version_minor = '19'
def setUp(self):
super(GroupSnapshotsTest, self).setUp()
@ -295,3 +295,54 @@ class GroupSnapshotsTest(functional_helpers._FunctionalTestBase):
self.assertFalse(found_group_from_group)
self.assertFalse(found_volume)
self.assertFalse(found_group)
def test_reset_group_snapshot(self):
# Create group
group1 = self.api.post_group(
{'group': {'group_type': self.group_type['id'],
'volume_types': [self.volume_type['id']]}})
self.assertTrue(group1['id'])
group_id = group1['id']
self._poll_group_while(group_id, ['creating'])
# Create volume
created_volume = self.api.post_volume(
{'volume': {'size': 1,
'group_id': group_id,
'volume_type': self.volume_type['id']}})
self.assertTrue(created_volume['id'])
created_volume_id = created_volume['id']
self._poll_volume_while(created_volume_id, ['creating'])
# Create group snapshot
group_snapshot1 = self.api.post_group_snapshot(
{'group_snapshot': {'group_id': group_id}})
self.assertTrue(group_snapshot1['id'])
group_snapshot_id = group_snapshot1['id']
self._poll_group_snapshot_while(group_snapshot_id, 'creating')
group_snapshot1 = self.api.get_group_snapshot(group_snapshot_id)
self.assertEqual("available", group_snapshot1['status'])
# reset group snapshot status
self.api.reset_group_snapshot(group_snapshot_id,
{"reset_status": {"status": "error"}})
group_snapshot1 = self.api.get_group_snapshot(group_snapshot_id)
self.assertEqual("error", group_snapshot1['status'])
# Delete group, volume and group snapshot
self.api.delete_group_snapshot(group_snapshot_id)
found_group_snapshot = self._poll_group_snapshot_while(
group_snapshot_id, ['deleting'])
self.api.delete_group(group_id,
{'delete': {'delete-volumes': True}})
found_volume = self._poll_volume_while(created_volume_id, ['deleting'])
found_group = self._poll_group_while(group_id, ['deleting'])
# Created resoueces should be gone
self.assertFalse(found_group_snapshot)
self.assertFalse(found_volume)
self.assertFalse(found_group)

View File

@ -17,6 +17,7 @@
Tests for group_snapshot code.
"""
import ddt
import mock
import webob
@ -26,6 +27,7 @@ from cinder import db
from cinder import exception
from cinder.group import api as group_api
from cinder import objects
from cinder.objects import fields
from cinder import test
from cinder.tests.unit.api import fakes
from cinder.tests.unit import fake_constants as fake
@ -35,6 +37,7 @@ import cinder.volume
GROUP_MICRO_VERSION = '3.14'
@ddt.ddt
class GroupSnapshotsAPITestCase(test.TestCase):
"""Test Case for group_snapshots API."""
@ -395,7 +398,7 @@ class GroupSnapshotsAPITestCase(test.TestCase):
self.controller.delete,
req, fake.WILL_NOT_BE_FOUND_ID)
def test_delete_group_snapshot_with_Invalid_group_snapshot(self):
def test_delete_group_snapshot_with_invalid_group_snapshot(self):
group = utils.create_group(
self.context,
group_type_id=fake.GROUP_TYPE_ID,
@ -418,3 +421,69 @@ class GroupSnapshotsAPITestCase(test.TestCase):
db.volume_destroy(context.get_admin_context(),
volume_id)
group.destroy()
@ddt.data(('3.11', 'fake_snapshot_001',
fields.GroupSnapshotStatus.AVAILABLE,
exception.VersionNotFoundForAPIMethod),
('3.18', 'fake_snapshot_001',
fields.GroupSnapshotStatus.AVAILABLE,
exception.VersionNotFoundForAPIMethod),
('3.19', 'fake_snapshot_001',
fields.GroupSnapshotStatus.AVAILABLE,
exception.GroupSnapshotNotFound))
@ddt.unpack
def test_reset_group_snapshot_status_illegal(self, version,
group_snapshot_id,
status, exceptions):
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s/action' %
(fake.PROJECT_ID, group_snapshot_id),
version=version)
body = {"reset_status": {
"status": status
}}
self.assertRaises(exceptions,
self.controller.reset_status,
req, group_snapshot_id, body)
def test_reset_group_snapshot_status_invalid_status(self):
group = utils.create_group(
self.context,
group_type_id=fake.GROUP_TYPE_ID,
volume_type_ids=[fake.VOLUME_TYPE_ID])
group_snapshot = utils.create_group_snapshot(
self.context,
group_id=group.id,
status=fields.GroupSnapshotStatus.CREATING)
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s/action' %
(fake.PROJECT_ID, group_snapshot.id),
version='3.19')
body = {"reset_status": {
"status": "invalid_test_status"
}}
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.reset_status,
req, group_snapshot.id, body)
def test_reset_group_snapshot_status(self):
group = utils.create_group(
self.context,
group_type_id=fake.GROUP_TYPE_ID,
volume_type_ids=[fake.VOLUME_TYPE_ID])
group_snapshot = utils.create_group_snapshot(
self.context,
group_id=group.id,
status=fields.GroupSnapshotStatus.CREATING)
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s/action' %
(fake.PROJECT_ID, group_snapshot.id),
version='3.19')
body = {"reset_status": {
"status": fields.GroupSnapshotStatus.AVAILABLE
}}
response = self.controller.reset_status(req, group_snapshot.id,
body)
g_snapshot = objects.GroupSnapshot.get_by_id(self.context,
group_snapshot.id)
self.assertEqual(202, response.status_int)
self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE,
g_snapshot.status)

View File

@ -515,3 +515,16 @@ class GroupAPITestCase(test.TestCase):
self.assertEqual(grp.obj_to_primitive(), ret_group.obj_to_primitive())
mock_create_from_snap.assert_called_once_with(
self.ctxt, grp, fake.GROUP_SNAPSHOT_ID)
@mock.patch('oslo_utils.timeutils.utcnow')
@mock.patch('cinder.objects.GroupSnapshot')
def test_reset_group_snapshot_status(self, mock_group_snapshot,
mock_time_util):
mock_time_util.return_value = "time_now"
self.group_api.reset_group_snapshot_status(
self.ctxt, mock_group_snapshot, fields.GroupSnapshotStatus.ERROR)
update_field = {'updated_at': "time_now",
'status': fields.GroupSnapshotStatus.ERROR}
mock_group_snapshot.update.assert_called_once_with(update_field)
mock_group_snapshot.save.assert_called_once_with()

View File

@ -131,6 +131,7 @@
"group:update_group_snapshot": "",
"group:get_group_snapshot": "",
"group:get_all_group_snapshots": "",
"group:reset_group_snapshot_status":"",
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",

View File

@ -126,6 +126,7 @@
"group:update_group_snapshot": "rule:admin_or_owner",
"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",
"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 group snapshot.