Merge "[2/4]Reset group snapshot status"
This commit is contained in:
commit
9be53e1449
@ -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"
|
||||
|
||||
|
@ -210,3 +210,7 @@ user documentation.
|
||||
3.18
|
||||
----
|
||||
Added backup project attribute.
|
||||
|
||||
3.19
|
||||
----
|
||||
Added reset status actions 'reset_status' to group snapshot.
|
||||
|
@ -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())
|
||||
|
@ -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'],
|
||||
|
@ -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.")
|
||||
|
@ -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()
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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",
|
||||
|
||||
|
@ -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",
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added reset status API to group snapshot.
|
Loading…
x
Reference in New Issue
Block a user