Browse Source

Merge "Add support to reset checkpoint state"

Zuul 4 months ago
parent
commit
86ec524b83

+ 19
- 0
karbor/api/schemas/checkpoints.py View File

@@ -35,3 +35,22 @@ create = {
35 35
     'required': ['checkpoint'],
36 36
     'additionalProperties': False,
37 37
 }
38
+
39
+update = {
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
+}

+ 39
- 0
karbor/api/v1/providers.py View File

@@ -476,6 +476,45 @@ class ProvidersController(wsgi.Controller):
476 476
         LOG.info("Delete checkpoint request issued successfully.")
477 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
 
480 519
 def create_resource():
481 520
     return wsgi.Resource(ProvidersController())

+ 6
- 0
karbor/api/v1/router.py View File

@@ -96,6 +96,12 @@ class APIRouter(base_wsgi.Router):
96 96
                        controller=providers_resources,
97 97
                        action='checkpoints_delete',
98 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 105
         mapper.resource("trigger", "triggers",
100 106
                         controller=trigger_resources,
101 107
                         collection={},

+ 4
- 0
karbor/exception.py View File

@@ -390,6 +390,10 @@ class CheckpointNotBeDeleted(KarborException):
390 390
     message = _("The checkpoint %(checkpoint_id)s can not be deleted.")
391 391
 
392 392
 
393
+class CheckpointNotBeReset(KarborException):
394
+    message = _("The checkpoint %(checkpoint_id)s can not be reset.")
395
+
396
+
393 397
 class GetProtectionNetworkSubResourceFailed(KarborException):
394 398
     message = _("Get protection network sub-resources of type %(type)s failed:"
395 399
                 " %(reason)s")

+ 12
- 0
karbor/policies/providers.py View File

@@ -24,6 +24,7 @@ CHECKPOINT_GET_POLICY = 'provider:checkpoint_get'
24 24
 CHECKPOINT_GET_ALL_POLICY = 'provider:checkpoint_get_all'
25 25
 CHECKPOINT_CREATE_POLICY = 'provider:checkpoint_create'
26 26
 CHECKPOINT_DELETE_POLICY = 'provider:checkpoint_delete'
27
+CHECKPOINT_UPDATE_POLICY = 'provider:checkpoint_update'
27 28
 
28 29
 
29 30
 providers_policies = [
@@ -87,6 +88,17 @@ providers_policies = [
87 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
 

+ 8
- 0
karbor/services/protection/api.py View File

@@ -44,6 +44,14 @@ class API(base.Base):
44 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 55
     def show_checkpoint(self, context, provider_id, checkpoint_id):
48 56
         return self.protection_rpcapi.show_checkpoint(
49 57
             context,

+ 24
- 0
karbor/services/protection/manager.py View File

@@ -363,6 +363,30 @@ class ProtectionManager(manager.Manager):
363 363
             ))
364 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 390
     def start(self, plan):
367 391
         # TODO(wangliuan)
368 392
         pass

+ 9
- 0
karbor/services/protection/rpcapi.py View File

@@ -82,6 +82,15 @@ class ProtectionAPI(object):
82 82
             provider_id=provider_id,
83 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 94
     def show_checkpoint(self, ctxt, provider_id, checkpoint_id):
86 95
         cctxt = self.client.prepare(version='1.0')
87 96
         return cctxt.call(

+ 96
- 0
karbor/tests/unit/api/v1/test_providers.py View File

@@ -18,6 +18,7 @@ from webob import exc
18 18
 
19 19
 from karbor.api.v1 import providers
20 20
 from karbor import context
21
+from karbor import exception
21 22
 from karbor.tests import base
22 23
 from karbor.tests.unit.api import fakes
23 24
 
@@ -138,3 +139,98 @@ class ProvidersApiTest(base.TestCase):
138 139
             body=body)
139 140
         self.assertTrue(mock_plan_create.called)
140 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)

+ 48
- 0
karbor/tests/unit/protection/test_manager.py View File

@@ -233,6 +233,54 @@ class ProtectionServiceTest(base.TestCase):
233 233
                           'provider1',
234 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 284
     def tearDown(self):
237 285
         flow_manager.Worker._load_engine = self.load_engine
238 286
         super(ProtectionServiceTest, self).tearDown()

+ 4
- 0
releasenotes/notes/checkpoint-status-reset-d714b4a04da2f44d.yaml View File

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

Loading…
Cancel
Save