Coverage for manila/tests/share/drivers/zadara/test_zadara.py: 85%

516 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright (c) 2021 Zadara Storage, Inc. 

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. 

15""" 

16Tests for Zadara VPSA Share driver 

17""" 

18 

19import copy 

20import requests 

21 

22from unittest import mock 

23from urllib import parse 

24 

25from manila import context 

26from manila import exception as manila_exception 

27from manila.share import configuration 

28from manila.share.drivers.zadara import zadara 

29from manila import test 

30from manila.tests import fake_share 

31 

32 

33def check_access_key(func): 

34 """A decorator for all operations that needed an API before executing""" 

35 def wrap(self, *args, **kwargs): 

36 if not self._is_correct_access_key(): 36 ↛ 37line 36 didn't jump to line 37 because the condition on line 36 was never true

37 return RUNTIME_VARS['bad_login'] 

38 return func(self, *args, **kwargs) 

39 

40 return wrap 

41 

42 

43DEFAULT_RUNTIME_VARS = { 

44 'status': 200, 

45 'user': 'test', 

46 'password': 'test_password', 

47 'access_key': '0123456789ABCDEF', 

48 'volumes': [], 

49 'servers': [], 

50 'controllers': [('active_ctrl', {'display-name': 'test_ctrl'})], 

51 'counter': 1000, 

52 

53 "login": """ 

54 { 

55 "response": { 

56 "user": { 

57 "updated-at": "2021-01-22", 

58 "access-key": "%s", 

59 "id": 1, 

60 "created-at": "2021-01-22", 

61 "email": "jsmith@example.com", 

62 "username": "jsmith" 

63 }, 

64 "status": 0 

65 } 

66 }""", 

67 "good": """ 

68 { 

69 "response": { 

70 "status": 0 

71 } 

72 }""", 

73 "good_snapshot": """ 

74 { 

75 "response": { 

76 "snapshot_name": "fakesnaplocation", 

77 "status": 0 

78 } 

79 }""", 

80 "bad_login": """ 

81 { 

82 "response": { 

83 "status": 5, 

84 "status-msg": "Some message..." 

85 } 

86 }""", 

87 "bad_volume": """ 

88 { 

89 "response": { 

90 "status": 10081, 

91 "status-msg": "Virtual volume xxx should be found" 

92 } 

93 }""", 

94 "fake_volume": """ 

95 { 

96 "response": { 

97 "volumes": [], 

98 "status": 0, 

99 "status-msg": "Virtual volume xxx doesn't exist" 

100 } 

101 }""", 

102 "bad_server": """ 

103 { 

104 "response": { 

105 "status": 10086, 

106 "status-msg": "Server xxx not found" 

107 } 

108 }""", 

109 "server_created": """ 

110 { 

111 "response": { 

112 "server_name": "%s", 

113 "status": 0 

114 } 

115 }""", 

116} 

117 

118RUNTIME_VARS = None 

119 

120 

121class FakeResponse(object): 

122 def __init__(self, method, url, params, body, headers, **kwargs): 

123 # kwargs include: verify, timeout 

124 self.method = method 

125 self.url = url 

126 self.body = body 

127 self.params = params 

128 self.headers = headers 

129 self.status = RUNTIME_VARS['status'] 

130 

131 @property 

132 def access_key(self): 

133 """Returns Response Access Key""" 

134 return self.headers["X-Access-Key"] 

135 

136 def read(self): 

137 ops = {'POST': [('/api/users/login.json', self._login), 

138 ('/api/volumes.json', self._create_volume), 

139 ('/api/servers.json', self._create_server), 

140 ('/api/servers/*/volumes.json', self._attach), 

141 ('/api/volumes/*/rename.json', self._rename), 

142 ('/api/volumes/*/detach.json', self._detach), 

143 ('/api/volumes/*/expand.json', self._expand), 

144 ('/api/consistency_groups/*/snapshots.json', 

145 self._create_snapshot), 

146 ('/api/snapshots/*/rename.json', 

147 self._rename_snapshot), 

148 ('/api/consistency_groups/*/clone.json', 

149 self._create_clone_from_snapshot), 

150 ('/api/consistency_groups/*/clone.json', 

151 self._create_clone)], 

152 'DELETE': [('/api/volumes/*', self._delete), 

153 ('/api/snapshots/*', self._delete_snapshot)], 

154 'GET': [('/api/volumes.json?showonlyfile=YES', 

155 self._list_volumes), 

156 ('/api/volumes.json?display_name=*', 

157 self._get_volume_by_name), 

158 ('/api/pools/*.json', self._get_pool), 

159 ('/api/vcontrollers.json', self._list_controllers), 

160 ('/api/servers.json', self._list_servers), 

161 ('/api/consistency_groups/*/snapshots.json', 

162 self._list_vol_snapshots), 

163 ('/api/volumes/*/servers.json', 

164 self._list_vol_attachments)] 

165 } 

166 

167 ops_list = ops[self.method] 

168 for (templ_url, func) in ops_list: 168 ↛ exitline 168 didn't return from function 'read' because the loop on line 168 didn't complete

169 if self._compare_url(self.url, templ_url): 

170 result = func() 

171 return result 

172 

173 @staticmethod 

174 def _compare_url(url, template_url): 

175 items = url.split('/') 

176 titems = template_url.split('/') 

