Merge "Add support to reset checkpoint state"
This commit is contained in:
commit
86ec524b83
|
@ -35,3 +35,22 @@ create = {
|
|||
'required': ['checkpoint'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'os-resetState': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'state': {
|
||||
'type': 'string',
|
||||
'enum': ['available', 'error'],
|
||||
},
|
||||
},
|
||||
'required': ['state'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': [],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
|
|
@ -476,6 +476,45 @@ class ProvidersController(wsgi.Controller):
|
|||
LOG.info("Delete checkpoint request issued successfully.")
|
||||
return {}
|
||||
|
||||
def _checkpoint_reset_state(self, context, provider_id,
|
||||
checkpoint_id, state):
|
||||
try:
|
||||
self.protection_api.reset_state(context, provider_id,
|
||||
checkpoint_id, state)
|
||||
except exception.AccessCheckpointNotAllowed as error:
|
||||
raise exc.HTTPForbidden(explanation=error.msg)
|
||||
except exception.CheckpointNotFound as error:
|
||||
raise exc.HTTPNotFound(explanation=error.msg)
|
||||
except exception.CheckpointNotBeReset as error:
|
||||
raise exc.HTTPBadRequest(explanation=error.msg)
|
||||
LOG.info("Reset checkpoint state request issued successfully.")
|
||||
return {}
|
||||
|
||||
@validation.schema(checkpoint_schema.update)
|
||||
def checkpoints_update(self, req, provider_id, checkpoint_id, body):
|
||||
"""Reset a checkpoint's state"""
|
||||
context = req.environ['karbor.context']
|
||||
|
||||
LOG.info("Reset checkpoint state with id: %s", checkpoint_id)
|
||||
LOG.info("provider_id: %s.", provider_id)
|
||||
|
||||
if not uuidutils.is_uuid_like(provider_id):
|
||||
msg = _("Invalid provider id provided.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if not uuidutils.is_uuid_like(checkpoint_id):
|
||||
msg = _("Invalid checkpoint id provided.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
context.can(provider_policy.CHECKPOINT_UPDATE_POLICY)
|
||||
if body.get("os-resetState"):
|
||||
state = body["os-resetState"]["state"]
|
||||
return self._checkpoint_reset_state(
|
||||
context, provider_id, checkpoint_id, state)
|
||||
else:
|
||||
msg = _("Invalid input.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
|
||||
def create_resource():
|
||||
return wsgi.Resource(ProvidersController())
|
||||
|
|
|
@ -96,6 +96,12 @@ class APIRouter(base_wsgi.Router):
|
|||
controller=providers_resources,
|
||||
action='checkpoints_delete',
|
||||
conditions={"method": ['DELETE']})
|
||||
mapper.connect("provider",
|
||||
"/{project_id}/providers/{provider_id}/checkpoints/"
|
||||
"{checkpoint_id}",
|
||||
controller=providers_resources,
|
||||
action='checkpoints_update',
|
||||
conditions={'method': ['PUT']})
|
||||
mapper.resource("trigger", "triggers",
|
||||
controller=trigger_resources,
|
||||
collection={},
|
||||
|
|
|
@ -390,6 +390,10 @@ class CheckpointNotBeDeleted(KarborException):
|
|||
message = _("The checkpoint %(checkpoint_id)s can not be deleted.")
|
||||
|
||||
|
||||
class CheckpointNotBeReset(KarborException):
|
||||
message = _("The checkpoint %(checkpoint_id)s can not be reset.")
|
||||
|
||||
|
||||
class GetProtectionNetworkSubResourceFailed(KarborException):
|
||||
message = _("Get protection network sub-resources of type %(type)s failed:"
|
||||
" %(reason)s")
|
||||
|
|
|
@ -24,6 +24,7 @@ CHECKPOINT_GET_POLICY = 'provider:checkpoint_get'
|
|||
CHECKPOINT_GET_ALL_POLICY = 'provider:checkpoint_get_all'
|
||||
CHECKPOINT_CREATE_POLICY = 'provider:checkpoint_create'
|
||||
CHECKPOINT_DELETE_POLICY = 'provider:checkpoint_delete'
|
||||
CHECKPOINT_UPDATE_POLICY = 'provider:checkpoint_update'
|
||||
|
||||
|
||||
providers_policies = [
|
||||
|
@ -87,6 +88,17 @@ providers_policies = [
|
|||
'path': '/providers/{provider_id}/checkpoints/{checkpoint_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=CHECKPOINT_UPDATE_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description='Reset checkpoint state.',
|
||||
operations=[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/providers/{provider_id}/checkpoints/{checkpoint_id}'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,14 @@ class API(base.Base):
|
|||
checkpoint_id
|
||||
)
|
||||
|
||||
def reset_state(self, context, provider_id, checkpoint_id, state):
|
||||
return self.protection_rpcapi.reset_state(
|
||||
context,
|
||||
provider_id,
|
||||
checkpoint_id,
|
||||
state
|
||||
)
|
||||
|
||||
def show_checkpoint(self, context, provider_id, checkpoint_id):
|
||||
return self.protection_rpcapi.show_checkpoint(
|
||||
context,
|
||||
|
|
|
@ -363,6 +363,30 @@ class ProtectionManager(manager.Manager):
|
|||
))
|
||||
self._spawn(self.worker.run_flow, flow)
|
||||
|
||||
@messaging.expected_exceptions(exception.AccessCheckpointNotAllowed,
|
||||
exception.CheckpointNotBeReset)
|
||||
def reset_state(self, context, provider_id, checkpoint_id, state):
|
||||
provider = self.provider_registry.show_provider(provider_id)
|
||||
|
||||
checkpoint = provider.get_checkpoint(checkpoint_id, context=context)
|
||||
checkpoint_dict = checkpoint.to_dict()
|
||||
if not context.is_admin and (
|
||||
context.project_id != checkpoint_dict['project_id']):
|
||||
raise exception.AccessCheckpointNotAllowed(
|
||||
checkpoint_id=checkpoint_id)
|
||||
|
||||
if checkpoint.status not in [
|
||||
constants.CHECKPOINT_STATUS_AVAILABLE,
|
||||
constants.CHECKPOINT_STATUS_ERROR,
|
||||
constants.CHECKPOINT_STATUS_COPYING,
|
||||
constants.CHECKPOINT_STATUS_WAIT_COPYING,
|
||||
constants.CHECKPOINT_STATUS_COPY_FINISHED
|
||||
]:
|
||||
raise exception.CheckpointNotBeReset(
|
||||
checkpoint_id=checkpoint_id)
|
||||
checkpoint.status = state
|
||||
checkpoint.commit()
|
||||
|
||||
def start(self, plan):
|
||||
# TODO(wangliuan)
|
||||
pass
|
||||
|
|
|
@ -82,6 +82,15 @@ class ProtectionAPI(object):
|
|||
provider_id=provider_id,
|
||||
checkpoint_id=checkpoint_id)
|
||||
|
||||
def reset_state(self, ctxt, provider_id, checkpoint_id, state):
|
||||
cctxt = self.client.prepare(version='1.0')
|
||||
return cctxt.call(
|
||||
ctxt,
|
||||
'reset_state',
|
||||
provider_id=provider_id,
|
||||
checkpoint_id=checkpoint_id,
|
||||
state=state)
|
||||
|
||||
def show_checkpoint(self, ctxt, provider_id, checkpoint_id):
|
||||
cctxt = self.client.prepare(version='1.0')
|
||||
return cctxt.call(
|
||||
|
|
|
@ -18,6 +18,7 @@ from webob import exc
|
|||
|
||||
from karbor.api.v1 import providers
|
||||
from karbor import context
|
||||
from karbor import exception
|
||||
from karbor.tests import base
|
||||
from karbor.tests.unit.api import fakes
|
||||
|
||||
|
@ -138,3 +139,98 @@ class ProvidersApiTest(base.TestCase):
|
|||
body=body)
|
||||
self.assertTrue(mock_plan_create.called)
|
||||
self.assertTrue(mock_protect.called)
|
||||
|
||||
@mock.patch('karbor.services.protection.api.API.reset_state')
|
||||
def test_checkpoints_update_reset_state(self, mock_reset_state):
|
||||
req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
|
||||
'checkpoints/{checkpoint_id}')
|
||||
body = {
|
||||
'os-resetState': {'state': 'error'}
|
||||
}
|
||||
self.controller.checkpoints_update(
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body=body)
|
||||
self.assertTrue(mock_reset_state.called)
|
||||
|
||||
def test_checkpoints_update_reset_state_with_invalid_provider_id(self):
|
||||
req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
|
||||
'checkpoints/{checkpoint_id}')
|
||||
body = {
|
||||
'os-resetState': {'state': 'error'}
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.HTTPBadRequest,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'invalid_provider_id',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body=body)
|
||||
|
||||
def test_checkpoints_update_reset_state_with_invalid_checkpoint_id(self):
|
||||
req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
|
||||
'checkpoints/{checkpoint_id}')
|
||||
body = {
|
||||
'os-resetState': {'state': 'error'}
|
||||
}
|
||||
self.assertRaises(
|
||||
exc.HTTPBadRequest,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'invalid_checkpoint_id',
|
||||
body=body)
|
||||
|
||||
def test_checkpoints_update_reset_state_with_invalid_body(self):
|
||||
req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
|
||||
'checkpoints/{checkpoint_id}')
|
||||
self.assertRaises(
|
||||
exc.HTTPBadRequest,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body={})
|
||||
self.assertRaises(
|
||||
exception.ValidationError,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body={'os-resetState': {'state': 'invalid_state'}})
|
||||
|
||||
@mock.patch('karbor.services.protection.api.API.reset_state')
|
||||
def test_checkpoints_update_reset_state_with_protection_api_exceptions(
|
||||
self, mock_reset_state):
|
||||
req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
|
||||
'checkpoints/{checkpoint_id}')
|
||||
body = {
|
||||
'os-resetState': {'state': 'error'}
|
||||
}
|
||||
mock_reset_state.side_effect = exception.AccessCheckpointNotAllowed(
|
||||
checkpoint_id='2220f8b1-975d-4621-a872-fa9afb43cb6c')
|
||||
self.assertRaises(exc.HTTPForbidden,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body=body)
|
||||
|
||||
mock_reset_state.side_effect = exception.CheckpointNotFound(
|
||||
checkpoint_id='2220f8b1-975d-4621-a872-fa9afb43cb6c')
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body=body)
|
||||
|
||||
mock_reset_state.side_effect = exception.CheckpointNotBeReset(
|
||||
checkpoint_id='2220f8b1-975d-4621-a872-fa9afb43cb6c')
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.checkpoints_update,
|
||||
req,
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
body=body)
|
||||
|
|
|
@ -233,6 +233,54 @@ class ProtectionServiceTest(base.TestCase):
|
|||
'provider1',
|
||||
'non_existent_checkpoint')
|
||||
|
||||
@mock.patch.object(provider.ProviderRegistry, 'show_provider')
|
||||
def test_checkpoint_state_reset(self, mock_provider):
|
||||
fake_provider = fakes.FakeProvider()
|
||||
fake_checkpoint = fakes.FakeCheckpoint()
|
||||
fake_checkpoint.commit = mock.MagicMock()
|
||||
fake_provider.get_checkpoint = mock.MagicMock(
|
||||
return_value=fake_checkpoint)
|
||||
mock_provider.return_value = fake_provider
|
||||
context = mock.MagicMock(project_id='fake_project_id', is_admin=True)
|
||||
self.pro_manager.reset_state(context, 'provider1', 'fake_checkpoint',
|
||||
'error')
|
||||
self.assertEqual(fake_checkpoint.status, 'error')
|
||||
self.assertEqual(True, fake_checkpoint.commit.called)
|
||||
|
||||
@mock.patch.object(provider.ProviderRegistry, 'show_provider')
|
||||
def test_checkpoint_state_reset_with_access_not_allowed(
|
||||
self, mock_provider):
|
||||
fake_provider = fakes.FakeProvider()
|
||||
fake_checkpoint = fakes.FakeCheckpoint()
|
||||
fake_provider.get_checkpoint = mock.MagicMock(
|
||||
return_value=fake_checkpoint)
|
||||
mock_provider.return_value = fake_provider
|
||||
context = mock.MagicMock(project_id='fake_project_id_01',
|
||||
is_admin=False)
|
||||
self.assertRaises(oslo_messaging.ExpectedException,
|
||||
self.pro_manager.reset_state,
|
||||
context,
|
||||
'fake_project_id',
|
||||
'fake_checkpoint_id',
|
||||
'error')
|
||||
|
||||
@mock.patch.object(provider.ProviderRegistry, 'show_provider')
|
||||
def test_checkpoint_state_reset_with_wrong_checkpoint_state(
|
||||
self, mock_provider):
|
||||
fake_provider = fakes.FakeProvider()
|
||||
fake_checkpoint = fakes.FakeCheckpoint()
|
||||
fake_checkpoint.status = 'deleting'
|
||||
fake_provider.get_checkpoint = mock.MagicMock(
|
||||
return_value=fake_checkpoint)
|
||||
mock_provider.return_value = fake_provider
|
||||
context = mock.MagicMock(project_id='fake_project_id', is_admin=True)
|
||||
self.assertRaises(oslo_messaging.ExpectedException,
|
||||
self.pro_manager.reset_state,
|
||||
context,
|
||||
'fake_project_id',
|
||||
'fake_checkpoint_id',
|
||||
'error')
|
||||
|
||||
def tearDown(self):
|
||||
flow_manager.Worker._load_engine = self.load_engine
|
||||
super(ProtectionServiceTest, self).tearDown()
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added support for checkpoint state reset by admin and owner.
|
Loading…
Reference in New Issue