Merge "[2/4]Reset group snapshot status"

This commit is contained in:
Jenkins 2016-12-22 15:53:52 +00:00 committed by Gerrit Code Review
commit 9be53e1449
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.