177 for (i, titem) in enumerate(titems): 

178 if '*' not in titem and titem != items[i]: 

179 return False 

180 if '?' in titem and titem.split('=')[0] != items[i].split('=')[0]: 

181 return False 

182 

183 return True 

184 

185 @staticmethod 

186 def _get_counter(): 

187 cnt = RUNTIME_VARS['counter'] 

188 RUNTIME_VARS['counter'] += 1 

189 return cnt 

190 

191 def _login(self): 

192 params = self.body 

193 if (params['user'] == RUNTIME_VARS['user'] and 

194 params['password'] == RUNTIME_VARS['password']): 

195 return RUNTIME_VARS['login'] % RUNTIME_VARS['access_key'] 

196 else: 

197 return RUNTIME_VARS['bad_login'] 

198 

199 def _is_correct_access_key(self): 

200 return self.access_key == RUNTIME_VARS['access_key'] 

201 

202 @check_access_key 

203 def _create_volume(self): 

204 params = self.body 

205 params['display-name'] = params['name'] 

206 params['cg-name'] = params['name'] 

207 params['snapshots'] = [] 

208 params['server_ext_names'] = '' 

209 params['provisioned-capacity'] = 1 

210 vpsa_vol = 'volume-%07d' % self._get_counter() 

211 params['nfs-export-path'] = '10.2.1.56:/export/%s' % vpsa_vol 

212 RUNTIME_VARS['volumes'].append((vpsa_vol, params)) 

213 return RUNTIME_VARS['good'] 

214 

215 @check_access_key 

216 def _create_server(self): 

217 params = self.body 

218 

219 params['display-name'] = params['display_name'] 

220 vpsa_srv = 'srv-%07d' % self._get_counter() 

221 RUNTIME_VARS['servers'].append((vpsa_srv, params)) 

222 return RUNTIME_VARS['server_created'] % vpsa_srv 

223 

224 @check_access_key 

225 def _attach(self): 

226 srv = self.url.split('/')[3] 

227 

228 params = self.body 

229 

230 vol = params['volume_name[]'] 

231 

232 for (vol_name, params) in RUNTIME_VARS['volumes']: 232 ↛ 245line 232 didn't jump to line 245 because the loop on line 232 didn't complete

233 if params['name'] == vol: 233 ↛ 232line 233 didn't jump to line 232 because the condition on line 233 was always true

234 attachments = params['server_ext_names'].split(',') 

235 if srv in attachments: 235 ↛ 237line 235 didn't jump to line 237 because the condition on line 235 was never true

236 # already attached - ok 

237 return RUNTIME_VARS['good'] 

238 else: 

239 if not attachments[0]: 239 ↛ 242line 239 didn't jump to line 242 because the condition on line 239 was always true

240 params['server_ext_names'] = srv 

241 else: 

242 params['server_ext_names'] += ',' + srv 

243 return RUNTIME_VARS['good'] 

244 

245 return RUNTIME_VARS['bad_volume'] 

246 

247 @check_access_key 

248 def _detach(self): 

249 params = self.body 

250 vol = self.url.split('/')[3] 

251 srv = params['server_name[]'] 

252 

253 for (vol_name, params) in RUNTIME_VARS['volumes']: 253 ↛ 264line 253 didn't jump to line 264 because the loop on line 253 didn't complete

254 if params['name'] == vol: 254 ↛ 253line 254 didn't jump to line 253 because the condition on line 254 was always true

255 attachments = params['server_ext_names'].split(',') 

256 if srv not in attachments: 256 ↛ 257line 256 didn't jump to line 257 because the condition on line 256 was never true

257 return RUNTIME_VARS['bad_server'] 

258 else: 

259 attachments.remove(srv) 

260 params['server_ext_names'] = (','.join([str(elem) 

261 for elem in attachments])) 

262 return RUNTIME_VARS['good'] 

263 

264 return RUNTIME_VARS['bad_volume'] 

265 

266 @check_access_key 

267 def _expand(self): 

268 params = self.body 

269 vol = self.url.split('/')[3] 

270 capacity = params['capacity'] 

271 

272 for (vol_name, params) in RUNTIME_VARS['volumes']: 272 ↛ 277line 272 didn't jump to line 277 because the loop on line 272 didn't complete

273 if params['name'] == vol: 273 ↛ 272line 273 didn't jump to line 272 because the condition on line 273 was always true

274 params['capacity'] = capacity 

275 return RUNTIME_VARS['good'] 

276 

277 return RUNTIME_VARS['bad_volume'] 

278 

279 @check_access_key 

280 def _rename(self): 

281 params = self.body 

282 vol = self.url.split('/')[3] 

283 

284 for (vol_name, vol_params) in RUNTIME_VARS['volumes']: 284 ↛ 291line 284 didn't jump to line 291 because the loop on line 284 didn't complete

285 if vol_params['name'] == vol: 285 ↛ 284line 285 didn't jump to line 284 because the condition on line 285 was always true

286 vol_params['name'] = params['new_name'] 

287 vol_params['display-name'] = params['new_name'] 

288 vol_params['cg-name'] = params['new_name'] 

289 return RUNTIME_VARS['good'] 

290 

291 return RUNTIME_VARS['bad_volume'] 

292 

293 @check_access_key 

294 def _rename_snapshot(self): 

295 params = self.body 

