summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjiaopengju <jiaopengju@cmss.chinamobile.com>2018-12-02 20:59:00 +0800
committerjiaopengju <jiaopengju@cmss.chinamobile.com>2018-12-03 14:02:01 +0800
commitecc6b64f4aa1b212b9b6f819fea2bfdf6d5b49fd (patch)
treedcb7765187d32c85772166d25b7276030a6d8c90
parent18dcda1ee0ac211d84a1d78499e62be92f91fa9b (diff)
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
Notes
Notes (review): Code-Review+2: Jiao Pengju <jiaopengju@cmss.chinamobile.com> Code-Review+1: liushuai <liushuai@cmss.chinamobile.com> Workflow+1: Jiao Pengju <jiaopengju@cmss.chinamobile.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Mon, 03 Dec 2018 08:16:44 +0000 Reviewed-on: https://review.openstack.org/621403 Project: openstack/karbor Branch: refs/heads/master
-rw-r--r--karbor/api/schemas/checkpoints.py19
-rw-r--r--karbor/api/v1/providers.py39
-rw-r--r--karbor/api/v1/router.py6
-rw-r--r--karbor/exception.py4
-rw-r--r--karbor/policies/providers.py12
-rw-r--r--karbor/services/protection/api.py8
-rw-r--r--karbor/services/protection/manager.py24
-rw-r--r--karbor/services/protection/rpcapi.py9
-rw-r--r--karbor/tests/unit/api/v1/test_providers.py96
-rw-r--r--karbor/tests/unit/protection/test_manager.py48
-rw-r--r--releasenotes/notes/checkpoint-status-reset-d714b4a04da2f44d.yaml4
11 files changed, 269 insertions, 0 deletions
diff --git a/karbor/api/schemas/checkpoints.py b/karbor/api/schemas/checkpoints.py
index 9e1fb8c..d6da967 100644
--- a/karbor/api/schemas/checkpoints.py
+++ b/karbor/api/schemas/checkpoints.py
@@ -35,3 +35,22 @@ create = {
35 'required': ['checkpoint'], 35 'required': ['checkpoint'],
36 'additionalProperties': False, 36 'additionalProperties': False,
37} 37}
38
39update = {
40 'type': 'object',
41 'properties': {
42 'os-resetState': {
43 'type': 'object',
44 'properties': {
45 'state': {
46 'type': 'string',
47 'enum': ['available', 'error'],
48 },
49 },
50 'required': ['state'],
51 'additionalProperties': False,
52 },
53 },
54 'required': [],
55 'additionalProperties': False,
56}
diff --git a/karbor/api/v1/providers.py b/karbor/api/v1/providers.py
index 96854d1..3549b03 100644
--- a/karbor/api/v1/providers.py
+++ b/karbor/api/v1/providers.py
@@ -476,6 +476,45 @@ class ProvidersController(wsgi.Controller):
476 LOG.info("Delete checkpoint request issued successfully.") 476 LOG.info("Delete checkpoint request issued successfully.")
477 return {} 477 return {}
478 478
479 def _checkpoint_reset_state(self, context, provider_id,
480 checkpoint_id, state):
481 try:
482 self.protection_api.reset_state(context, provider_id,
483 checkpoint_id, state)
484 except exception.AccessCheckpointNotAllowed as error:
485 raise exc.HTTPForbidden(explanation=error.msg)
486 except exception.CheckpointNotFound as error:
487 raise exc.HTTPNotFound(explanation=error.msg)
488 except exception.CheckpointNotBeReset as error:
489 raise exc.HTTPBadRequest(explanation=error.msg)
490 LOG.info("Reset checkpoint state request issued successfully.")
491 return {}
492
493 @validation.schema(checkpoint_schema.update)
494 def checkpoints_update(self, req, provider_id, checkpoint_id, body):
495 """Reset a checkpoint's state"""
496 context = req.environ['karbor.context']
497
498 LOG.info("Reset checkpoint state with id: %s", checkpoint_id)
499 LOG.info("provider_id: %s.", provider_id)
500
501 if not uuidutils.is_uuid_like(provider_id):
502 msg = _("Invalid provider id provided.")
503 raise exc.HTTPBadRequest(explanation=msg)
504
505 if not uuidutils.is_uuid_like(checkpoint_id):
506 msg = _("Invalid checkpoint id provided.")
507 raise exc.HTTPBadRequest(explanation=msg)
508
509 context.can(provider_policy.CHECKPOINT_UPDATE_POLICY)
510 if body.get("os-resetState"):
511 state = body["os-resetState"]["state"]
512 return self._checkpoint_reset_state(
513 context, provider_id, checkpoint_id, state)
514 else:
515 msg = _("Invalid input.")
516 raise exc.HTTPBadRequest(explanation=msg)
517
479 518
480def create_resource(): 519def create_resource():
481 return wsgi.Resource(ProvidersController()) 520 return wsgi.Resource(ProvidersController())
diff --git a/karbor/api/v1/router.py b/karbor/api/v1/router.py
index a39159d..4838289 100644
--- a/karbor/api/v1/router.py
+++ b/karbor/api/v1/router.py
@@ -96,6 +96,12 @@ class APIRouter(base_wsgi.Router):
96 controller=providers_resources, 96 controller=providers_resources,
97 action='checkpoints_delete', 97 action='checkpoints_delete',
98 conditions={"method": ['DELETE']}) 98 conditions={"method": ['DELETE']})
99 mapper.connect("provider",
100 "/{project_id}/providers/{provider_id}/checkpoints/"
101 "{checkpoint_id}",
102 controller=providers_resources,
103 action='checkpoints_update',
104 conditions={'method': ['PUT']})
99 mapper.resource("trigger", "triggers", 105 mapper.resource("trigger", "triggers",
100 controller=trigger_resources, 106 controller=trigger_resources,
101 collection={}, 107 collection={},
diff --git a/karbor/exception.py b/karbor/exception.py
index 18cff95..f5fc127 100644
--- a/karbor/exception.py
+++ b/karbor/exception.py
@@ -390,6 +390,10 @@ class CheckpointNotBeDeleted(KarborException):
390 message = _("The checkpoint %(checkpoint_id)s can not be deleted.") 390 message = _("The checkpoint %(checkpoint_id)s can not be deleted.")
391 391
392 392
393class CheckpointNotBeReset(KarborException):
394 message = _("The checkpoint %(checkpoint_id)s can not be reset.")
395
396
393class GetProtectionNetworkSubResourceFailed(KarborException): 397class GetProtectionNetworkSubResourceFailed(KarborException):
394 message = _("Get protection network sub-resources of type %(type)s failed:" 398 message = _("Get protection network sub-resources of type %(type)s failed:"
395 " %(reason)s") 399 " %(reason)s")
diff --git a/karbor/policies/providers.py b/karbor/policies/providers.py
index a3f4865..eb6f547 100644
--- a/karbor/policies/providers.py
+++ b/karbor/policies/providers.py
@@ -24,6 +24,7 @@ CHECKPOINT_GET_POLICY = 'provider:checkpoint_get'
24CHECKPOINT_GET_ALL_POLICY = 'provider:checkpoint_get_all' 24CHECKPOINT_GET_ALL_POLICY = 'provider:checkpoint_get_all'
25CHECKPOINT_CREATE_POLICY = 'provider:checkpoint_create' 25CHECKPOINT_CREATE_POLICY = 'provider:checkpoint_create'
26CHECKPOINT_DELETE_POLICY = 'provider:checkpoint_delete' 26CHECKPOINT_DELETE_POLICY = 'provider:checkpoint_delete'
27CHECKPOINT_UPDATE_POLICY = 'provider:checkpoint_update'
27 28
28 29
29providers_policies = [ 30providers_policies = [
@@ -87,6 +88,17 @@ providers_policies = [
87 'path': '/providers/{provider_id}/checkpoints/{checkpoint_id}' 88 'path': '/providers/{provider_id}/checkpoints/{checkpoint_id}'
88 } 89 }
89 ]), 90 ]),
91 policy.DocumentedRuleDefault(
92 name=CHECKPOINT_UPDATE_POLICY,
93 check_str=base.RULE_ADMIN_OR_OWNER,
94 description='Reset checkpoint state.',
95 operations=[
96 {
97 'method': 'PUT',
98 'path': '/providers/{provider_id}/checkpoints/{checkpoint_id}'
99 }
100 ]
101 )
90] 102]
91 103
92 104
diff --git a/karbor/services/protection/api.py b/karbor/services/protection/api.py
index d8c01ef..89b6782 100644
--- a/karbor/services/protection/api.py
+++ b/karbor/services/protection/api.py
@@ -44,6 +44,14 @@ class API(base.Base):
44 checkpoint_id 44 checkpoint_id
45 ) 45 )
46 46
47 def reset_state(self, context, provider_id, checkpoint_id, state):
48 return self.protection_rpcapi.reset_state(
49 context,
50 provider_id,
51 checkpoint_id,
52 state
53 )
54
47 def show_checkpoint(self, context, provider_id, checkpoint_id): 55 def show_checkpoint(self, context, provider_id, checkpoint_id):
48 return self.protection_rpcapi.show_checkpoint( 56 return self.protection_rpcapi.show_checkpoint(
49 context, 57 context,
diff --git a/karbor/services/protection/manager.py b/karbor/services/protection/manager.py
index a2de22e..0bb7429 100644
--- a/karbor/services/protection/manager.py
+++ b/karbor/services/protection/manager.py
@@ -363,6 +363,30 @@ class ProtectionManager(manager.Manager):
363 )) 363 ))
364 self._spawn(self.worker.run_flow, flow) 364 self._spawn(self.worker.run_flow, flow)
365 365
366 @messaging.expected_exceptions(exception.AccessCheckpointNotAllowed,
367 exception.CheckpointNotBeReset)
368 def reset_state(self, context, provider_id, checkpoint_id, state):
369 provider = self.provider_registry.show_provider(provider_id)
370
371 checkpoint = provider.get_checkpoint(checkpoint_id, context=context)
372 checkpoint_dict = checkpoint.to_dict()
373 if not context.is_admin and (
374 context.project_id != checkpoint_dict['project_id']):
375 raise exception.AccessCheckpointNotAllowed(
376 checkpoint_id=checkpoint_id)
377
378 if checkpoint.status not in [
379 constants.CHECKPOINT_STATUS_AVAILABLE,
380 constants.CHECKPOINT_STATUS_ERROR,
381 constants.CHECKPOINT_STATUS_COPYING,
382 constants.CHECKPOINT_STATUS_WAIT_COPYING,
383 constants.CHECKPOINT_STATUS_COPY_FINISHED
384 ]:
385 raise exception.CheckpointNotBeReset(
386 checkpoint_id=checkpoint_id)
387 checkpoint.status = state
388 checkpoint.commit()
389
366 def start(self, plan): 390 def start(self, plan):
367 # TODO(wangliuan) 391 # TODO(wangliuan)
368 pass 392 pass
diff --git a/karbor/services/protection/rpcapi.py b/karbor/services/protection/rpcapi.py
index 7fd2f81..ce734c9 100644
--- a/karbor/services/protection/rpcapi.py
+++ b/karbor/services/protection/rpcapi.py
@@ -82,6 +82,15 @@ class ProtectionAPI(object):
82 provider_id=provider_id, 82 provider_id=provider_id,
83 checkpoint_id=checkpoint_id) 83 checkpoint_id=checkpoint_id)
84 84
85 def reset_state(self, ctxt, provider_id, checkpoint_id, state):
86 cctxt = self.client.prepare(version='1.0')
87 return cctxt.call(
88 ctxt,
89 'reset_state',
90 provider_id=provider_id,
91 checkpoint_id=checkpoint_id,
92 state=state)
93
85 def show_checkpoint(self, ctxt, provider_id, checkpoint_id): 94 def show_checkpoint(self, ctxt, provider_id, checkpoint_id):
86 cctxt = self.client.prepare(version='1.0') 95 cctxt = self.client.prepare(version='1.0')
87 return cctxt.call( 96 return cctxt.call(
diff --git a/karbor/tests/unit/api/v1/test_providers.py b/karbor/tests/unit/api/v1/test_providers.py
index 65cffd3..56a2826 100644
--- a/karbor/tests/unit/api/v1/test_providers.py
+++ b/karbor/tests/unit/api/v1/test_providers.py
@@ -18,6 +18,7 @@ from webob import exc
18 18
19from karbor.api.v1 import providers 19from karbor.api.v1 import providers
20from karbor import context 20from karbor import context
21from karbor import exception
21from karbor.tests import base 22from karbor.tests import base
22from karbor.tests.unit.api import fakes 23from karbor.tests.unit.api import fakes
23 24
@@ -138,3 +139,98 @@ class ProvidersApiTest(base.TestCase):
138 body=body) 139 body=body)
139 self.assertTrue(mock_plan_create.called) 140 self.assertTrue(mock_plan_create.called)
140 self.assertTrue(mock_protect.called) 141 self.assertTrue(mock_protect.called)
142
143 @mock.patch('karbor.services.protection.api.API.reset_state')
144 def test_checkpoints_update_reset_state(self, mock_reset_state):
145 req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
146 'checkpoints/{checkpoint_id}')
147 body = {
148 'os-resetState': {'state': 'error'}
149 }
150 self.controller.checkpoints_update(
151 req,
152 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
153 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
154 body=body)
155 self.assertTrue(mock_reset_state.called)
156
157 def test_checkpoints_update_reset_state_with_invalid_provider_id(self):
158 req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
159 'checkpoints/{checkpoint_id}')
160 body = {
161 'os-resetState': {'state': 'error'}
162 }
163 self.assertRaises(
164 exc.HTTPBadRequest,
165 self.controller.checkpoints_update,
166 req,
167 'invalid_provider_id',
168 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
169 body=body)
170
171 def test_checkpoints_update_reset_state_with_invalid_checkpoint_id(self):
172 req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
173 'checkpoints/{checkpoint_id}')
174 body = {
175 'os-resetState': {'state': 'error'}
176 }
177 self.assertRaises(
178 exc.HTTPBadRequest,
179 self.controller.checkpoints_update,
180 req,
181 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
182 'invalid_checkpoint_id',
183 body=body)
184
185 def test_checkpoints_update_reset_state_with_invalid_body(self):
186 req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
187 'checkpoints/{checkpoint_id}')
188 self.assertRaises(
189 exc.HTTPBadRequest,
190 self.controller.checkpoints_update,
191 req,
192 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
193 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
194 body={})
195 self.assertRaises(
196 exception.ValidationError,
197 self.controller.checkpoints_update,
198 req,
199 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
200 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
201 body={'os-resetState': {'state': 'invalid_state'}})
202
203 @mock.patch('karbor.services.protection.api.API.reset_state')
204 def test_checkpoints_update_reset_state_with_protection_api_exceptions(
205 self, mock_reset_state):
206 req = fakes.HTTPRequest.blank('/v1/providers/{provider_id}/'
207 'checkpoints/{checkpoint_id}')
208 body = {
209 'os-resetState': {'state': 'error'}
210 }
211 mock_reset_state.side_effect = exception.AccessCheckpointNotAllowed(
212 checkpoint_id='2220f8b1-975d-4621-a872-fa9afb43cb6c')
213 self.assertRaises(exc.HTTPForbidden,
214 self.controller.checkpoints_update,
215 req,
216 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
217 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
218 body=body)
219
220 mock_reset_state.side_effect = exception.CheckpointNotFound(
221 checkpoint_id='2220f8b1-975d-4621-a872-fa9afb43cb6c')
222 self.assertRaises(exc.HTTPNotFound,
223 self.controller.checkpoints_update,
224 req,
225 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
226 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
227 body=body)
228
229 mock_reset_state.side_effect = exception.CheckpointNotBeReset(
230 checkpoint_id='2220f8b1-975d-4621-a872-fa9afb43cb6c')
231 self.assertRaises(exc.HTTPBadRequest,
232 self.controller.checkpoints_update,
233 req,
234 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
235 '2220f8b1-975d-4621-a872-fa9afb43cb6c',
236 body=body)
diff --git a/karbor/tests/unit/protection/test_manager.py b/karbor/tests/unit/protection/test_manager.py
index 6ecf263..132304b 100644
--- a/karbor/tests/unit/protection/test_manager.py
+++ b/karbor/tests/unit/protection/test_manager.py
@@ -233,6 +233,54 @@ class ProtectionServiceTest(base.TestCase):
233 'provider1', 233 'provider1',
234 'non_existent_checkpoint') 234 'non_existent_checkpoint')
235 235
236 @mock.patch.object(provider.ProviderRegistry, 'show_provider')
237 def test_checkpoint_state_reset(self, mock_provider):
238 fake_provider = fakes.FakeProvider()
239 fake_checkpoint = fakes.FakeCheckpoint()
240 fake_checkpoint.commit = mock.MagicMock()
241 fake_provider.get_checkpoint = mock.MagicMock(
242 return_value=fake_checkpoint)
243 mock_provider.return_value = fake_provider
244 context = mock.MagicMock(project_id='fake_project_id', is_admin=True)
245 self.pro_manager.reset_state(context, 'provider1', 'fake_checkpoint',
246 'error')
247 self.assertEqual(fake_checkpoint.status, 'error')
248 self.assertEqual(True, fake_checkpoint.commit.called)
249
250 @mock.patch.object(provider.ProviderRegistry, 'show_provider')
251 def test_checkpoint_state_reset_with_access_not_allowed(
252 self, mock_provider):
253 fake_provider = fakes.FakeProvider()
254 fake_checkpoint = fakes.FakeCheckpoint()
255 fake_provider.get_checkpoint = mock.MagicMock(
256 return_value=fake_checkpoint)
257 mock_provider.return_value = fake_provider
258 context = mock.MagicMock(project_id='fake_project_id_01',
259 is_admin=False)
260 self.assertRaises(oslo_messaging.ExpectedException,
261 self.pro_manager.reset_state,
262 context,
263 'fake_project_id',
264 'fake_checkpoint_id',
265 'error')
266
267 @mock.patch.object(provider.ProviderRegistry, 'show_provider')
268 def test_checkpoint_state_reset_with_wrong_checkpoint_state(
269 self, mock_provider):
270 fake_provider = fakes.FakeProvider()
271 fake_checkpoint = fakes.FakeCheckpoint()
272 fake_checkpoint.status = 'deleting'
273 fake_provider.get_checkpoint = mock.MagicMock(
274 return_value=fake_checkpoint)
275 mock_provider.return_value = fake_provider
276 context = mock.MagicMock(project_id='fake_project_id', is_admin=True)
277 self.assertRaises(oslo_messaging.ExpectedException,
278 self.pro_manager.reset_state,
279 context,
280 'fake_project_id',
281 'fake_checkpoint_id',
282 'error')
283
236 def tearDown(self): 284 def tearDown(self):
237 flow_manager.Worker._load_engine = self.load_engine 285 flow_manager.Worker._load_engine = self.load_engine
238 super(ProtectionServiceTest, self).tearDown() 286 super(ProtectionServiceTest, self).tearDown()
diff --git a/releasenotes/notes/checkpoint-status-reset-d714b4a04da2f44d.yaml b/releasenotes/notes/checkpoint-status-reset-d714b4a04da2f44d.yaml
new file mode 100644
index 0000000..aee4633
--- /dev/null
+++ b/releasenotes/notes/checkpoint-status-reset-d714b4a04da2f44d.yaml
@@ -0,0 +1,4 @@
1---
2features:
3 - |
4 Added support for checkpoint state reset by admin and owner.