Add support to reset checkpoint state
Now when doing checkpoint copy failed, checkpoint will be wait_copying status forever, and so we can not do the restore anymore. So we should add an API to support checkpoint status reset if we deeply knows that the checkpoint is ok. This patch added API support for doing checkpoint state reset. Implements: bp checkpoint-status-reset Change-Id: Iabaa98c9900fba554be2ad0833d438901e01147a
This commit is contained in:
parent
18dcda1ee0
commit
ecc6b64f4a
@ -35,3 +35,22 @@ create = {
|
|||||||
'required': ['checkpoint'],
|
'required': ['checkpoint'],
|
||||||
'additionalProperties': False,
|
'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.")
|
LOG.info("Delete checkpoint request issued successfully.")
|
||||||
return {}
|
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():
|
def create_resource():
|
||||||
return wsgi.Resource(ProvidersController())
|
return wsgi.Resource(ProvidersController())
|
||||||
|
@ -96,6 +96,12 @@ class APIRouter(base_wsgi.Router):
|
|||||||
controller=providers_resources,
|
controller=providers_resources,
|
||||||
action='checkpoints_delete',
|
action='checkpoints_delete',
|
||||||
conditions={"method": ['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",
|
mapper.resource("trigger", "triggers",
|
||||||
controller=trigger_resources,
|
controller=trigger_resources,
|
||||||
collection={},
|
collection={},
|
||||||
|
@ -390,6 +390,10 @@ class CheckpointNotBeDeleted(KarborException):
|
|||||||
message = _("The checkpoint %(checkpoint_id)s can not be deleted.")
|
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):
|
class GetProtectionNetworkSubResourceFailed(KarborException):
|
||||||
message = _("Get protection network sub-resources of type %(type)s failed:"
|
message = _("Get protection network sub-resources of type %(type)s failed:"
|
||||||
" %(reason)s")
|
" %(reason)s")
|
||||||
|
@ -24,6 +24,7 @@ CHECKPOINT_GET_POLICY = 'provider:checkpoint_get'
|
|||||||
CHECKPOINT_GET_ALL_POLICY = 'provider:checkpoint_get_all'
|
CHECKPOINT_GET_ALL_POLICY = 'provider:checkpoint_get_all'
|
||||||
CHECKPOINT_CREATE_POLICY = 'provider:checkpoint_create'
|
CHECKPOINT_CREATE_POLICY = 'provider:checkpoint_create'
|
||||||
CHECKPOINT_DELETE_POLICY = 'provider:checkpoint_delete'
|
CHECKPOINT_DELETE_POLICY = 'provider:checkpoint_delete'
|
||||||
|
CHECKPOINT_UPDATE_POLICY = 'provider:checkpoint_update'
|
||||||
|
|
||||||
|
|
||||||
providers_policies = [
|
providers_policies = [
|
||||||
@ -87,6 +88,17 @@ providers_policies = [
|
|||||||
'path': '/providers/{provider_id}/checkpoints/{checkpoint_id}'
|
'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
|
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):
|
def show_checkpoint(self, context, provider_id, checkpoint_id):
|
||||||
return self.protection_rpcapi.show_checkpoint(
|
return self.protection_rpcapi.show_checkpoint(
|
||||||
context,
|
context,
|
||||||
|
@ -363,6 +363,30 @@ class ProtectionManager(manager.Manager):
|
|||||||
))
|
))
|
||||||
self._spawn(self.worker.run_flow, flow)
|
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):
|
def start(self, plan):
|
||||||
# TODO(wangliuan)
|
# TODO(wangliuan)
|
||||||
pass
|
pass
|
||||||
|
@ -82,6 +82,15 @@ class ProtectionAPI(object):
|
|||||||
provider_id=provider_id,
|
provider_id=provider_id,
|
||||||
checkpoint_id=checkpoint_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):
|
def show_checkpoint(self, ctxt, provider_id, checkpoint_id):
|
||||||
cctxt = self.client.prepare(version='1.0')
|
cctxt = self.client.prepare(version='1.0')
|
||||||
return cctxt.call(
|
return cctxt.call(
|
||||||
|
@ -18,6 +18,7 @@ from webob import exc
|
|||||||
|
|
||||||
from karbor.api.v1 import providers
|
from karbor.api.v1 import providers
|
||||||
from karbor import context
|
from karbor import context
|
||||||
|
from karbor import exception
|
||||||
from karbor.tests import base
|
from karbor.tests import base
|
||||||
from karbor.tests.unit.api import fakes
|
from karbor.tests.unit.api import fakes
|
||||||
|
|
||||||
@ -138,3 +139,98 @@ class ProvidersApiTest(base.TestCase):
|
|||||||
body=body)
|
body=body)
|
||||||
self.assertTrue(mock_plan_create.called)
|
self.assertTrue(mock_plan_create.called)
|
||||||
self.assertTrue(mock_protect.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',
|
'provider1',
|
||||||
'non_existent_checkpoint')
|
'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):
|
def tearDown(self):
|
||||||
flow_manager.Worker._load_engine = self.load_engine
|
flow_manager.Worker._load_engine = self.load_engine
|
||||||
super(ProtectionServiceTest, self).tearDown()
|
super(ProtectionServiceTest, self).tearDown()
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for checkpoint state reset by admin and owner.
|
Loading…
Reference in New Issue
Block a user