296 vpsa_snapshot = self.url.split('/')[3] 

297 

298 for (vol_name, vol_params) in RUNTIME_VARS['volumes']: 298 ↛ 305line 298 didn't jump to line 305 because the loop on line 298 didn't complete

299 for snapshot in vol_params['snapshots']: 299 ↛ 298line 299 didn't jump to line 298 because the loop on line 299 didn't complete

300 if vpsa_snapshot == snapshot['provider-location']: 300 ↛ 299line 300 didn't jump to line 299 because the condition on line 300 was always true

301 snapshot['name'] = params['newname'] 

302 snapshot['display-name'] = params['newname'] 

303 return RUNTIME_VARS['good'] 

304 

305 return RUNTIME_VARS['bad_volume'] 

306 

307 @check_access_key 

308 def _create_snapshot(self): 

309 params = self.body 

310 cg_name = self.url.split('/')[3] 

311 snap_name = params['display_name'] 

312 

313 for (vol_name, params) in RUNTIME_VARS['volumes']: 313 ↛ 323line 313 didn't jump to line 323 because the loop on line 313 didn't complete

314 if params['cg-name'] == cg_name: 314 ↛ 313line 314 didn't jump to line 313 because the condition on line 314 was always true

315 snapshots = params['snapshots'] 

316 if snap_name in snapshots: 316 ↛ 318line 316 didn't jump to line 318 because the condition on line 316 was never true

317 # already attached 

318 return RUNTIME_VARS['bad_volume'] 

319 else: 

320 snapshots.append(snap_name) 

321 return RUNTIME_VARS['good_snapshot'] 

322 

323 return RUNTIME_VARS['bad_volume'] 

324 

325 @check_access_key 

326 def _delete_snapshot(self): 

327 snap = self.url.split('/')[3].split('.')[0] 

328 

329 for (vol_name, params) in RUNTIME_VARS['volumes']: 329 ↛ 334line 329 didn't jump to line 334 because the loop on line 329 didn't complete

330 if snap in params['snapshots']: 330 ↛ 329line 330 didn't jump to line 329 because the condition on line 330 was always true

331 params['snapshots'].remove(snap) 

332 return RUNTIME_VARS['good'] 

333 

334 return RUNTIME_VARS['bad_volume'] 

335 

336 @check_access_key 

337 def _create_clone_from_snapshot(self): 

338 params = self.body 

339 params['display-name'] = params['name'] 

340 params['cg-name'] = params['name'] 

341 params['capacity'] = 1 

342 params['snapshots'] = [] 

343 params['server_ext_names'] = '' 

344 params['pool'] = 'pool-0001' 

345 params['provisioned-capacity'] = 1 

346 vpsa_vol = 'volume-%07d' % self._get_counter() 

347 params['nfs-export-path'] = '10.2.1.56:/export/%s' % vpsa_vol 

348 RUNTIME_VARS['volumes'].append((vpsa_vol, params)) 

349 return RUNTIME_VARS['good'] 

350 

351 @check_access_key 

352 def _create_clone(self): 

353 params = self.body 

354 params['display-name'] = params['name'] 

355 params['cg-name'] = params['name'] 

356 params['capacity'] = 1 

357 params['snapshots'] = [] 

358 params['server_ext_names'] = '' 

359 vpsa_vol = 'volume-%07d' % self._get_counter() 

360 RUNTIME_VARS['volumes'].append((vpsa_vol, params)) 

361 return RUNTIME_VARS['good'] 

362 

363 def _delete(self): 

364 vol = self.url.split('/')[3].split('.')[0] 

365 

366 for (vol_name, params) in RUNTIME_VARS['volumes']: 366 ↛ 375line 366 didn't jump to line 375 because the loop on line 366 didn't complete

367 if params['name'] == vol: 367 ↛ 366line 367 didn't jump to line 366 because the condition on line 367 was always true

368 if params['server_ext_names']: 368 ↛ 370line 368 didn't jump to line 370 because the condition on line 368 was never true

369 # there are attachments - should be volume busy error 

370 return RUNTIME_VARS['bad_volume'] 

371 else: 

372 RUNTIME_VARS['volumes'].remove((vol_name, params)) 

373 return RUNTIME_VARS['good'] 

374 

375 return RUNTIME_VARS['bad_volume'] 

376 

377 def _generate_list_resp(self, null_body, body, lst, vol): 

378 resp = '' 

379 for (obj, params) in lst: 

380 if vol: 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true

381 resp += body % (params['name'], 

382 params['display-name'], 

383 params['cg-name'], 

384 params['capacity'], 

385 params['pool'], 

386 params['provisioned-capacity'], 

387 params['nfs-export-path']) 

388 else: 

389 resp += body % (obj, params['display-name']) 

390 if resp: 

391 return resp 

392 else: 

393 return null_body 

394 

395 def _list_volumes(self): 

396 null_body = """ 

397 { 

398 "response": { 

399 "volumes": [ 

400 ], 

401 "status": 0 

402 } 

403 }""" 

404 body = """ 

405 { 

406 "response": { 

407 "volumes": %s, 

408 "status": 0 

409 } 

410 }""" 

411 

