Coverage for manila/tests/api/v2/test_share_backups.py: 95%
268 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# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
13import ddt
14from oslo_config import cfg
15from unittest import mock
16from webob import exc
18from manila.api.openstack import api_version_request as api_version
19from manila.api.v2 import share_backups
20from manila.common import constants
21from manila import context
22from manila import exception
23from manila import policy
24from manila import share
25from manila import test
26from manila.tests.api import fakes
27from manila.tests import db_utils
28from manila.tests import fake_share
30CONF = cfg.CONF
33@ddt.ddt
34class ShareBackupsApiTest(test.TestCase):
35 """Share backups API Test Cases."""
36 def setUp(self):
37 super(ShareBackupsApiTest, self).setUp()
38 self.controller = share_backups.ShareBackupController()
39 self.resource_name = self.controller.resource_name
40 self.api_version = share_backups.MIN_SUPPORTED_API_VERSION
41 self.backups_req = fakes.HTTPRequest.blank(
42 '/share-backups', version=self.api_version,
43 experimental=True)
44 self.member_context = context.RequestContext('fake', 'fake')
45 self.backups_req.environ['manila.context'] = self.member_context
46 self.backups_req_admin = fakes.HTTPRequest.blank(
47 '/share-backups', version=self.api_version,
48 experimental=True, use_admin_context=True)
49 self.admin_context = self.backups_req_admin.environ['manila.context']
50 self.mock_policy_check = self.mock_object(policy, 'check_policy')
52 def _get_context(self, role):
53 return getattr(self, '%s_context' % role)
55 def _create_backup_get_req(self, **kwargs):
56 if 'status' not in kwargs:
57 kwargs['status'] = constants.STATUS_AVAILABLE
58 backup = db_utils.create_share_backup(**kwargs)
59 req = fakes.HTTPRequest.blank(
60 '/v2/fake/share-backups/%s/action' % backup['id'],
61 version=self.api_version)
62 req.method = 'POST'
63 req.headers['content-type'] = 'application/json'
64 req.headers['X-Openstack-Manila-Api-Version'] = self.api_version
65 req.headers['X-Openstack-Manila-Api-Experimental'] = True
67 return backup, req
69 def _get_fake_backup(self, admin=False, summary=False,
70 apiversion=share_backups.MIN_SUPPORTED_API_VERSION,
71 **values):
72 backup = fake_share.fake_backup(**values)
73 backup['updated_at'] = '2016-06-12T19:57:56.506805'
74 expected_keys = {'id', 'share_id', 'status'}
75 expected_backup = {key: backup[key] for key in backup if key
76 in expected_keys}
77 expected_backup.update({'name': backup.get('display_name')})
79 if not summary:
80 expected_backup.update({
81 'id': backup.get('id'),
82 'share_id': backup.get('share_id'),
83 'status': backup.get('status'),
84 'description': backup.get('display_description'),
85 'size': backup.get('size'),
86 'created_at': backup.get('created_at'),
87 'updated_at': backup.get('updated_at'),
88 'availability_zone': backup.get('availability_zone'),
89 'progress': backup.get('progress'),
90 'restore_progress': backup.get('restore_progress'),
91 })
92 if self.is_microversion_ge(apiversion, '2.85'):
93 expected_backup.update({
94 'backup_type': backup.get('backup_type'),
95 })
97 if admin:
98 expected_backup.update({
99 'host': backup.get('host'),
100 'topic': backup.get('topic'),
101 })
103 return backup, expected_backup
105 def test_list_backups_summary(self):
106 fake_backup, expected_backup = self._get_fake_backup(summary=True)
107 self.mock_object(share_backups.db, 'share_backups_get_all',
108 mock.Mock(return_value=[fake_backup]))
110 res_dict = self.controller.index(self.backups_req)
111 self.assertEqual([expected_backup], res_dict['share_backups'])
112 self.mock_policy_check.assert_called_once_with(
113 self.member_context, self.resource_name, 'get_all')
115 def test_list_backups_summary_with_share_id(self):
116 fake_backup, expected_backup = self._get_fake_backup(summary=True)
117 self.mock_object(share.API, 'get',
118 mock.Mock(return_value={'id': 'FAKE_SHAREID'}))
119 self.mock_object(share_backups.db, 'share_backups_get_all',
120 mock.Mock(return_value=[fake_backup]))
121 req = fakes.HTTPRequest.blank(
122 '/share-backups?share_id=FAKE_SHARE_ID',
123 version=self.api_version, experimental=True)
124 req_context = req.environ['manila.context']
126 res_dict = self.controller.index(req)
128 self.assertEqual([expected_backup], res_dict['share_backups'])
129 self.mock_policy_check.assert_called_once_with(
130 req_context, self.resource_name, 'get_all')
132 @ddt.data(True, False)
133 def test_list_backups_detail(self, is_admin):
134 fake_backup, expected_backup = self._get_fake_backup(admin=is_admin)
135 self.mock_object(share_backups.db, 'share_backups_get_all',
136 mock.Mock(return_value=[fake_backup]))
138 req = self.backups_req if not is_admin else self.backups_req_admin
139 req_context = req.environ['manila.context']
141 res_dict = self.controller.detail(req)
143 self.assertEqual([expected_backup], res_dict['share_backups'])
144 self.mock_policy_check.assert_called_once_with(
145 req_context, self.resource_name, 'get_all')
147 def test_list_share_backups_detail_with_limit(self):
148 fake_backup_1, expected_backup_1 = self._get_fake_backup()
149 fake_backup_2, expected_backup_2 = self._get_fake_backup(
150 id="fake_id2")
151 self.mock_object(
152 share_backups.db, 'share_backups_get_all',
153 mock.Mock(return_value=[fake_backup_1]))
154 req = fakes.HTTPRequest.blank('/share-backups?limit=1',
155 version=self.api_version,
156 experimental=True)
157 req_context = req.environ['manila.context']
159 res_dict = self.controller.detail(req)
161 self.assertEqual(1, len(res_dict['share_backups']))
162 self.assertEqual([expected_backup_1], res_dict['share_backups'])
163 self.mock_policy_check.assert_called_once_with(
164 req_context, self.resource_name, 'get_all')
166 def test_list_share_backups_detail_with_limit_and_offset(self):
167 fake_backup_1, expected_backup_1 = self._get_fake_backup()
168 fake_backup_2, expected_backup_2 = self._get_fake_backup(
169 id="fake_id2")
170 self.mock_object(
171 share_backups.db, 'share_backups_get_all',
172 mock.Mock(return_value=[fake_backup_2]))
173 req = fakes.HTTPRequest.blank(
174 '/share-backups/detail?limit=1&offset=1',
175 version=self.api_version, experimental=True)
176 req_context = req.environ['manila.context']
178 res_dict = self.controller.detail(req)
180 self.assertEqual(1, len(res_dict['share_backups']))
181 self.assertEqual([expected_backup_2], res_dict['share_backups'])
182 self.mock_policy_check.assert_called_once_with(
183 req_context, self.resource_name, 'get_all')
185 def test_list_share_backups_detail_invalid_share(self):
186 self.mock_object(share_backups.db, 'share_backups_get_all',
187 mock.Mock(side_effect=exception.NotFound))
188 mock__view_builder_call = self.mock_object(
189 share_backups.backup_view.BackupViewBuilder,
190 'detail_list')
191 req = self.backups_req
192 req.GET['share_id'] = 'FAKE_SHARE_ID'
194 self.assertRaises(exc.HTTPBadRequest,
195 self.controller.detail, req)
196 self.assertFalse(mock__view_builder_call.called)
197 self.mock_policy_check.assert_called_once_with(
198 self.member_context, self.resource_name, 'get_all')
200 @ddt.data(share_backups.MIN_SUPPORTED_API_VERSION,
201 api_version._MAX_API_VERSION)
202 def test_list_share_backups_detail(self, apiversion):
203 fake_backup, expected_backup = self._get_fake_backup(
204 apiversion=apiversion,
205 )
206 self.mock_object(share.API, 'get',
207 mock.Mock(return_value={'id': 'FAKE_SHAREID'}))
208 self.mock_object(share_backups.db, 'share_backups_get_all',
209 mock.Mock(return_value=[fake_backup]))
210 req = fakes.HTTPRequest.blank(
211 '/share-backups?share_id=FAKE_SHARE_ID',
212 version=apiversion, experimental=True)
213 req.environ['manila.context'] = (
214 self.member_context)
215 req_context = req.environ['manila.context']
217 res_dict = self.controller.detail(req)
219 self.assertEqual([expected_backup], res_dict['share_backups'])
220 self.mock_policy_check.assert_called_once_with(
221 req_context, self.resource_name, 'get_all')
223 def test_list_share_backups_with_limit(self):
224 fake_backup_1, expected_backup_1 = self._get_fake_backup()
225 fake_backup_2, expected_backup_2 = self._get_fake_backup(
226 id="fake_id2")
228 self.mock_object(share.API, 'get',
229 mock.Mock(return_value={'id': 'FAKE_SHAREID'}))
230 self.mock_object(
231 share_backups.db, 'share_backups_get_all',
232 mock.Mock(return_value=[fake_backup_1]))
233 req = fakes.HTTPRequest.blank(
234 '/share-backups?share_id=FAKE_SHARE_ID&limit=1',
235 version=self.api_version, experimental=True)
236 req_context = req.environ['manila.context']
238 res_dict = self.controller.detail(req)
240 self.assertEqual(1, len(res_dict['share_backups']))
241 self.assertEqual([expected_backup_1], res_dict['share_backups'])
242 self.mock_policy_check.assert_called_once_with(
243 req_context, self.resource_name, 'get_all')
245 def test_list_share_backups_with_limit_and_offset(self):
246 fake_backup_1, expected_backup_1 = self._get_fake_backup()
247 fake_backup_2, expected_backup_2 = self._get_fake_backup(
248 id="fake_id2")
249 self.mock_object(share.API, 'get',
250 mock.Mock(return_value={'id': 'FAKE_SHAREID'}))
251 self.mock_object(
252 share_backups.db, 'share_backups_get_all',
253 mock.Mock(return_value=[fake_backup_2]))
254 req = fakes.HTTPRequest.blank(
255 '/share-backups?share_id=FAKE_SHARE_ID&limit=1&offset=1',
256 version=self.api_version, experimental=True)
257 req_context = req.environ['manila.context']
259 res_dict = self.controller.detail(req)
261 self.assertEqual(1, len(res_dict['share_backups']))
262 self.assertEqual([expected_backup_2], res_dict['share_backups'])
263 self.mock_policy_check.assert_called_once_with(
264 req_context, self.resource_name, 'get_all')
266 def test_show(self):
267 fake_backup, expected_backup = self._get_fake_backup()
268 self.mock_object(
269 share_backups.db, 'share_backup_get',
270 mock.Mock(return_value=fake_backup))
272 req = self.backups_req
273 res_dict = self.controller.show(req, fake_backup.get('id'))
275 self.assertEqual(expected_backup, res_dict['share_backup'])
277 def test_show_no_backup(self):
278 mock__view_builder_call = self.mock_object(
279 share_backups.backup_view.BackupViewBuilder, 'detail')
280 fake_exception = exception.ShareBackupNotFound(
281 backup_id='FAKE_backup_ID')
282 self.mock_object(share_backups.db, 'share_backup_get', mock.Mock(
283 side_effect=fake_exception))
285 self.assertRaises(exc.HTTPNotFound,
286 self.controller.show,
287 self.backups_req,
288 'FAKE_backup_ID')
289 self.assertFalse(mock__view_builder_call.called)
291 def test_create_invalid_body(self):
292 body = {}
293 mock__view_builder_call = self.mock_object(
294 share_backups.backup_view.BackupViewBuilder,
295 'detail_list')
297 self.assertRaises(exc.HTTPUnprocessableEntity,
298 self.controller.create,
299 self.backups_req, body)
300 self.assertEqual(0, mock__view_builder_call.call_count)
302 def test_create_no_share_id(self):
303 body = {
304 'share_backup': {
305 'share_id': None,
306 'availability_zone': None,
307 }
308 }
309 mock__view_builder_call = self.mock_object(
310 share_backups.backup_view.BackupViewBuilder,
311 'detail_list')
312 self.mock_object(share_backups.db, 'share_get',
313 mock.Mock(side_effect=exception.NotFound))
315 self.assertRaises(exc.HTTPBadRequest,
316 self.controller.create,
317 self.backups_req, body)
318 self.assertFalse(mock__view_builder_call.called)
320 def test_create_invalid_share_id(self):
321 body = {
322 'share_backup': {
323 'share_id': None,
324 }
325 }
326 mock__view_builder_call = self.mock_object(
327 share_backups.backup_view.BackupViewBuilder,
328 'detail_list')
329 self.mock_object(share.API, 'get',
330 mock.Mock(side_effect=exception.NotFound))
332 self.assertRaises(exc.HTTPBadRequest,
333 self.controller.create,
334 self.backups_req, body)
335 self.assertFalse(mock__view_builder_call.called)
337 @ddt.data(exception.InvalidBackup, exception.ShareBusyException)
338 def test_create_exception_path(self, exception_type):
339 fake_backup, _ = self._get_fake_backup()
340 mock__view_builder_call = self.mock_object(
341 share_backups.backup_view.BackupViewBuilder,
342 'detail_list')
343 body = {
344 'share_backup': {
345 'share_id': 'FAKE_SHAREID',
346 }
347 }
348 exc_args = {'id': 'xyz', 'reason': 'abc'}
349 self.mock_object(share.API, 'get',
350 mock.Mock(return_value={'id': 'FAKE_SHAREID'}))
351 self.mock_object(share.API, 'create_share_backup',
352 mock.Mock(side_effect=exception_type(**exc_args)))
354 if exception_type == exception.InvalidBackup:
355 expected_exception = exc.HTTPBadRequest
356 else:
357 expected_exception = exc.HTTPConflict
358 self.assertRaises(expected_exception,
359 self.controller.create,
360 self.backups_req, body)
361 self.assertFalse(mock__view_builder_call.called)
363 def test_create(self):
364 fake_backup, expected_backup = self._get_fake_backup()
365 body = {
366 'share_backup': {
367 'share_id': 'FAKE_SHAREID',
368 }
369 }
370 self.mock_object(share.API, 'get',
371 mock.Mock(return_value={'id': 'FAKE_SHAREID'}))
372 self.mock_object(share.API, 'create_share_backup',
373 mock.Mock(return_value=fake_backup))
375 req = self.backups_req
376 res_dict = self.controller.create(req, body)
377 self.assertEqual(expected_backup, res_dict['share_backup'])
379 def test_delete_invalid_backup(self):
380 fake_exception = exception.ShareBackupNotFound(
381 backup_id='FAKE_backup_ID')
382 self.mock_object(share_backups.db, 'share_backup_get',
383 mock.Mock(side_effect=fake_exception))
384 mock_delete_backup_call = self.mock_object(
385 share.API, 'delete_share_backup')
387 self.assertRaises(
388 exc.HTTPNotFound, self.controller.delete,
389 self.backups_req, 'FAKE_backup_ID')
390 self.assertFalse(mock_delete_backup_call.called)
392 def test_delete_exception(self):
393 fake_backup_1 = self._get_fake_backup(
394 share_id='FAKE_SHARE_ID',
395 status=constants.STATUS_BACKUP_CREATING)[0]
396 fake_backup_2 = self._get_fake_backup(
397 share_id='FAKE_SHARE_ID',
398 status=constants.STATUS_BACKUP_CREATING)[0]
399 exception_type = exception.InvalidBackup(reason='xyz')
400 self.mock_object(share_backups.db, 'share_backup_get',
401 mock.Mock(return_value=fake_backup_1))
402 self.mock_object(
403 share_backups.db, 'share_backups_get_all',
404 mock.Mock(return_value=[fake_backup_1, fake_backup_2]))
405 self.mock_object(share.API, 'delete_share_backup',
406 mock.Mock(side_effect=exception_type))
408 self.assertRaises(exc.HTTPBadRequest, self.controller.delete,
409 self.backups_req, 'FAKE_backup_ID')
411 def test_delete(self):
412 fake_backup = self._get_fake_backup(
413 share_id='FAKE_SHARE_ID',
414 status=constants.STATUS_AVAILABLE)[0]
415 self.mock_object(share_backups.db, 'share_backup_get',
416 mock.Mock(return_value=fake_backup))
417 self.mock_object(share.API, 'delete_share_backup')
419 resp = self.controller.delete(
420 self.backups_req, 'FAKE_backup_ID')
422 self.assertEqual(202, resp.status_code)
424 def test_restore_invalid_backup_id(self):
425 body = {'restore': None}
426 fake_exception = exception.ShareBackupNotFound(
427 backup_id='FAKE_BACKUP_ID')
428 self.mock_object(share.API, 'restore',
429 mock.Mock(side_effect=fake_exception))
431 self.assertRaises(exc.HTTPNotFound,
432 self.controller.restore,
433 self.backups_req,
434 'FAKE_BACKUP_ID', body)
436 def test_restore(self):
437 body = {'restore': None}
438 fake_share_obj = fake_share.fake_share(
439 id='FAKE_SHARE_ID',
440 status=constants.STATUS_AVAILABLE,
441 size=1
442 )
443 fake_backup = self._get_fake_backup(
444 share_id=fake_share_obj['id'],
445 status=constants.STATUS_AVAILABLE)[0]
447 fake_backup_restore = {
448 'share_id': fake_share_obj['id'],
449 'backup_id': fake_backup['id'],
450 }
451 mock_api_restore_backup_call = self.mock_object(
452 share.API, 'restore_share_backup',
453 mock.Mock(return_value=fake_backup_restore))
455 self.mock_object(share_backups.db, 'share_get',
456 mock.Mock(return_value=fake_share_obj))
457 self.mock_object(share_backups.db, 'share_backup_get',
458 mock.Mock(return_value=fake_backup))
459 self.mock_object(share.API, 'get',
460 mock.Mock(return_value={'id': 'FAKE_SHARE_ID'}))
461 resp = self.controller.restore(self.backups_req,
462 fake_backup['id'], body)
464 self.assertEqual(fake_backup_restore, resp['restore'])
465 self.assertTrue(mock_api_restore_backup_call.called)
467 def test_restore_to_target_share(self):
468 body = {'restore': 'FAKE_TRGT_SHARE_ID'}
469 # overide req version to microversion with targeted restore.
470 self.backups_req = fakes.HTTPRequest.blank(
471 '/share-backups', version='2.91', experimental=True)
473 fake_target_share_obj = fake_share.fake_share(
474 id='FAKE_TRGT_SHARE_ID',
475 status=constants.STATUS_AVAILABLE,
476 size=1
477 )
478 fake_share_obj = fake_share.fake_share(
479 id='FAKE_SHARE_ID',
480 status=constants.STATUS_AVAILABLE,
481 size=1
482 )
483 fake_backup = self._get_fake_backup(
484 share_id=fake_share_obj['id'],
485 status=constants.STATUS_AVAILABLE)[0]
487 fake_backup_restore = {
488 'share_id': fake_target_share_obj['id'],
489 'backup_id': fake_backup['id'],
490 }
491 mock_api_restore_backup_call = self.mock_object(
492 share.API, 'restore_share_backup',
493 mock.Mock(return_value=fake_backup_restore))
495 self.mock_object(share_backups.db, 'share_get',
496 mock.Mock(return_value=fake_target_share_obj))
497 self.mock_object(share_backups.db, 'share_backup_get',
498 mock.Mock(return_value=fake_backup))
499 self.mock_object(share.API, 'get',
500 mock.Mock(return_value={'id': 'FAKE_TRGT_SHARE_ID'}))
501 resp = self.controller.restore(self.backups_req,
502 fake_backup['id'], body)
504 self.assertEqual(fake_backup_restore, resp['restore'])
505 self.assertTrue(mock_api_restore_backup_call.called)
507 def test_update(self):
508 fake_backup = self._get_fake_backup(
509 share_id='FAKE_SHARE_ID',
510 status=constants.STATUS_AVAILABLE)[0]
511 self.mock_object(share_backups.db, 'share_backup_get',
512 mock.Mock(return_value=fake_backup))
514 body = {'share_backup': {'name': 'backup1'}}
515 fake_backup_update = {
516 'share_id': 'FAKE_SHARE_ID',
517 'backup_id': fake_backup['id'],
518 'display_name': 'backup1'
519 }
520 mock_api_update_backup_call = self.mock_object(
521 share.API, 'update_share_backup',
522 mock.Mock(return_value=fake_backup_update))
524 resp = self.controller.update(self.backups_req,
525 fake_backup['id'], body)
527 self.assertEqual(fake_backup_update['display_name'],
528 resp['share_backup']['name'])
529 self.assertTrue(mock_api_update_backup_call.called)
531 @ddt.data('index', 'detail')
532 def test_policy_not_authorized(self, method_name):
534 method = getattr(self.controller, method_name)
535 arguments = {
536 'id': 'FAKE_backup_ID',
537 'body': {'FAKE_KEY': 'FAKE_VAL'},
538 }
539 if method_name in ('index', 'detail'): 539 ↛ 542line 539 didn't jump to line 542 because the condition on line 539 was always true
540 arguments.clear()
542 noauthexc = exception.PolicyNotAuthorized(action=method)
544 with mock.patch.object(
545 policy, 'check_policy', mock.Mock(side_effect=noauthexc)):
547 self.assertRaises(
548 exc.HTTPForbidden, method, self.backups_req, **arguments)
550 @ddt.data('index', 'detail', 'show', 'create', 'delete')
551 def test_upsupported_microversion(self, method_name):
553 unsupported_microversions = ('1.0', '2.2', '2.18')
554 method = getattr(self.controller, method_name)
555 arguments = {
556 'id': 'FAKE_BACKUP_ID',
557 'body': {'FAKE_KEY': 'FAKE_VAL'},
558 }
559 if method_name in ('index', 'detail'):
560 arguments.clear()
562 for microversion in unsupported_microversions:
563 req = fakes.HTTPRequest.blank(
564 '/share-backups', version=microversion,
565 experimental=True)
566 self.assertRaises(exception.VersionNotFoundForAPIMethod,
567 method, req, **arguments)