[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:
parent
152138b13b
commit
304ff4c23d
@ -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…
Reference in New Issue
Block a user