412 volume_obj = """ 

413 { 

414 "name": "%s", 

415 "display_name": "%s", 

416 "cg_name": "%s", 

417 "status": "Available", 

418 "virtual_capacity": %d, 

419 "pool_name": "%s", 

420 "allocated-capacity": 1, 

421 "provisioned_capacity": "%s", 

422 "raid-group-name": "r5", 

423 "cache": "write-through", 

424 "created-at": "2021-01-22", 

425 "modified-at": "2021-01-22", 

426 "nfs_export_path": "%s" 

427 } 

428 """ 

429 if len(RUNTIME_VARS['volumes']) == 0: 429 ↛ 430line 429 didn't jump to line 430 because the condition on line 429 was never true

430 return null_body 

431 resp = '' 

432 volume_list = '' 

433 count = 0 

434 for (vol_name, params) in RUNTIME_VARS['volumes']: 

435 volume_dict = volume_obj % (params['name'], 

436 params['display-name'], 

437 params['cg-name'], 

438 params['capacity'], 

439 params['pool'], 

440 params['provisioned-capacity'], 

441 params['nfs-export-path']) 

442 if count == 0: 442 ↛ 445line 442 didn't jump to line 445 because the condition on line 442 was always true

443 volume_list += volume_dict 

444 count += 1 

445 elif count != len(RUNTIME_VARS['volumes']): 

446 volume_list = volume_list + ',' + volume_dict 

447 count += 1 

448 if volume_list: 448 ↛ 453line 448 didn't jump to line 453 because the condition on line 448 was always true

449 volume_list = '[' + volume_list + ']' 

450 resp = body % volume_list 

451 return resp 

452 

453 return RUNTIME_VARS['bad_volume'] 

454 

455 def _get_volume_by_name(self): 

456 volume_name = self.url.split('=')[1] 

457 body = """ 

458 { 

459 "response": { 

460 "volumes": [ 

461 { 

462 "name": "%s", 

463 "display_name": "%s", 

464 "cg_name": "%s", 

465 "status": "Available", 

466 "virtual_capacity": %d, 

467 "pool_name": "%s", 

468 "allocated-capacity": 1, 

469 "provisioned_capacity": %d, 

470 "raid-group-name": "r5", 

471 "cache": "write-through", 

472 "created-at": "2021-01-22", 

473 "modified-at": "2021-01-22", 

474 "nfs_export_path": "%s", 

475 "server_ext_names": "%s" 

476 } 

477 ], 

478 "status": 0 

479 } 

480 }""" 

481 for (vol_name, params) in RUNTIME_VARS['volumes']: 

482 if params['name'] == volume_name: 

483 resp = body % (volume_name, params['display-name'], 

484 params['cg-name'], params['capacity'], 

485 params['pool'], params['provisioned-capacity'], 

486 params['nfs-export-path'], 

487 params['server_ext_names']) 

488 return resp 

489 

490 return RUNTIME_VARS['fake_volume'] 

491 

492 def _list_controllers(self): 

493 null_body = """ 

494 { 

495 "response": { 

496 "vcontrollers": [ 

497 ], 

498 "status": 0 

499 } 

500 }""" 

501 body = """ 

502 { 

503 "response": { 

504 "vcontrollers": [ 

505 { 

506 "name": "%s", 

507 "display_name": "%s", 

508 "state": "active", 

509 "target": 

510 "iqn.2011-04.zadarastorage:vsa-xxx:1", 

511 "iscsi_ip": "1.1.1.1", 

512 "iscsi_ipv6": "", 

513 "mgmt-ip": "1.1.1.1", 

514 "software-ver": "0.0.09-05.1--77.7", 

515 "heartbeat1": "ok", 

516 "heartbeat2": "ok", 

517 "vpsa_chap_user": "test_chap_user", 

518 "vpsa_chap_secret": "test_chap_secret" 

519 } 

520 ], 

521 "status": 0 

522 } 

523 }""" 

524 return self._generate_list_resp(null_body, 

525 body, 

526 RUNTIME_VARS['controllers'], 

527 False) 

528 

529 def _get_pool(self): 

530 response = """ 

531 { 

532 "response": { 

533 "pool": { 

534 "name": "pool-0001", 

535 "capacity": 100, 

536 "available_capacity": 99, 

537 "provisioned_capacity": 1 

538 }, 

539 "status": 0 

540 } 

541 }""" 

542 return response 

543 

544 def _list_servers(self): 

545 null_body = """ 

546 { 

547 "response": { 

548 "servers": [ 

549 ], 

550 "status": 0 

551 } 

552 }""" 

553 body = """ 

554 { 

555 "response": { 

556 "servers": %s, 

557 "status": 0 

558 } 

559 }""" 

560 

561 server_obj = """ 

562 { 

563 "name": "%s", 

564 "display_name": "%s", 

565 "iscsi_ip": "%s", 

566 "status": "Active", 

567 "created-at": "2021-01-22", 

568 "modified-at": "2021-01-22" 

569 } 

570 """ 

571 resp = '' 

572 server_list = '' 

573 count = 0 

574 for (obj, params) in RUNTIME_VARS['servers']: 

575 server_dict = server_obj % (obj, 

576 params['display-name'], 

577 params['iqn']) 

578 if count == 0: 578 ↛ 581line 578 didn't jump to line 581 because the condition on line 578 was always true

579 server_list += server_dict 

580 count += 1 

581 elif count != len(RUNTIME_VARS['servers']): 

582 server_list = server_list + ',' + server_dict 

583 count += 1 

