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
« 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"""
19import copy
20import requests
22from unittest import mock
23from urllib import parse
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
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)
40 return wrap
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,
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}
118RUNTIME_VARS = None
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']
131 @property
132 def access_key(self):
133 """Returns Response Access Key"""
134 return self.headers["X-Access-Key"]
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 }
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
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
183 return True
185 @staticmethod
186 def _get_counter():
187 cnt = RUNTIME_VARS['counter']
188 RUNTIME_VARS['counter'] += 1
189 return cnt
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']
199 def _is_correct_access_key(self):
200 return self.access_key == RUNTIME_VARS['access_key']
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']
215 @check_access_key
216 def _create_server(self):
217 params = self.body
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
224 @check_access_key
225 def _attach(self):
226 srv = self.url.split('/')[3]
228 params = self.body
230 vol = params['volume_name[]']
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']
245 return RUNTIME_VARS['bad_volume']
247 @check_access_key
248 def _detach(self):
249 params = self.body
250 vol = self.url.split('/')[3]
251 srv = params['server_name[]']
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']
264 return RUNTIME_VARS['bad_volume']
266 @check_access_key
267 def _expand(self):
268 params = self.body
269 vol = self.url.split('/')[3]
270 capacity = params['capacity']
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']
277 return RUNTIME_VARS['bad_volume']
279 @check_access_key
280 def _rename(self):
281 params = self.body
282 vol = self.url.split('/')[3]
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']
291 return RUNTIME_VARS['bad_volume']
293 @check_access_key
294 def _rename_snapshot(self):
295 params = self.body
296 vpsa_snapshot = self.url.split('/')[3]
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']
305 return RUNTIME_VARS['bad_volume']
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']
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']
323 return RUNTIME_VARS['bad_volume']
325 @check_access_key
326 def _delete_snapshot(self):
327 snap = self.url.split('/')[3].split('.')[0]
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']
334 return RUNTIME_VARS['bad_volume']
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']
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']
363 def _delete(self):
364 vol = self.url.split('/')[3].split('.')[0]
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']
375 return RUNTIME_VARS['bad_volume']
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
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 }"""
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
453 return RUNTIME_VARS['bad_volume']
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
490 return RUNTIME_VARS['fake_volume']
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)
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
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 }"""
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
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
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 }"""
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
647 return RUNTIME_VARS['bad_volume']
649 def _list_vol_snapshots(self):
650 cg_name = self.url.split('/')[3]
652 null_body = """
653 {
654 "response": {
655 "snapshots": [
656 ],
657 "status": 0
658 }
659 }"""
661 body = """
662 {
663 "response": {
664 "snapshots": %s,
665 "status": 0
666 }
667 }"""
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
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
700 return RUNTIME_VARS['bad_volume']
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
717class ZadaraVPSAShareDriverTestCase(test.TestCase):
719 @mock.patch.object(requests.Session, 'request', FakeRequests)
720 def setUp(self):
721 super(ZadaraVPSAShareDriverTestCase, self).setUp()
723 def _safe_get(opt):
724 return getattr(self.configuration, opt)
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)
730 global RUNTIME_VARS
731 RUNTIME_VARS = copy.deepcopy(DEFAULT_RUNTIME_VARS)
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()
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)
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()
786 RUNTIME_VARS['controllers'] = []
787 self.assertRaises(manila_exception.ZadaraVPSANoActiveController,
788 self.driver._allow_access,
789 self._context,
790 share, access)
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)
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)
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)
815 share2 = fake_share.fake_share(id='fakeid2', share_proto='CIFS',
816 share_id='fakeshareid2')
817 self.driver.create_share(self._context, share2)
819 self.driver.delete_share(self._context, share1)
820 self.driver.delete_share(self._context, share2)
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)
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')
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})
850 self.driver.create_snapshot(self._context, snapshot)
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})
863 self.driver.delete_snapshot(self._context, snapshot)
864 self.driver.delete_share(self._context, share1)
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)
875 self.assertRaises(manila_exception.ZadaraShareNotFound,
876 self.driver.extend_share,
877 share2, 15)
879 self.driver.extend_share(share1, 15)
880 self.driver.delete_share(self._context, share1)
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)
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)
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']})
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']})
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)
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
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()
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)
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)
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')
983 self.assertRaises(manila_exception.ZadaraInvalidShareAccessType,
984 self.driver._allow_access,
985 self._context, share, access)
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()
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'])
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)
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)
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}
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)
1045 snapshot = {'id': 'manage_snapname',
1046 'name': 'manage_snapname',
1047 'display_name': 'manage_snapname',
1048 'provider_location': 'fakesnaplocation',
1049 'share': share}
1050 driver_options = {}
1052 self.driver.manage_existing_snapshot(snapshot, driver_options)
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)