Merge "Add support to reset checkpoint state"

This commit is contained in:
Zuul 2018-12-03 08:16:44 +00:00 committed by Gerrit Code Review
commit 86ec524b83
11 changed files with 269 additions and 0 deletions

View File

@ -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,
}

View File

@ -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())

View File

@ -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={},

View File

@ -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")

View File

@ -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}'
}
]
)
]

View File

@ -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,

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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()

View File

@ -0,0 +1,4 @@
---
features:
- |
Added support for checkpoint state reset by admin and owner.