584 server_list = '[' + server_list + ']' 

585 resp = body % server_list 

586 if resp: 586 ↛ 589line 586 didn't jump to line 589 because the condition on line 586 was always true

587 return resp 

588 else: 

589 return null_body 

590 

591 def _get_server_obj(self, name): 

592 for (srv_name, params) in RUNTIME_VARS['servers']: 592 ↛ exitline 592 didn't return from function '_get_server_obj' because the loop on line 592 didn't complete

593 if srv_name == name: 593 ↛ 592line 593 didn't jump to line 592 because the condition on line 593 was always true

594 return params 

595 

596 def _list_vol_attachments(self): 

597 vol = self.url.split('/')[3] 

598 null_body = """ 

599 { 

600 "response": { 

601 "servers": [ 

602 ], 

603 "status": 0 

604 } 

605 }""" 

606 body = """ 

607 { 

608 "response": { 

609 "servers": %s, 

610 "status": 0 

611 } 

612 }""" 

613 

614 server_obj = """ 

615 { 

616 "name": "%s", 

617 "display_name": "%s", 

618 "iscsi_ip": "%s", 

619 "target": 

620 "iqn.2011-04.zadarastorage:vsa-xxx:1", 

621 "lun": 0 

622 } 

623 """ 

624 for (vol_name, params) in RUNTIME_VARS['volumes']: 624 ↛ 647line 624 didn't jump to line 647 because the loop on line 624 didn't complete

625 if params['name'] == vol: 625 ↛ 624line 625 didn't jump to line 624 because the condition on line 625 was always true

626 attachments = params['server_ext_names'].split(',') 

627 if not attachments[0]: 627 ↛ 628line 627 didn't jump to line 628 because the condition on line 627 was never true

628 return null_body 

629 resp = '' 

630 server_list = '' 

631 count = 0 

632 for server in attachments: 

633 srv_params = self._get_server_obj(server) 

634 server_dict = (server_obj % (server, 

635 srv_params['display_name'], 

636 srv_params['iscsi'])) 

637 if count == 0: 637 ↛ 640line 637 didn't jump to line 640 because the condition on line 637 was always true

638 server_list += server_dict 

639 count += 1 

640 elif count != len(attachments): 

641 server_list = server_list + ',' + server_dict 

642 count += 1 

643 server_list = '[' + server_list + ']' 

644 resp = body % server_list 

645 return resp 

646 

647 return RUNTIME_VARS['bad_volume'] 

648 

649 def _list_vol_snapshots(self): 

650 cg_name = self.url.split('/')[3] 

651 

652 null_body = """ 

653 { 

654 "response": { 

655 "snapshots": [ 

656 ], 

657 "status": 0 

658 } 

659 }""" 

660 

661 body = """ 

662 { 

663 "response": { 

664 "snapshots": %s, 

665 "status": 0 

666 } 

667 }""" 

668 

669 snapshot_obj = """ 

670 { 

671 "name": "%s", 

672 "display_name": "%s", 

673 "status": "normal", 

674 "cg-name": "%s", 

675 "pool-name": "pool-00000001" 

676 } 

677 """ 

678 for (vol_name, params) in RUNTIME_VARS['volumes']: 678 ↛ 700line 678 didn't jump to line 700 because the loop on line 678 didn't complete

679 if params['cg-name'] == cg_name: 679 ↛ 678line 679 didn't jump to line 678 because the condition on line 679 was always true

680 snapshots = params['snapshots'] 

681 if len(snapshots) == 0: 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true

682 return null_body 

683 resp = '' 

684 snapshot_list = '' 

685 count = 0 

686 

687 for snapshot in snapshots: 

688 snapshot_dict = snapshot_obj % (snapshot, snapshot, 

689 cg_name) 

690 if count == 0: 690 ↛ 693line 690 didn't jump to line 693 because the condition on line 690 was always true

691 snapshot_list += snapshot_dict 

692 count += 1 

693 elif count != len(snapshots): 

694 snapshot_list = snapshot_list + ',' + snapshot_dict 

695 count += 1 

696 snapshot_list = '[' + snapshot_list + ']' 

697 resp = body % snapshot_list 

698 return resp 

699 

700 return RUNTIME_VARS['bad_volume'] 

701 

702 

703class FakeRequests(object): 

704 """A fake requests for zadara volume driver tests.""" 

705 def __init__(self, method, api_url, params=None, data=None, 

706 headers=None, **kwargs): 

707 apiurl_items = parse.urlparse(api_url) 

708 if apiurl_items.query: 

709 url = apiurl_items.path + '?' + apiurl_items.query 

710 else: 

711 url = apiurl_items.path 

712 res = FakeResponse(method, url, params, data, headers, **kwargs) 

713 self.content = res.read() 

714 self.status_code = res.status 

715 

716 

717class ZadaraVPSAShareDriverTestCase(test.TestCase): 

718 

719 @mock.patch.object(requests.Session, 'request', FakeRequests) 

720 def setUp(self): 

721 super(ZadaraVPSAShareDriverTestCase, self).setUp() 

722 

723 def _safe_get(opt): 

724 return getattr(self.configuration, opt) 

725 

726 self._context = context.get_admin_context() 

727 self.configuration = mock.Mock(spec=configuration.Configuration) 

728 self.configuration.safe_get = mock.Mock(side_effect=_safe_get) 

729 

