Coverage for manila/tests/api/v2/test_share_replicas.py: 99%
480 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2015 Goutham Pacha Ravi
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16from unittest import mock
18import copy
19import ddt
20from oslo_config import cfg
21from oslo_serialization import jsonutils
22from oslo_utils import strutils
23from webob import exc
25from manila.api import common
26from manila.api.openstack import api_version_request as api_version
27from manila.api.v2 import share_replicas
28from manila.common import constants
29from manila import context
30from manila import exception
31from manila import policy
32from manila import share
33from manila import test
34from manila.tests.api import fakes
35from manila.tests import db_utils
36from manila.tests import fake_share
38CONF = cfg.CONF
39CAST_RULES_READONLY_VERSION = '2.30'
40PRE_GRADUATION_VERSION = '2.55'
41GRADUATION_VERSION = '2.56'
42PROMOTE_QUIESCE_WAIT_VERSION = '2.75'
45@ddt.ddt
46class ShareReplicasApiTest(test.TestCase):
47 """Share Replicas API Test Cases."""
49 def setUp(self):
50 super(ShareReplicasApiTest, self).setUp()
51 self.controller = share_replicas.ShareReplicationController()
52 self.resource_name = self.controller.resource_name
53 self.api_version = share_replicas.MIN_SUPPORTED_API_VERSION
54 self.replicas_req = fakes.HTTPRequest.blank(
55 '/share-replicas', version=self.api_version,
56 experimental=True)
57 self.member_context = context.RequestContext('fake', 'fake')
58 self.replicas_req.environ['manila.context'] = self.member_context
59 self.replicas_req_admin = fakes.HTTPRequest.blank(
60 '/share-replicas', version=self.api_version,
61 experimental=True, use_admin_context=True)
62 self.admin_context = self.replicas_req_admin.environ['manila.context']
63 self.mock_policy_check = self.mock_object(policy, 'check_policy')
64 self.fake_share_network = {
65 'id': 'fake network id',
66 'project_id': 'fake project',
67 'updated_at': None,
68 'name': 'fake name',
69 'description': 'fake description',
70 'security_services': [],
71 'share_network_subnets': [],
72 'security_service_update_support': True,
73 'status': 'active'
74 }
76 def _get_context(self, role):
77 return getattr(self, '%s_context' % role)
79 def _create_replica_get_req(self, **kwargs):
80 if 'status' not in kwargs: 80 ↛ 82line 80 didn't jump to line 82 because the condition on line 80 was always true
81 kwargs['status'] = constants.STATUS_AVAILABLE
82 if 'replica_state' not in kwargs:
83 kwargs['replica_state'] = constants.REPLICA_STATE_IN_SYNC
84 replica = db_utils.create_share_replica(**kwargs)
85 path = '/v2/fake/share-replicas/%s/action' % replica['id']
86 microversion = kwargs.get('microversion', self.api_version)
87 experimental = True
88 if (api_version.APIVersionRequest(microversion) >=
89 api_version.APIVersionRequest(GRADUATION_VERSION)):
90 experimental = False
91 req = fakes.HTTPRequest.blank(path, script_name=path,
92 version=microversion,
93 experimental=experimental)
94 req.method = 'POST'
95 req.headers['content-type'] = 'application/json'
96 req.headers['X-Openstack-Manila-Api-Version'] = microversion
97 req.headers['X-Openstack-Manila-Api-Experimental'] = True
99 return replica, req
101 def _get_fake_replica(
102 self, summary=False, admin=False,
103 microversion=share_replicas.MIN_SUPPORTED_API_VERSION, **values):
104 replica = fake_share.fake_replica(**values)
105 replica['updated_at'] = '2016-02-11T19:57:56.506805'
106 expected_keys = {'id', 'share_id', 'status', 'replica_state'}
107 expected_replica = {key: replica[key] for key in replica if key
108 in expected_keys}
110 if not summary:
111 expected_replica.update({
112 'availability_zone': None,
113 'created_at': None,
114 'share_network_id': replica['share_network_id'],
115 'updated_at': replica['updated_at'],
116 })
118 if admin:
119 expected_replica['share_server_id'] = replica['share_server_id']
120 expected_replica['host'] = replica['host']
122 if (api_version.APIVersionRequest(microversion) >=
123 api_version.APIVersionRequest(CAST_RULES_READONLY_VERSION)
124 and admin):
125 expected_replica['cast_rules_to_readonly'] = False
127 return replica, expected_replica
129 def _get_request(self, microversion, is_admin=False):
130 experimental = (api_version.APIVersionRequest(microversion) <=
131 api_version.APIVersionRequest(GRADUATION_VERSION))
132 req = fakes.HTTPRequest.blank(
133 '/share-replicas', version=microversion,
134 experimental=experimental, use_admin_context=is_admin)
136 return req
138 @ddt.data((False, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION))
139 @ddt.unpack
140 def test_list_replicas_summary(self, is_admin, microversion):
141 fake_replica, expected_replica = self._get_fake_replica(
142 summary=True, admin=is_admin, microversion=microversion)
143 self.mock_object(share_replicas.db, 'share_replicas_get_all',
144 mock.Mock(return_value=[fake_replica]))
146 req = self._get_request(is_admin=is_admin, microversion=microversion)
147 context = req.environ['manila.context']
148 res_dict = self.controller.index(req)
150 self.assertEqual([expected_replica], res_dict['share_replicas'])
151 self.mock_policy_check.assert_called_once_with(
152 context, self.resource_name, 'get_all')
154 def test_list_share_replicas_summary(self):
155 fake_replica, expected_replica = self._get_fake_replica(summary=True)
156 self.mock_object(share_replicas.db, 'share_replicas_get_all_by_share',
157 mock.Mock(return_value=[fake_replica]))
158 req = fakes.HTTPRequest.blank(
159 '/share-replicas?share_id=FAKE_SHARE_ID',
160 version=self.api_version, experimental=True)
161 req_context = req.environ['manila.context']
163 res_dict = self.controller.index(req)
165 self.assertEqual([expected_replica], res_dict['share_replicas'])
166 self.mock_policy_check.assert_called_once_with(
167 req_context, self.resource_name, 'get_all')
169 @ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION))
170 @ddt.unpack
171 def test_list_replicas_detail(self, is_admin, microversion):
172 fake_replica, expected_replica = self._get_fake_replica(
173 admin=is_admin, microversion=microversion)
174 self.mock_object(share_replicas.db, 'share_replicas_get_all',
175 mock.Mock(return_value=[fake_replica]))
177 req = self._get_request(is_admin=is_admin, microversion=microversion)
178 req_context = req.environ['manila.context']
180 res_dict = self.controller.detail(req)
182 self.assertEqual([expected_replica], res_dict['share_replicas'])
183 self.mock_policy_check.assert_called_once_with(
184 req_context, self.resource_name, 'get_all')
186 def test_list_replicas_detail_with_limit(self):
187 fake_replica_1, expected_replica_1 = self._get_fake_replica()
188 fake_replica_2, expected_replica_2 = self._get_fake_replica(
189 id="fake_id2")
190 self.mock_object(
191 share_replicas.db, 'share_replicas_get_all',
192 mock.Mock(return_value=[fake_replica_1, fake_replica_2]))
193 req = fakes.HTTPRequest.blank('/share-replicas?limit=1',
194 version=self.api_version,
195 experimental=True)
196 req_context = req.environ['manila.context']
198 res_dict = self.controller.detail(req)
200 self.assertEqual(1, len(res_dict['share_replicas']))
201 self.assertEqual([expected_replica_1], res_dict['share_replicas'])
202 self.mock_policy_check.assert_called_once_with(
203 req_context, self.resource_name, 'get_all')
205 def test_list_replicas_detail_with_limit_and_offset(self):
206 fake_replica_1, expected_replica_1 = self._get_fake_replica()
207 fake_replica_2, expected_replica_2 = self._get_fake_replica(
208 id="fake_id2")
209 self.mock_object(
210 share_replicas.db, 'share_replicas_get_all',
211 mock.Mock(return_value=[fake_replica_1, fake_replica_2]))
212 req = fakes.HTTPRequest.blank(
213 '/share-replicas/detail?limit=1&offset=1',
214 version=self.api_version, experimental=True)
215 req_context = req.environ['manila.context']
217 res_dict = self.controller.detail(req)
219 self.assertEqual(1, len(res_dict['share_replicas']))
220 self.assertEqual([expected_replica_2], res_dict['share_replicas'])
221 self.mock_policy_check.assert_called_once_with(
222 req_context, self.resource_name, 'get_all')
224 def test_list_share_replicas_detail_invalid_share(self):
225 self.mock_object(share_replicas.db, 'share_replicas_get_all_by_share',
226 mock.Mock(side_effect=exception.NotFound))
227 mock__view_builder_call = self.mock_object(
228 share_replicas.replication_view.ReplicationViewBuilder,
229 'detail_list')
230 req = self.replicas_req
231 req.GET['share_id'] = 'FAKE_SHARE_ID'
233 self.assertRaises(exc.HTTPNotFound,
234 self.controller.detail, req)
235 self.assertFalse(mock__view_builder_call.called)
236 self.mock_policy_check.assert_called_once_with(
237 self.member_context, self.resource_name, 'get_all')
239 @ddt.data(True, False)
240 def test_list_share_replicas_detail(self, is_admin):
241 fake_replica, expected_replica = self._get_fake_replica(admin=is_admin)
242 self.mock_object(share_replicas.db, 'share_replicas_get_all_by_share',
243 mock.Mock(return_value=[fake_replica]))
244 req = fakes.HTTPRequest.blank(
245 '/share-replicas?share_id=FAKE_SHARE_ID',
246 version=self.api_version, experimental=True)
247 req.environ['manila.context'] = (
248 self.member_context if not is_admin else self.admin_context)
249 req_context = req.environ['manila.context']
251 res_dict = self.controller.detail(req)
253 self.assertEqual([expected_replica], res_dict['share_replicas'])
254 self.mock_policy_check.assert_called_once_with(
255 req_context, self.resource_name, 'get_all')
257 def test_list_share_replicas_with_limit(self):
258 fake_replica_1, expected_replica_1 = self._get_fake_replica()
259 fake_replica_2, expected_replica_2 = self._get_fake_replica(
260 id="fake_id2")
261 self.mock_object(
262 share_replicas.db, 'share_replicas_get_all_by_share',
263 mock.Mock(return_value=[fake_replica_1, fake_replica_2]))
264 req = fakes.HTTPRequest.blank(
265 '/share-replicas?share_id=FAKE_SHARE_ID&limit=1',
266 version=self.api_version, experimental=True)
267 req_context = req.environ['manila.context']
269 res_dict = self.controller.detail(req)
271 self.assertEqual(1, len(res_dict['share_replicas']))
272 self.assertEqual([expected_replica_1], res_dict['share_replicas'])
273 self.mock_policy_check.assert_called_once_with(
274 req_context, self.resource_name, 'get_all')
276 def test_list_share_replicas_with_limit_and_offset(self):
277 fake_replica_1, expected_replica_1 = self._get_fake_replica()
278 fake_replica_2, expected_replica_2 = self._get_fake_replica(
279 id="fake_id2")
280 self.mock_object(
281 share_replicas.db, 'share_replicas_get_all_by_share',
282 mock.Mock(return_value=[fake_replica_1, fake_replica_2]))
283 req = fakes.HTTPRequest.blank(
284 '/share-replicas?share_id=FAKE_SHARE_ID&limit=1&offset=1',
285 version=self.api_version, experimental=True)
286 req_context = req.environ['manila.context']
288 res_dict = self.controller.detail(req)
290 self.assertEqual(1, len(res_dict['share_replicas']))
291 self.assertEqual([expected_replica_2], res_dict['share_replicas'])
292 self.mock_policy_check.assert_called_once_with(
293 req_context, self.resource_name, 'get_all')
295 @ddt.data((True, PRE_GRADUATION_VERSION),
296 (False, GRADUATION_VERSION))
297 @ddt.unpack
298 def test_show(self, is_admin, microversion):
299 fake_replica, expected_replica = self._get_fake_replica(
300 admin=is_admin, microversion=microversion)
301 self.mock_object(
302 share_replicas.db, 'share_replica_get',
303 mock.Mock(return_value=fake_replica))
305 req = self._get_request(microversion, is_admin)
306 req_context = req.environ['manila.context']
308 res_dict = self.controller.show(req, fake_replica.get('id'))
310 self.assertEqual(expected_replica, res_dict['share_replica'])
311 self.mock_policy_check.assert_called_once_with(
312 req_context, self.resource_name, 'show')
314 def test_show_no_replica(self):
315 mock__view_builder_call = self.mock_object(
316 share_replicas.replication_view.ReplicationViewBuilder, 'detail')
317 fake_exception = exception.ShareReplicaNotFound(
318 replica_id='FAKE_REPLICA_ID')
319 self.mock_object(share_replicas.db, 'share_replica_get', mock.Mock(
320 side_effect=fake_exception))
322 self.assertRaises(exc.HTTPNotFound,
323 self.controller.show,
324 self.replicas_req,
325 'FAKE_REPLICA_ID')
326 self.assertFalse(mock__view_builder_call.called)
327 self.mock_policy_check.assert_called_once_with(
328 self.member_context, self.resource_name, 'show')
330 def test_create_invalid_body(self):
331 body = {}
332 mock__view_builder_call = self.mock_object(
333 share_replicas.replication_view.ReplicationViewBuilder,
334 'detail_list')
336 self.assertRaises(exc.HTTPUnprocessableEntity,
337 self.controller.create,
338 self.replicas_req, body)
339 self.assertEqual(0, mock__view_builder_call.call_count)
340 self.mock_policy_check.assert_called_once_with(
341 self.member_context, self.resource_name, 'create')
343 def test_create_no_share_id(self):
344 body = {
345 'share_replica': {
346 'share_id': None,
347 'availability_zone': None,
348 }
349 }
350 mock__view_builder_call = self.mock_object(
351 share_replicas.replication_view.ReplicationViewBuilder,
352 'detail_list')
354 self.assertRaises(exc.HTTPBadRequest,
355 self.controller.create,
356 self.replicas_req, body)
357 self.assertFalse(mock__view_builder_call.called)
358 self.mock_policy_check.assert_called_once_with(
359 self.member_context, self.resource_name, 'create')
361 def test_create_invalid_share_id(self):
362 body = {
363 'share_replica': {
364 'share_id': 'FAKE_SHAREID',
365 'availability_zone': 'FAKE_AZ'
366 }
367 }
368 mock__view_builder_call = self.mock_object(
369 share_replicas.replication_view.ReplicationViewBuilder,
370 'detail_list')
371 self.mock_object(share_replicas.db, 'share_get',
372 mock.Mock(side_effect=exception.NotFound))
374 self.assertRaises(exc.HTTPNotFound,
375 self.controller.create,
376 self.replicas_req, body)
377 self.assertFalse(mock__view_builder_call.called)
378 self.mock_policy_check.assert_called_once_with(
379 self.member_context, self.resource_name, 'create')
381 def test_create_has_been_soft_deleted(self):
382 share_ref = fake_share.fake_share(is_soft_deleted=True)
383 body = {
384 'share_replica': {
385 'share_id': 'FAKE_SHAREID',
386 'availability_zone': 'FAKE_AZ'
387 }
388 }
389 mock__view_builder_call = self.mock_object(
390 share_replicas.replication_view.ReplicationViewBuilder,
391 'detail_list')
392 self.mock_object(share_replicas.db, 'share_get',
393 mock.Mock(return_value=share_ref))
395 self.assertRaises(exc.HTTPForbidden,
396 self.controller.create,
397 self.replicas_req, body)
398 self.assertFalse(mock__view_builder_call.called)
399 self.mock_policy_check.assert_called_once_with(
400 self.member_context, self.resource_name, 'create')
402 def test_create_has_been_encrypted(self):
403 share_ref = fake_share.fake_share(encryption_key_ref='fake')
404 body = {
405 'share_replica': {
406 'share_id': 'FAKE_SHAREID',
407 'availability_zone': 'FAKE_AZ'
408 }
409 }
410 mock__view_builder_call = self.mock_object(
411 share_replicas.replication_view.ReplicationViewBuilder,
412 'detail_list')
413 self.mock_object(share_replicas.db, 'share_get',
414 mock.Mock(return_value=share_ref))
416 self.assertRaises(exc.HTTPForbidden,
417 self.controller.create,
418 self.replicas_req, body)
419 self.assertFalse(mock__view_builder_call.called)
420 self.mock_policy_check.assert_called_once_with(
421 self.member_context, self.resource_name, 'create')
423 def test_create_invalid_network_id(self):
424 fake_replica, _ = self._get_fake_replica(
425 replication_type='writable')
426 req = self._get_request("2.72", False)
427 req_context = req.environ['manila.context']
429 body = {
430 'share_replica': {
431 'share_id': 'FAKE_SHAREID',
432 'availability_zone': 'FAKE_AZ',
433 'share_network_id': 'FAKE_NETID'
434 }
435 }
436 mock__view_builder_call = self.mock_object(
437 share_replicas.replication_view.ReplicationViewBuilder,
438 'detail_list')
439 self.mock_object(share_replicas.db, 'share_get',
440 mock.Mock(return_value=fake_replica))
441 self.mock_object(share_replicas.db, 'share_network_get',
442 mock.Mock(side_effect=exception.ShareNetworkNotFound(
443 share_network_id='FAKE_NETID')))
445 self.assertRaises(exc.HTTPBadRequest,
446 self.controller.create,
447 req, body)
448 self.assertFalse(mock__view_builder_call.called)
449 self.mock_policy_check.assert_called_once_with(
450 req_context, self.resource_name, 'create')
452 @ddt.data(exception.AvailabilityZoneNotFound,
453 exception.ReplicationException, exception.ShareBusyException)
454 def test_create_exception_path(self, exception_type):
455 fake_replica, _ = self._get_fake_replica(
456 replication_type='writable')
457 mock__view_builder_call = self.mock_object(
458 share_replicas.replication_view.ReplicationViewBuilder,
459 'detail_list')
460 share_network = db_utils.create_share_network()
461 body = {
462 'share_replica': {
463 'share_id': 'FAKE_SHAREID',
464 'availability_zone': 'FAKE_AZ'
465 }
466 }
467 exc_args = {'id': 'xyz', 'reason': 'abc'}
468 self.mock_object(share_replicas.db, 'share_get',
469 mock.Mock(return_value=fake_replica))
470 self.mock_object(share.API, 'create_share_replica',
471 mock.Mock(side_effect=exception_type(**exc_args)))
472 self.mock_object(share_replicas.db, 'share_network_get',
473 mock.Mock(return_value=share_network))
474 self.mock_object(common, 'check_share_network_is_active',
475 mock.Mock(return_value=True))
477 self.assertRaises(exc.HTTPBadRequest,
478 self.controller.create,
479 self.replicas_req, body)
480 self.assertFalse(mock__view_builder_call.called)
481 self.mock_policy_check.assert_called_once_with(
482 self.member_context, self.resource_name, 'create')
483 share_replicas.db.share_network_get.assert_called_once_with(
484 self.member_context, fake_replica['share_network_id'])
485 common.check_share_network_is_active.assert_called_once_with(
486 share_network)
488 @ddt.data((True, PRE_GRADUATION_VERSION), (False, GRADUATION_VERSION),
489 (False, "2.72"))
490 @ddt.unpack
491 def test_create(self, is_admin, microversion):
492 fake_replica, expected_replica = self._get_fake_replica(
493 replication_type='writable', admin=is_admin,
494 microversion=microversion)
495 body = {
496 'share_replica': {
497 'share_id': 'FAKE_SHAREID',
498 'availability_zone': 'FAKE_AZ'
499 }
500 }
501 if self.is_microversion_ge(microversion, '2.72'):
502 body["share_replica"].update({"share_network_id": 'FAKE_NETID'})
503 share_network = {'id': 'FAKE_NETID'}
504 else:
505 share_network = db_utils.create_share_network()
507 self.mock_object(share_replicas.db, 'share_get',
508 mock.Mock(return_value=fake_replica))
509 self.mock_object(share.API, 'create_share_replica',
510 mock.Mock(return_value=fake_replica))
511 self.mock_object(share_replicas.db,
512 'share_replicas_get_available_active_replica',
513 mock.Mock(return_value=[{'id': 'active1'}]))
514 self.mock_object(share_replicas.db, 'share_network_get',
515 mock.Mock(return_value=share_network))
516 self.mock_object(common, 'check_share_network_is_active',
517 mock.Mock(return_value=True))
519 req = self._get_request(microversion, is_admin)
520 req_context = req.environ['manila.context']
522 res_dict = self.controller.create(req, body)
524 self.assertEqual(expected_replica, res_dict['share_replica'])
525 self.mock_policy_check.assert_called_once_with(
526 req_context, self.resource_name, 'create')
527 if self.is_microversion_ge(microversion, '2.72'):
528 share_replicas.db.share_network_get.assert_called_once_with(
529 req_context, 'FAKE_NETID')
530 else:
531 share_replicas.db.share_network_get.assert_called_once_with(
532 req_context, fake_replica['share_network_id'])
533 common.check_share_network_is_active.assert_called_once_with(
534 share_network)
536 def test_delete_invalid_replica(self):
537 fake_exception = exception.ShareReplicaNotFound(
538 replica_id='FAKE_REPLICA_ID')
539 self.mock_object(share_replicas.db, 'share_replica_get',
540 mock.Mock(side_effect=fake_exception))
541 mock_delete_replica_call = self.mock_object(
542 share.API, 'delete_share_replica')
544 self.assertRaises(
545 exc.HTTPNotFound, self.controller.delete,
546 self.replicas_req, 'FAKE_REPLICA_ID')
547 self.assertFalse(mock_delete_replica_call.called)
548 self.mock_policy_check.assert_called_once_with(
549 self.member_context, self.resource_name, 'delete')
551 def test_delete_exception(self):
552 fake_replica_1 = self._get_fake_replica(
553 share_id='FAKE_SHARE_ID',
554 replica_state=constants.REPLICA_STATE_ACTIVE)[0]
555 fake_replica_2 = self._get_fake_replica(
556 share_id='FAKE_SHARE_ID',
557 replica_state=constants.REPLICA_STATE_ACTIVE)[0]
558 exception_type = exception.ReplicationException(reason='xyz')
559 self.mock_object(share_replicas.db, 'share_replica_get',
560 mock.Mock(return_value=fake_replica_1))
561 self.mock_object(
562 share_replicas.db, 'share_replicas_get_all_by_share',
563 mock.Mock(return_value=[fake_replica_1, fake_replica_2]))
564 self.mock_object(share.API, 'delete_share_replica',
565 mock.Mock(side_effect=exception_type))
567 self.assertRaises(exc.HTTPBadRequest, self.controller.delete,
568 self.replicas_req, 'FAKE_REPLICA_ID')
569 self.mock_policy_check.assert_called_once_with(
570 self.member_context, self.resource_name, 'delete')
572 @ddt.data(PRE_GRADUATION_VERSION, GRADUATION_VERSION)
573 def test_delete(self, microversion):
574 fake_replica = self._get_fake_replica(
575 share_id='FAKE_SHARE_ID',
576 replica_state=constants.REPLICA_STATE_ACTIVE)[0]
577 req = self._get_request(microversion=microversion)
578 context = req.environ['manila.context']
579 self.mock_object(share_replicas.db, 'share_replica_get',
580 mock.Mock(return_value=fake_replica))
581 self.mock_object(share.API, 'delete_share_replica')
583 resp = self.controller.delete(
584 req, 'FAKE_REPLICA_ID')
586 self.assertEqual(202, resp.status_code)
587 self.mock_policy_check.assert_called_once_with(
588 context, self.resource_name, 'delete')
590 def test_promote_invalid_replica_id(self):
591 body = {'promote': None}
592 fake_exception = exception.ShareReplicaNotFound(
593 replica_id='FAKE_REPLICA_ID')
594 self.mock_object(share_replicas.db, 'share_replica_get',
595 mock.Mock(side_effect=fake_exception))
597 self.assertRaises(exc.HTTPNotFound,
598 self.controller.promote,
599 self.replicas_req,
600 'FAKE_REPLICA_ID', body)
601 self.mock_policy_check.assert_called_once_with(
602 self.member_context, self.resource_name, 'promote')
604 def test_promote_already_active(self):
605 body = {'promote': None}
606 replica, expected_replica = self._get_fake_replica(
607 replica_state=constants.REPLICA_STATE_ACTIVE)
608 self.mock_object(share_replicas.db, 'share_replica_get',
609 mock.Mock(return_value=replica))
610 self.mock_object(share_replicas.db, 'share_network_get',
611 mock.Mock(return_value=self.fake_share_network))
612 mock_api_promote_replica_call = self.mock_object(
613 share.API, 'promote_share_replica')
615 resp = self.controller.promote(self.replicas_req, replica['id'], body)
617 self.assertEqual(200, resp.status_code)
618 self.assertFalse(mock_api_promote_replica_call.called)
619 self.mock_policy_check.assert_called_once_with(
620 self.member_context, self.resource_name, 'promote')
622 def test_promote_replication_exception(self):
623 body = {'promote': None}
624 replica, expected_replica = self._get_fake_replica(
625 replica_state=constants.REPLICA_STATE_IN_SYNC)
626 exception_type = exception.ReplicationException(reason='xyz')
627 self.mock_object(share_replicas.db, 'share_replica_get',
628 mock.Mock(return_value=replica))
629 self.mock_object(share_replicas.db, 'share_network_get',
630 mock.Mock(return_value=self.fake_share_network))
631 mock_api_promote_replica_call = self.mock_object(
632 share.API, 'promote_share_replica',
633 mock.Mock(side_effect=exception_type))
635 self.assertRaises(exc.HTTPBadRequest,
636 self.controller.promote,
637 self.replicas_req,
638 replica['id'],
639 body)
640 self.assertTrue(mock_api_promote_replica_call.called)
641 self.mock_policy_check.assert_called_once_with(
642 self.member_context, self.resource_name, 'promote')
644 def test_promote_share_network_not_active(self):
645 body = {'promote': None}
646 replica, expected_replica = self._get_fake_replica(
647 replica_state=constants.REPLICA_STATE_IN_SYNC)
648 fake_share_network = copy.deepcopy(self.fake_share_network)
649 fake_share_network['status'] = constants.STATUS_NETWORK_CHANGE
650 self.mock_object(share_replicas.db, 'share_replica_get',
651 mock.Mock(return_value=replica))
652 self.mock_object(share_replicas.db, 'share_network_get',
653 mock.Mock(return_value=fake_share_network))
655 self.assertRaises(exc.HTTPBadRequest,
656 self.controller.promote,
657 self.replicas_req,
658 replica['id'],
659 body)
660 self.mock_policy_check.assert_called_once_with(
661 self.member_context, self.resource_name, 'promote')
663 def test_promote_admin_required_exception(self):
664 body = {'promote': None}
665 replica, expected_replica = self._get_fake_replica(
666 replica_state=constants.REPLICA_STATE_IN_SYNC)
667 self.mock_object(share_replicas.db, 'share_replica_get',
668 mock.Mock(return_value=replica))
669 self.mock_object(share_replicas.db, 'share_network_get',
670 mock.Mock(return_value=self.fake_share_network))
671 mock_api_promote_replica_call = self.mock_object(
672 share.API, 'promote_share_replica',
673 mock.Mock(side_effect=exception.AdminRequired))
675 self.assertRaises(exc.HTTPForbidden,
676 self.controller.promote,
677 self.replicas_req,
678 replica['id'],
679 body)
680 self.assertTrue(mock_api_promote_replica_call.called)
681 self.mock_policy_check.assert_called_once_with(
682 self.member_context, self.resource_name, 'promote')
684 @ddt.data(PRE_GRADUATION_VERSION, GRADUATION_VERSION)
685 def test_promote(self, microversion):
686 body = {'promote': None}
687 replica, expected_replica = self._get_fake_replica(
688 replica_state=constants.REPLICA_STATE_IN_SYNC,
689 microversion=microversion)
690 self.mock_object(share_replicas.db, 'share_replica_get',
691 mock.Mock(return_value=replica))
692 self.mock_object(share_replicas.db, 'share_network_get',
693 mock.Mock(return_value=self.fake_share_network))
694 mock_api_promote_replica_call = self.mock_object(
695 share.API, 'promote_share_replica',
696 mock.Mock(return_value=replica))
697 req = self._get_request(microversion=microversion)
698 context = req.environ['manila.context']
699 resp = self.controller.promote(req, replica['id'], body)
701 self.assertEqual(expected_replica, resp['share_replica'])
702 self.assertTrue(mock_api_promote_replica_call.called)
703 self.mock_policy_check.assert_called_once_with(
704 context, self.resource_name, 'promote')
706 @ddt.data(('2.74', None),
707 (PROMOTE_QUIESCE_WAIT_VERSION, None),
708 (PROMOTE_QUIESCE_WAIT_VERSION, 10),
709 (PROMOTE_QUIESCE_WAIT_VERSION, 'foobar'),
710 )
711 @ddt.unpack
712 def test_promote_quiesce_wait_time(self, microversion, time):
713 body = {'promote': {'quiesce_wait_time': time}}
714 replica, expected_replica = self._get_fake_replica(
715 replica_state=constants.REPLICA_STATE_IN_SYNC,
716 microversion=microversion)
717 self.mock_object(share_replicas.db, 'share_replica_get',
718 mock.Mock(return_value=replica))
719 self.mock_object(share_replicas.db, 'share_network_get',
720 mock.Mock(return_value=self.fake_share_network))
722 req = self._get_request(microversion=microversion)
723 allow_quiesce_wait_time = False
724 if (api_version.APIVersionRequest(microversion) >=
725 api_version.APIVersionRequest(PROMOTE_QUIESCE_WAIT_VERSION)):
726 allow_quiesce_wait_time = True
727 if time and allow_quiesce_wait_time:
728 if strutils.is_int_like(time):
729 mock_api_promote_replica_call = self.mock_object(
730 share.API, 'promote_share_replica',
731 mock.Mock(return_value=replica))
732 resp = self.controller.promote(req, replica['id'], body)
733 self.assertEqual(expected_replica, resp['share_replica'])
734 self.assertTrue(mock_api_promote_replica_call.called)
735 else:
736 self.assertRaises(exc.HTTPBadRequest,
737 self.controller.promote,
738 req,
739 replica['id'],
740 body)
742 @ddt.data('index', 'detail', '_show', '_create', '_delete_share_replica',
743 '_promote', 'reset_replica_state', 'reset_status', '_resync')
744 def test_policy_not_authorized(self, method_name):
746 method = getattr(self.controller, method_name)
747 arguments = {
748 'id': 'FAKE_REPLICA_ID',
749 'body': {'FAKE_KEY': 'FAKE_VAL'},
750 }
751 if method_name in ('index', 'detail'):
752 arguments.clear()
754 noauthexc = exception.PolicyNotAuthorized(action=method_name)
756 with mock.patch.object(
757 policy, 'check_policy', mock.Mock(side_effect=noauthexc)):
759 self.assertRaises(
760 exc.HTTPForbidden, method, self.replicas_req, **arguments)
762 @ddt.data('index', 'detail', 'show', 'create', 'delete', 'promote',
763 'reset_replica_state', 'reset_status', 'resync')
764 def test_upsupported_microversion(self, method_name):
766 unsupported_microversions = ('1.0', '2.2', '2.10')
767 method = getattr(self.controller, method_name)
768 arguments = {
769 'id': 'FAKE_REPLICA_ID',
770 'body': {'FAKE_KEY': 'FAKE_VAL'},
771 }
772 if method_name in ('index', 'detail'):
773 arguments.clear()
775 for microversion in unsupported_microversions:
776 req = fakes.HTTPRequest.blank(
777 '/share-replicas', version=microversion,
778 experimental=True)
779 self.assertRaises(exception.VersionNotFoundForAPIMethod,
780 method, req, **arguments)
782 def _reset_status(self, context, replica, req,
783 valid_code=202, status_attr='status',
784 valid_status=None, body=None):
786 if status_attr == 'status':
787 action_name = 'reset_status'
788 body = body or {action_name: {'status': constants.STATUS_ERROR}}
789 else:
790 action_name = 'reset_replica_state'
791 body = body or {
792 action_name: {'replica_state': constants.STATUS_ERROR},
793 }
795 req.body = jsonutils.dumps(body).encode("utf-8")
796 req.environ['manila.context'] = context
798 with mock.patch.object(
799 policy, 'check_policy', fakes.mock_fake_admin_check):
800 resp = req.get_response(fakes.app())
802 # validate response code and model status
803 self.assertEqual(valid_code, resp.status_int)
805 actual_replica = share_replicas.db.share_replica_get(
806 context, replica['id'])
807 self.assertEqual(valid_status, actual_replica[status_attr])
809 @ddt.data(*fakes.fixture_reset_replica_status_with_different_roles)
810 @ddt.unpack
811 def test_reset_status_with_different_roles(self, role, valid_code,
812 valid_status, microversion):
813 context = self._get_context(role)
814 replica, action_req = self._create_replica_get_req(
815 microversion=microversion)
817 self._reset_status(context, replica, action_req,
818 valid_code=valid_code, status_attr='status',
819 valid_status=valid_status)
821 @ddt.data(
822 {'os-reset_status': {'x-status': 'bad'}},
823 {'os-reset_status': {'status': constants.STATUS_AVAILABLE}},
824 {'reset_status': {'x-status': 'bad'}},
825 {'reset_status': {'status': 'invalid'}},
826 )
827 def test_reset_status_invalid_body(self, body):
828 replica, action_req = self._create_replica_get_req()
830 self._reset_status(self.admin_context, replica, action_req,
831 valid_code=400, status_attr='status',
832 valid_status=constants.STATUS_AVAILABLE, body=body)
834 @ddt.data(*fakes.fixture_reset_replica_state_with_different_roles)
835 @ddt.unpack
836 def test_reset_replica_state_with_different_roles(
837 self, role, valid_code, valid_status, microversion):
838 context = self._get_context(role)
839 replica, action_req = self._create_replica_get_req(
840 microversion=microversion)
841 body = {'reset_replica_state': {'replica_state': valid_status}}
843 self._reset_status(context, replica, action_req,
844 valid_code=valid_code, status_attr='replica_state',
845 valid_status=valid_status, body=body)
847 def test_reset_replica_with_active_state(self):
848 body = {
849 'reset_replica_state': {
850 'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC,
851 }
852 }
854 replica, action_req = self._create_replica_get_req(
855 replica_state=constants.REPLICA_STATE_ACTIVE)
857 self._reset_status(self.admin_context, replica, action_req,
858 status_attr='replica_state',
859 valid_code=400,
860 valid_status=constants.REPLICA_STATE_ACTIVE,
861 body=body)
863 @ddt.data(
864 {'os-reset_replica_state': {'x-replica_state': 'bad'}},
865 {'os-reset_replica_state': {'replica_state': constants.STATUS_ERROR}},
866 {'reset_replica_state': {'x-replica_state': 'bad'}},
867 {'reset_replica_state': {'replica_state': constants.STATUS_AVAILABLE}},
868 )
869 def test_reset_replica_state_invalid_body(self, body):
870 replica, action_req = self._create_replica_get_req()
872 self._reset_status(self.admin_context, replica, action_req,
873 valid_code=400, status_attr='status',
874 valid_status=constants.STATUS_AVAILABLE, body=body)
876 def _force_delete(self, context, req, valid_code=202):
877 body = {'force_delete': {}}
878 req.environ['manila.context'] = context
879 req.body = jsonutils.dumps(body).encode("utf-8")
881 with mock.patch.object(
882 policy, 'check_policy', fakes.mock_fake_admin_check):
883 resp = req.get_response(fakes.app())
885 # validate response
886 self.assertEqual(valid_code, resp.status_int)
888 @ddt.data(*fakes.fixture_force_delete_with_different_roles)
889 @ddt.unpack
890 def test_force_delete_replica_with_different_roles(self, role, resp_code,
891 version):
892 replica, req = self._create_replica_get_req()
893 context = self._get_context(role)
895 self._force_delete(context, req, valid_code=resp_code)
897 @ddt.data((PRE_GRADUATION_VERSION, 202),
898 (GRADUATION_VERSION, 202))
899 @ddt.unpack
900 def test_force_delete_replica(self, microversion, resp_code):
901 replica, req = self._create_replica_get_req(microversion=microversion)
902 context = self.admin_context
904 self._force_delete(context, req, valid_code=resp_code)
906 def test_force_delete_missing_replica(self):
907 replica, req = self._create_replica_get_req()
908 share_replicas.db.share_replica_delete(
909 self.admin_context, replica['id'], need_to_update_usages=False)
911 self._force_delete(self.admin_context, req, valid_code=404)
913 def test_resync_replica_not_found(self):
915 replica, req = self._create_replica_get_req()
916 share_replicas.db.share_replica_delete(
917 self.admin_context, replica['id'], need_to_update_usages=False)
918 share_api_call = self.mock_object(self.controller.share_api,
919 'update_share_replica')
920 body = {'resync': {}}
921 req.body = jsonutils.dumps(body).encode("utf-8")
922 req.environ['manila.context'] = self.admin_context
924 with mock.patch.object(
925 policy, 'check_policy', fakes.mock_fake_admin_check):
926 resp = req.get_response(fakes.app())
928 self.assertEqual(404, resp.status_int)
929 self.assertFalse(share_api_call.called)
931 def test_resync_API_exception(self):
933 replica, req = self._create_replica_get_req(
934 replica_state=constants.REPLICA_STATE_OUT_OF_SYNC)
935 self.mock_object(share_replicas.db, 'share_replica_get',
936 mock.Mock(return_value=replica))
937 share_api_call = self.mock_object(
938 share.API, 'update_share_replica', mock.Mock(
939 side_effect=exception.InvalidHost(reason='')))
941 body = {'resync': None}
942 req.body = jsonutils.dumps(body).encode("utf-8")
943 req.environ['manila.context'] = self.admin_context
945 with mock.patch.object(
946 policy, 'check_policy', fakes.mock_fake_admin_check):
947 resp = req.get_response(fakes.app())
949 self.assertEqual(400, resp.status_int)
950 share_api_call.assert_called_once_with(self.admin_context, replica)
952 @ddt.data((constants.REPLICA_STATE_ACTIVE, PRE_GRADUATION_VERSION),
953 (constants.REPLICA_STATE_IN_SYNC, PRE_GRADUATION_VERSION),
954 (constants.REPLICA_STATE_OUT_OF_SYNC, GRADUATION_VERSION),
955 (constants.STATUS_ERROR, GRADUATION_VERSION))
956 @ddt.unpack
957 def test_resync(self, replica_state, microversion):
959 replica, req = self._create_replica_get_req(
960 replica_state=replica_state, host='skywalker@jedi#temple',
961 microversion=microversion)
962 share_api_call = self.mock_object(
963 share.API, 'update_share_replica', mock.Mock(return_value=None))
964 body = {'resync': {}}
965 req.body = jsonutils.dumps(body).encode("utf-8")
966 req.environ['manila.context'] = self.admin_context
968 with mock.patch.object(
969 policy, 'check_policy', fakes.mock_fake_admin_check):
970 resp = req.get_response(fakes.app())
972 if replica_state == constants.REPLICA_STATE_ACTIVE:
973 self.assertEqual(200, resp.status_int)
974 self.assertFalse(share_api_call.called)
975 else:
976 self.assertEqual(202, resp.status_int)
977 self.assertTrue(share_api_call.called)