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

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. 

12 

13import ddt 

14from oslo_config import cfg 

15from unittest import mock 

16from webob import exc 

17 

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 

29 

30CONF = cfg.CONF 

31 

32 

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') 

51 

52 def _get_context(self, role): 

53 return getattr(self, '%s_context' % role) 

54 

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 

66 

67 return backup, req 

68 

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')}) 

78 

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 }) 

96 

97 if admin: 

98 expected_backup.update({ 

99 'host': backup.get('host'), 

100 'topic': backup.get('topic'), 

101 }) 

102 

103 return backup, expected_backup 

104 

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])) 

109 

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') 

114 

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'] 

125 

126 res_dict = self.controller.index(req) 

127 

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') 

131 

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])) 

137 

138 req = self.backups_req if not is_admin else self.backups_req_admin 

139 req_context = req.environ['manila.context'] 

140 

141 res_dict = self.controller.detail(req) 

142 

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') 

146 

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'] 

158 

159 res_dict = self.controller.detail(req) 

160 

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') 

165 

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'] 

177 

178 res_dict = self.controller.detail(req) 

179 

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') 

184 

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' 

193 

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') 

199 

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'] 

216 

217 res_dict = self.controller.detail(req) 

218 

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') 

222 

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") 

227 

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'] 

237 

238 res_dict = self.controller.detail(req) 

239 

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') 

244 

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'] 

258 

259 res_dict = self.controller.detail(req) 

260 

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') 

265 

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)) 

271 

272 req = self.backups_req 

273 res_dict = self.controller.show(req, fake_backup.get('id')) 

274 

275 self.assertEqual(expected_backup, res_dict['share_backup']) 

276 

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)) 

284 

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) 

290 

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') 

296 

297 self.assertRaises(exc.HTTPUnprocessableEntity, 

298 self.controller.create, 

299 self.backups_req, body) 

300 self.assertEqual(0, mock__view_builder_call.call_count) 

301 

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)) 

314 

315 self.assertRaises(exc.HTTPBadRequest, 

316 self.controller.create, 

317 self.backups_req, body) 

318 self.assertFalse(mock__view_builder_call.called) 

319 

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)) 

331 

332 self.assertRaises(exc.HTTPBadRequest, 

333 self.controller.create, 

334 self.backups_req, body) 

335 self.assertFalse(mock__view_builder_call.called) 

336 

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))) 

353 

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) 

362 

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)) 

374 

375 req = self.backups_req 

376 res_dict = self.controller.create(req, body) 

377 self.assertEqual(expected_backup, res_dict['share_backup']) 

378 

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') 

386 

387 self.assertRaises( 

388 exc.HTTPNotFound, self.controller.delete, 

389 self.backups_req, 'FAKE_backup_ID') 

390 self.assertFalse(mock_delete_backup_call.called) 

391 

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)) 

407 

408 self.assertRaises(exc.HTTPBadRequest, self.controller.delete, 

409 self.backups_req, 'FAKE_backup_ID') 

410 

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') 

418 

419 resp = self.controller.delete( 

420 self.backups_req, 'FAKE_backup_ID') 

421 

422 self.assertEqual(202, resp.status_code) 

423 

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)) 

430 

431 self.assertRaises(exc.HTTPNotFound, 

432 self.controller.restore, 

433 self.backups_req, 

434 'FAKE_BACKUP_ID', body) 

435 

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] 

446 

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)) 

454 

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) 

463 

464 self.assertEqual(fake_backup_restore, resp['restore']) 

465 self.assertTrue(mock_api_restore_backup_call.called) 

466 

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) 

472 

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] 

486 

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)) 

494 

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) 

503 

504 self.assertEqual(fake_backup_restore, resp['restore']) 

505 self.assertTrue(mock_api_restore_backup_call.called) 

506 

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)) 

513 

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)) 

523 

524 resp = self.controller.update(self.backups_req, 

525 fake_backup['id'], body) 

526 

527 self.assertEqual(fake_backup_update['display_name'], 

528 resp['share_backup']['name']) 

529 self.assertTrue(mock_api_update_backup_call.called) 

530 

531 @ddt.data('index', 'detail') 

532 def test_policy_not_authorized(self, method_name): 

533 

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() 

541 

542 noauthexc = exception.PolicyNotAuthorized(action=method) 

543 

544 with mock.patch.object( 

545 policy, 'check_policy', mock.Mock(side_effect=noauthexc)): 

546 

547 self.assertRaises( 

548 exc.HTTPForbidden, method, self.backups_req, **arguments) 

549 

550 @ddt.data('index', 'detail', 'show', 'create', 'delete') 

551 def test_upsupported_microversion(self, method_name): 

552 

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() 

561 

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)