730 global RUNTIME_VARS 

731 RUNTIME_VARS = copy.deepcopy(DEFAULT_RUNTIME_VARS) 

732 

733 self.configuration.driver_handles_share_servers = False 

734 self.configuration.network_config_group = ( 

735 'fake_network_config_group') 

736 self.configuration.admin_network_config_group = ( 

737 'fake_admin_network_config_group') 

738 self.configuration.reserved_percentage = 0 

739 self.configuration.reserved_snapshot_percentage = 0 

740 self.configuration.reserved_share_extend_percentage = 0 

741 self.configuration.zadara_use_iser = True 

742 self.configuration.zadara_vpsa_host = '192.168.5.5' 

743 self.configuration.zadara_vpsa_port = '80' 

744 self.configuration.zadara_user = 'test' 

745 self.configuration.zadara_password = 'test_password' 

746 self.configuration.zadara_access_key = '0123456789ABCDEF' 

747 self.configuration.zadara_vpsa_poolname = 'pool-0001' 

748 self.configuration.zadara_vol_encrypt = False 

749 self.configuration.zadara_share_name_template = 'OS_share-%s' 

750 self.configuration.zadara_share_snap_name_template = ( 

751 'OS_share-snapshot-%s') 

752 self.configuration.zadara_vpsa_use_ssl = False 

753 self.configuration.zadara_ssl_cert_verify = False 

754 self.configuration.zadara_default_snap_policy = False 

755 self.configuration.zadara_driver_ssl_cert_path = None 

756 self.configuration.zadara_gen3_vol_compress = True 

757 self.configuration.zadara_gen3_vol_dedupe = True 

758 self.configuration.share_backend_name = 'zadaravpsa' 

759 self.configuration.reserved_share_percentage = '0' 

760 self.configuration.reserved_share_from_snapshot_percentage = '0' 

761 self.configuration.reserved_share_extend_percentage = 0 

762 self.configuration.replication_domain = None 

763 self.configuration.filter_function = None 

764 self.configuration.goodness_function = None 

765 self.configuration.goodness_function = None 

766 self.driver = (zadara.ZadaraVPSAShareDriver( 

767 configuration=self.configuration)) 

768 self.driver.do_setup(None) 

769 self.driver.api.get_share_metadata = mock.Mock(return_value={}) 

770 self.driver._get_share_export_location = mock.Mock() 

771 

772 @mock.patch.object(requests.Session, 'request', FakeRequests) 

773 def test_do_setup(self): 

774 self.driver.do_setup(self._context) 

775 self.assertIsNotNone(self.driver.vpsa) 

776 self.assertEqual(self.driver.vpsa.access_key, 

777 self.configuration.zadara_access_key) 

778 

779 @mock.patch.object(requests.Session, 'request', FakeRequests) 

780 def test_no_active_ctrl(self): 

781 share = fake_share.fake_share(id='fakeid', share_proto='NFS', 

782 share_id='fakeshareid') 

783 self.driver.create_share(self._context, share) 

784 access = fake_share.fake_access() 

785 

786 RUNTIME_VARS['controllers'] = [] 

787 self.assertRaises(manila_exception.ZadaraVPSANoActiveController, 

788 self.driver._allow_access, 

789 self._context, 

790 share, access) 

791 

792 @mock.patch.object(requests.Session, 'request', FakeRequests) 

793 def test_create_share_unsupported_proto(self): 

794 share = fake_share.fake_share(share_proto='INVALID') 

795 self.assertRaises(manila_exception.ZadaraInvalidProtocol, 

796 self.driver.create_share, 

797 self._context, 

798 share) 

799 

800 @mock.patch.object(requests.Session, 'request', FakeRequests) 

801 def test_create_delete_share(self): 

802 """Create share.""" 

803 share = fake_share.fake_share(share_proto='NFS', 

804 share_id='fakeshareid') 

805 self.driver.create_share(self._context, share) 

806 self.driver.delete_share(self._context, share) 

807 

808 @mock.patch.object(requests.Session, 'request', FakeRequests) 

809 def test_create_delete_multiple_shares(self): 

810 """Create/Delete multiple shares.""" 

811 share1 = fake_share.fake_share(id='fakeid1', share_proto='NFS', 

812 share_id='fakeshareid1') 

813 self.driver.create_share(self._context, share1) 

814 

815 share2 = fake_share.fake_share(id='fakeid2', share_proto='CIFS', 

816 share_id='fakeshareid2') 

817 self.driver.create_share(self._context, share2) 

818 

819 self.driver.delete_share(self._context, share1) 

820 self.driver.delete_share(self._context, share2) 

821 

822 @mock.patch.object(requests.Session, 'request', FakeRequests) 

823 def test_delete_non_existent(self): 

824 """Delete non-existent share.""" 

825 share = fake_share.fake_share(share_proto='NFS', 

826 share_id='fakeshareid') 

827 self.driver.delete_share(self._context, share) 

828 

829 @mock.patch.object(requests.Session, 'request', FakeRequests) 

830 def test_create_delete_share_snapshot(self): 

831 """Create/Delete share snapshot.""" 

832 share1 = fake_share.fake_share(id='fakeid1', share_proto='NFS', 

833 share_id='fakeshareid1') 

834 self.driver.create_share(self._context, share1) 

835 snapshot = fake_share.fake_snapshot(name='fakesnap', 

836 share=share1, 

837 share_name=share1['name'], 

838 share_id=share1['id'], 

839 provider_location='fakelocation') 

840 

841 share2 = fake_share.fake_share(id='fakeid2', share_proto='NFS', 

842 share_id='fakeshareid2') 

843 self.assertRaises(manila_exception.ManilaException, 

844 self.driver.create_snapshot, 

845 self._context, 

846 {'name': snapshot['name'], 

847 'id': snapshot['id'], 

848 'share': share2}) 

849 

850 self.driver.create_snapshot(self._context, snapshot) 

851 

852 # Deleted should succeed for missing volume 

853 self.driver.delete_snapshot(self._context, 

854 {'name': snapshot['name'], 

855 'id': snapshot['id'], 

856 'share': share2}) 

857 # Deleted should succeed for missing snap 

858 self.driver.delete_snapshot(self._context, 

859 {'name': 'wrong_snap', 

860 'id': 'wrong_id', 

861 'share': share1}) 

862 

863 self.driver.delete_snapshot(self._context, snapshot) 

864 self.driver.delete_share(self._context, share1) 

865 

866 @mock.patch.object(requests.Session, 'request', FakeRequests) 

867 def test_extend_share(self): 

868 """Expand share test.""" 

869 share1 = fake_share.fake_share(id='fakeid1', share_proto='NFS', 

870 share_id='fakeshareid', size=10) 

871 share2 = fake_share.fake_share(id='fakeid2', 

872 share_proto='NFS', size=10) 

873 self.driver.create_share(self._context, share1) 

874 

875 self.assertRaises(manila_exception.ZadaraShareNotFound, 

876 self.driver.extend_share, 

877 share2, 15) 

878 

879 self.driver.extend_share(share1, 15) 

880 self.driver.delete_share(self._context, share1) 

881 

882 @mock.patch.object(requests.Session, 'request', FakeRequests) 

883 def test_create_share_from_snapshot(self): 

884 """Create a share from snapshot test.""" 

885 share1 = fake_share.fake_share(id='fakeid1', share_proto='NFS', 

886 share_id='fakeshareid1') 

887 share2 = fake_share.fake_share(id='fakeid2', share_proto='NFS', 

888 share_id='fakeshareid2') 

889 self.driver.create_share(self._context, share1) 

890 

891 snapshot = fake_share.fake_snapshot(name='fakesnap', 

892 share=share1, 

893 share_name=share1['name'], 

894 share_id=share1['id'], 

895 share_instance_id=share1['id'], 

896 provider_location='fakelocation') 

897 self.driver.create_snapshot(self._context, snapshot) 

898 

899 self.assertRaises(manila_exception.ManilaException, 

900 self.driver.create_share_from_snapshot, 

901 self._context, 

902 share2, 

903 {'name': snapshot['name'], 

904 'id': snapshot['id'], 

905 'share': share2, 

906 'share_instance_id': share2['id']}) 

907 

908 self.assertRaises(manila_exception.ManilaException, 

909 self.driver.create_share_from_snapshot, 

910 self._context, 

911 share2, 

912 {'name': 'fakesnapname', 

913 'id': 'fakesnapid', 

914 'share': share1, 

915 'share_instance_id': share1['id']}) 

916 

917 self.driver.create_share_from_snapshot(self._context, share2, snapshot) 

918 self.driver.delete_share(self._context, share1) 

919 self.driver.delete_share(self._context, share2) 

920 

921 def create_vpsa_backend_share(self): 

922 vpsashare_params = {} 

923 vpsashare_params['id'] = 'fake_id' 

924 vpsashare_params['name'] = 'fake_name' 

925 vpsashare_params['display-name'] = 'fake_name' 

926 vpsashare_params['cg-name'] = 'fake_name' 

927 vpsashare_params['size'] = 1 

928 vpsashare_params['capacity'] = 1 

929 vpsashare_params['pool'] = 'pool-0001' 

930 vpsashare_params['share_proto'] = 'NFS' 

931 vpsashare_params['nfs-export-path'] = '10.2.1.56:/export/manage_id' 

932 vpsashare_params['provisioned-capacity'] = 1 

933 vpsashare_params['server_ext_names'] = '' 

934 vpsa_volname = 'fake-volume' 

935 vpsa_share = (vpsa_volname, vpsashare_params) 

936 RUNTIME_VARS['volumes'].append(vpsa_share) 

937 return vpsa_share 

938 

939 @mock.patch.object(requests.Session, 'request', FakeRequests) 

940 def test_manage_existing_share(self): 

941 share1 = {'id': 'manage_name', 

942 'name': 'manage_name', 

943 'display-name': 'manage_name', 

944 'size': 1, 

945 'share_proto': 'NFS', 

946 'export_locations': 

947 [{'path': '10.2.1.56:/export/manage_id'}]} 

948 driver_options = {} 

949 vpsa_share = self.create_vpsa_backend_share() 

950 

951 self.driver.manage_existing(share1, driver_options) 

952 self.assertEqual(vpsa_share[1]['display-name'].split('-')[1], 

953 share1['display-name']) 

954 self.driver.delete_share(self._context, share1) 

955 

956 @mock.patch.object(requests.Session, 'request', FakeRequests) 

957 def test_get_share_stats(self): 

958 """Get stats test.""" 

959 self.configuration.safe_get.return_value = 'ZadaraVPSAShareDriver' 

960 data = self.driver.get_share_stats(True) 

961 self.assertEqual('Zadara Storage', data['vendor_name']) 

962 self.assertEqual('unknown', data['total_capacity_gb']) 

963 self.assertEqual('unknown', data['free_capacity_gb']) 

964 self.assertEqual(data['reserved_percentage'], 

965 self.configuration.reserved_percentage) 

966 self.assertEqual(data['reserved_snapshot_percentage'], 

967 self.configuration.reserved_snapshot_percentage) 

968 self.assertEqual(data['reserved_share_extend_percentage'], 

969 self.configuration.reserved_share_extend_percentage) 

970 self.assertEqual(data['snapshot_support'], True) 

971 self.assertEqual(data['create_share_from_snapshot_support'], True) 

972 self.assertEqual(data['revert_to_snapshot_support'], False) 

973 self.assertEqual(data['vendor_name'], 'Zadara Storage') 

974 self.assertEqual(data['driver_version'], self.driver.VERSION) 

975 self.assertEqual(data['storage_protocol'], 'NFS_CIFS') 

976 self.assertEqual(data['share_backend_name'], 

977 self.configuration.share_backend_name) 

978 

979 def test_allow_access_with_incorrect_access_type(self): 

980 share = fake_share.fake_share(id='fakeid1', share_proto='NFS') 

981 access = fake_share.fake_access(access_type='fake_type') 

982 

983 self.assertRaises(manila_exception.ZadaraInvalidShareAccessType, 

984 self.driver._allow_access, 

985 self._context, share, access) 

986 

987 @mock.patch.object(requests.Session, 'request', FakeRequests) 

988 def test_share_allow_deny_access(self): 

989 """Test share access allow any deny rules.""" 

990 share = fake_share.fake_share(id='fakeid', share_proto='NFS', 

991 share_id='fakeshareid') 

992 self.driver.create_share(self._context, share) 

993 access = fake_share.fake_access() 

994 

995 # Attach server for accessing share with the fake access rules 

996 allow_access = self.driver._allow_access(self._context, share, access) 

997 self.assertEqual(allow_access['driver_volume_type'], 

998 share['share_proto']) 

999 self.assertEqual('1.1.1.1:3260', 

1000 allow_access['data']['target_portal']) 

1001 (srv_name, srv_params) = RUNTIME_VARS['servers'][0] 

1002 self.assertEqual(srv_params['iscsi'], 

1003 allow_access['data']['target_ip']) 

1004 self.assertEqual(share['id'], allow_access['data']['id']) 

1005 self.assertEqual('CHAP', allow_access['data']['auth_method']) 

1006 self.assertEqual('test_chap_user', 

1007 allow_access['data']['auth_username']) 

1008 self.assertEqual('test_chap_secret', 

1009 allow_access['data']['auth_password']) 

1010 

1011 # Detach will not throw any error with missing access rules 

1012 dup_access = fake_share.fake_access() 

1013 self.driver._deny_access(self._context, share, dup_access) 

1014 # Detach server from the share with deny access rules 

1015 self.driver._deny_access(self._context, share, access) 

1016 self.driver.delete_share(self._context, share) 

1017 

1018 def create_vpsa_backend_share_snapshot(self, share): 

1019 vpsasnap_params = {} 

1020 vpsasnap_params['id'] = 'fakesnapid' 

1021 vpsasnap_params['name'] = 'fakesnapname' 

1022 vpsasnap_params['display-name'] = 'fakesnapname' 

1023 vpsasnap_params['provider-location'] = 'fakesnaplocation' 

1024 (vol_name, vol_params) = RUNTIME_VARS['volumes'][0] 

1025 vol_params['snapshots'].append(vpsasnap_params) 

1026 

1027 @mock.patch.object(requests.Session, 'request', FakeRequests) 

1028 def test_manage_existing_snapshot(self): 

1029 share = {'id': 'fake_id', 

1030 'share_id': 'fake_shareid', 

1031 'name': 'fake_name', 

1032 'display-name': 'fake_name', 

1033 'cg-name': 'fake_name', 

1034 'size': 1, 

1035 'capacity': 1, 

1036 'share_proto': 'NFS', 

1037 'pool': 'pool-0001', 

1038 'nfs-export-path': '10.2.1.56:/export/manage_id', 

1039 'provisioned-capacity': 1} 

1040 

1041 self.driver.create_share(self._context, share) 

1042 # Create a backend share that will be managed for manila 

1043 self.create_vpsa_backend_share_snapshot(share) 

1044 

1045 snapshot = {'id': 'manage_snapname', 

1046 'name': 'manage_snapname', 

1047 'display_name': 'manage_snapname', 

1048 'provider_location': 'fakesnaplocation', 

1049 'share': share} 

1050 driver_options = {} 

1051 

1052 self.driver.manage_existing_snapshot(snapshot, driver_options) 

1053 

1054 # Check that the backend share has been renamed 

1055 (vol_name, vol_params) = RUNTIME_VARS['volumes'][0] 

1056 self.assertEqual( 

1057 vol_params['snapshots'][0]['display-name'].split('-')[2], 

1058 snapshot['display_name']) 

1059 self.driver.delete_snapshot(self._context, snapshot) 

1060 self.driver.delete_share(self._context, share)