1# Copyright (c) 2016 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 volume driver 17""" 18import copy 19import mock 20import requests 21from six.moves.urllib import parse 22 23from cinder import exception 24from cinder import test 25from cinder.volume import configuration as conf 26from cinder.volume.drivers import zadara 27 28 29DEFAULT_RUNTIME_VARS = { 30 'status': 200, 31 'user': 'test', 32 'password': 'test_password', 33 'access_key': '0123456789ABCDEF', 34 'volumes': [], 35 'servers': [], 36 'controllers': [('active_ctrl', {'display-name': 'test_ctrl'})], 37 'counter': 1000, 38 39 'login': """ 40 <hash> 41 <user> 42 <updated-at type="datetime">2012-04-30...</updated-at> 43 <access-key>%s</access-key> 44 <id type="integer">1</id> 45 <created-at type="datetime">2012-02-21...</created-at> 46 <email>jsmith@example.com</email> 47 <username>jsmith</username> 48 </user> 49 <status type="integer">0</status> 50 </hash>""", 51 52 'good': """ 53 <hash> 54 <status type="integer">0</status> 55 </hash>""", 56 57 'bad_login': """ 58 <hash> 59 <status type="integer">5</status> 60 <status-msg>Some message...</status-msg> 61 </hash>""", 62 63 'bad_volume': """ 64 <hash> 65 <status type="integer">10081</status> 66 <status-msg>Virtual volume xxx not found</status-msg> 67 </hash>""", 68 69 'bad_server': """ 70 <hash> 71 <status type="integer">10086</status> 72 <status-msg>Server xxx not found</status-msg> 73 </hash>""", 74 75 'server_created': """ 76 <create-server-response> 77 <server-name>%s</server-name> 78 <status type='integer'>0</status> 79 </create-server-response>""", 80} 81 82RUNTIME_VARS = None 83 84 85class FakeResponse(object): 86 def __init__(self, method, url, body): 87 self.method = method 88 self.url = url 89 self.body = body 90 self.status = RUNTIME_VARS['status'] 91 92 def read(self): 93 ops = {'POST': [('/api/users/login.xml', self._login), 94 ('/api/volumes.xml', self._create_volume), 95 ('/api/servers.xml', self._create_server), 96 ('/api/servers/*/volumes.xml', self._attach), 97 ('/api/volumes/*/detach.xml', self._detach), 98 ('/api/volumes/*/expand.xml', self._expand), 99 ('/api/consistency_groups/*/snapshots.xml', 100 self._create_snapshot), 101 ('/api/consistency_groups/*/clone.xml', 102 self._create_clone)], 103 'DELETE': [('/api/volumes/*', self._delete), 104 ('/api/snapshots/*', self._delete_snapshot)], 105 'GET': [('/api/volumes.xml', self._list_volumes), 106 ('/api/pools.xml', self._list_pools), 107 ('/api/vcontrollers.xml', self._list_controllers), 108 ('/api/servers.xml', self._list_servers), 109 ('/api/consistency_groups/*/snapshots.xml', 110 self._list_vol_snapshots), 111 ('/api/volumes/*/servers.xml', 112 self._list_vol_attachments)] 113 } 114 115 ops_list = ops[self.method] 116 modified_url = self.url.split('?')[0] 117 for (templ_url, func) in ops_list: 118 if self._compare_url(modified_url, templ_url): 119 result = func() 120 return result 121 122 def _compare_url(self, url, template_url): 123 items = url.split('/') 124 titems = template_url.split('/') 125 for (i, titem) in enumerate(titems): 126 if titem != '*' and titem != items[i]: 127 return False 128 return True 129 130 def _get_parameters(self, data): 131 items = data.split('&') 132 params = {} 133 for item in items: 134 if item: 135 (k, v) = item.split('=') 136 params[k] = v 137 return params 138 139 def _get_counter(self): 140 cnt = RUNTIME_VARS['counter'] 141 RUNTIME_VARS['counter'] += 1 142 return cnt 143 144 def _login(self): 145 params = self._get_parameters(self.body) 146 if (params['user'] == RUNTIME_VARS['user'] and 147 params['password'] == RUNTIME_VARS['password']): 148 return RUNTIME_VARS['login'] % RUNTIME_VARS['access_key'] 149 else: 150 return RUNTIME_VARS['bad_login'] 151 152 def _incorrect_access_key(self, params): 153 return (params['access_key'] != RUNTIME_VARS['access_key']) 154 155 def _create_volume(self): 156 params = self._get_parameters(self.body) 157 if self._incorrect_access_key(params): 158 return RUNTIME_VARS['bad_login'] 159 160 params['display-name'] = params['name'] 161 params['cg-name'] = params['name'] 162 params['snapshots'] = [] 163 params['attachments'] = [] 164 vpsa_vol = 'volume-%07d' % self._get_counter() 165 RUNTIME_VARS['volumes'].append((vpsa_vol, params)) 166 return RUNTIME_VARS['good'] 167 168 def _create_server(self): 169 params = self._get_parameters(self.body) 170 if self._incorrect_access_key(params): 171 return RUNTIME_VARS['bad_login'] 172 173 params['display-name'] = params['display_name'] 174 vpsa_srv = 'srv-%07d' % self._get_counter() 175 RUNTIME_VARS['servers'].append((vpsa_srv, params)) 176 return RUNTIME_VARS['server_created'] % vpsa_srv 177 178 def _attach(self): 179 params = self._get_parameters(self.body) 180 if self._incorrect_access_key(params): 181 return RUNTIME_VARS['bad_login'] 182 183 srv = self.url.split('/')[3] 184 vol = params['volume_name[]'] 185 186 for (vol_name, params) in RUNTIME_VARS['volumes']: 187 if vol_name == vol: 188 attachments = params['attachments'] 189 if srv in attachments: 190 # already attached - ok 191 return RUNTIME_VARS['good'] 192 else: 193 attachments.append(srv) 194 return RUNTIME_VARS['good'] 195 196 return RUNTIME_VARS['bad_volume'] 197 198 def _detach(self): 199 params = self._get_parameters(self.body) 200 if self._incorrect_access_key(params): 201 return RUNTIME_VARS['bad_login'] 202 203 vol = self.url.split('/')[3] 204 srv = params['server_name[]'] 205 206 for (vol_name, params) in RUNTIME_VARS['volumes']: 207 if vol_name == vol: 208 attachments = params['attachments'] 209 if srv not in attachments: 210 return RUNTIME_VARS['bad_server'] 211 else: 212 attachments.remove(srv) 213 return RUNTIME_VARS['good'] 214 215 return RUNTIME_VARS['bad_volume'] 216 217 def _expand(self): 218 params = self._get_parameters(self.body) 219 if self._incorrect_access_key(params): 220 return RUNTIME_VARS['bad_login'] 221 222 vol = self.url.split('/')[3] 223 capacity = params['capacity'] 224 225 for (vol_name, params) in RUNTIME_VARS['volumes']: 226 if vol_name == vol: 227 params['capacity'] = capacity 228 return RUNTIME_VARS['good'] 229 230 return RUNTIME_VARS['bad_volume'] 231 232 def _create_snapshot(self): 233 params = self._get_parameters(self.body) 234 if self._incorrect_access_key(params): 235 return RUNTIME_VARS['bad_login'] 236 237 cg_name = self.url.split('/')[3] 238 snap_name = params['display_name'] 239 240 for (vol_name, params) in RUNTIME_VARS['volumes']: 241 if params['cg-name'] == cg_name: 242 snapshots = params['snapshots'] 243 if snap_name in snapshots: 244 # already attached 245 return RUNTIME_VARS['bad_volume'] 246 else: 247 snapshots.append(snap_name) 248 return RUNTIME_VARS['good'] 249 250 return RUNTIME_VARS['bad_volume'] 251 252 def _delete_snapshot(self): 253 snap = self.url.split('/')[3].split('.')[0] 254 255 for (vol_name, params) in RUNTIME_VARS['volumes']: 256 if snap in params['snapshots']: 257 params['snapshots'].remove(snap) 258 return RUNTIME_VARS['good'] 259 260 return RUNTIME_VARS['bad_volume'] 261 262 def _create_clone(self): 263 params = self._get_parameters(self.body) 264 if self._incorrect_access_key(params): 265 return RUNTIME_VARS['bad_login'] 266 267 params['display-name'] = params['name'] 268 params['cg-name'] = params['name'] 269 params['capacity'] = 1 270 params['snapshots'] = [] 271 params['attachments'] = [] 272 vpsa_vol = 'volume-%07d' % self._get_counter() 273 RUNTIME_VARS['volumes'].append((vpsa_vol, params)) 274 return RUNTIME_VARS['good'] 275 276 def _delete(self): 277 vol = self.url.split('/')[3].split('.')[0] 278 279 for (vol_name, params) in RUNTIME_VARS['volumes']: 280 if vol_name == vol: 281 if params['attachments']: 282 # there are attachments - should be volume busy error 283 return RUNTIME_VARS['bad_volume'] 284 else: 285 RUNTIME_VARS['volumes'].remove((vol_name, params)) 286 return RUNTIME_VARS['good'] 287 288 return RUNTIME_VARS['bad_volume'] 289 290 def _generate_list_resp(self, header, footer, body, lst, vol): 291 resp = header 292 for (obj, params) in lst: 293 if vol: 294 resp += body % (obj, 295 params['display-name'], 296 params['cg-name'], 297 params['capacity']) 298 else: 299 resp += body % (obj, params['display-name']) 300 resp += footer 301 return resp 302 303 def _list_volumes(self): 304 header = """<show-volumes-response> 305 <status type='integer'>0</status> 306 <volumes type='array'>""" 307 footer = "</volumes></show-volumes-response>" 308 body = """<volume> 309 <name>%s</name> 310 <display-name>%s</display-name> 311 <cg-name>%s</cg-name> 312 <status>Available</status> 313 <virtual-capacity type='integer'>%s</virtual-capacity> 314 <allocated-capacity type='integer'>1</allocated-capacity> 315 <raid-group-name>r5</raid-group-name> 316 <cache>write-through</cache> 317 <created-at type='datetime'>2012-01-28...</created-at> 318 <modified-at type='datetime'>2012-01-28...</modified-at> 319 </volume>""" 320 return self._generate_list_resp(header, 321 footer, 322 body, 323 RUNTIME_VARS['volumes'], 324 True) 325 326 def _list_controllers(self): 327 header = """<show-vcontrollers-response> 328 <status type='integer'>0</status> 329 <vcontrollers type='array'>""" 330 footer = "</vcontrollers></show-vcontrollers-response>" 331 body = """<vcontroller> 332 <name>%s</name> 333 <display-name>%s</display-name> 334 <state>active</state> 335 <target>iqn.2011-04.com.zadarastorage:vsa-xxx:1</target> 336 <iscsi-ip>1.1.1.1</iscsi-ip> 337 <mgmt-ip>1.1.1.1</mgmt-ip> 338 <software-ver>0.0.09-05.1--77.7</software-ver> 339 <heartbeat1>ok</heartbeat1> 340 <heartbeat2>ok</heartbeat2> 341 <vpsa-chap-user>test_chap_user</vpsa-chap-user> 342 <vpsa-chap-secret>test_chap_secret</vpsa-chap-secret> 343 </vcontroller>""" 344 return self._generate_list_resp(header, 345 footer, 346 body, 347 RUNTIME_VARS['controllers'], 348 False) 349 350 def _list_pools(self): 351 header = """<show-pools-response> 352 <status type="integer">0</status> 353 <pools type="array"> 354 """ 355 footer = "</pools></show-pools-response>" 356 return header + footer 357 358 def _list_servers(self): 359 header = """<show-servers-response> 360 <status type='integer'>0</status> 361 <servers type='array'>""" 362 footer = "</servers></show-servers-response>" 363 body = """<server> 364 <name>%s</name> 365 <display-name>%s</display-name> 366 <iqn>%s</iqn> 367 <status>Active</status> 368 <created-at type='datetime'>2012-01-28...</created-at> 369 <modified-at type='datetime'>2012-01-28...</modified-at> 370 </server>""" 371 372 resp = header 373 for (obj, params) in RUNTIME_VARS['servers']: 374 resp += body % (obj, params['display-name'], params['iqn']) 375 resp += footer 376 return resp 377 378 def _get_server_obj(self, name): 379 for (srv_name, params) in RUNTIME_VARS['servers']: 380 if srv_name == name: 381 return params 382 383 def _list_vol_attachments(self): 384 vol = self.url.split('/')[3] 385 386 header = """<show-servers-response> 387 <status type="integer">0</status> 388 <servers type="array">""" 389 footer = "</servers></show-servers-response>" 390 body = """<server> 391 <name>%s</name> 392 <display-name>%s</display-name> 393 <iqn>%s</iqn> 394 <target>iqn.2011-04.com.zadarastorage:vsa-xxx:1</target> 395 <lun>0</lun> 396 </server>""" 397 398 for (vol_name, params) in RUNTIME_VARS['volumes']: 399 if vol_name == vol: 400 attachments = params['attachments'] 401 resp = header 402 for server in attachments: 403 srv_params = self._get_server_obj(server) 404 resp += body % (server, 405 srv_params['display-name'], 406 srv_params['iqn']) 407 resp += footer 408 return resp 409 410 return RUNTIME_VARS['bad_volume'] 411 412 def _list_vol_snapshots(self): 413 cg_name = self.url.split('/')[3] 414 415 header = """<show-snapshots-on-cg-response> 416 <status type="integer">0</status> 417 <snapshots type="array">""" 418 footer = "</snapshots></show-snapshots-on-cg-response>" 419 420 body = """<snapshot> 421 <name>%s</name> 422 <display-name>%s</display-name> 423 <status>normal</status> 424 <cg-name>%s</cg-name> 425 <pool-name>pool-00000001</pool-name> 426 </snapshot>""" 427 428 for (vol_name, params) in RUNTIME_VARS['volumes']: 429 if params['cg-name'] == cg_name: 430 snapshots = params['snapshots'] 431 resp = header 432 for snap in snapshots: 433 resp += body % (snap, snap, cg_name) 434 resp += footer 435 return resp 436 437 return RUNTIME_VARS['bad_volume'] 438 439 440class FakeRequests(object): 441 """A fake requests for zadara volume driver tests.""" 442 def __init__(self, method, api_url, data, verify): 443 url = parse.urlparse(api_url).path 444 res = FakeResponse(method, url, data) 445 self.content = res.read() 446 self.status_code = res.status 447 448 449class ZadaraVPSADriverTestCase(test.TestCase): 450 """Test case for Zadara VPSA volume driver.""" 451 @mock.patch.object(requests, 'request', FakeRequests) 452 def setUp(self): 453 super(ZadaraVPSADriverTestCase, self).setUp() 454 455 global RUNTIME_VARS 456 RUNTIME_VARS = copy.deepcopy(DEFAULT_RUNTIME_VARS) 457 self.configuration = mock.Mock(conf.Configuration(None)) 458 self.configuration.append_config_values(zadara.zadara_opts) 459 self.configuration.reserved_percentage = 10 460 self.configuration.zadara_use_iser = True 461 self.configuration.zadara_vpsa_host = '192.168.5.5' 462 self.configuration.zadara_vpsa_port = '80' 463 self.configuration.zadara_user = 'test' 464 self.configuration.zadara_password = 'test_password' 465 self.configuration.zadara_vpsa_poolname = 'pool-0001' 466 self.configuration.zadara_vol_encrypt = False 467 self.configuration.zadara_vol_name_template = 'OS_%s' 468 self.configuration.zadara_vpsa_use_ssl = False 469 self.configuration.zadara_ssl_cert_verify = False 470 self.configuration.zadara_default_snap_policy = False 471 self.driver = (zadara.ZadaraVPSAISCSIDriver( 472 configuration=self.configuration)) 473 self.driver.do_setup(None) 474 475 @mock.patch.object(requests, 'request', FakeRequests) 476 def test_create_destroy(self): 477 """Create/Delete volume.""" 478 volume = {'name': 'test_volume_01', 'size': 1} 479 self.driver.create_volume(volume) 480 self.driver.delete_volume(volume) 481 482 @mock.patch.object(requests, 'request', FakeRequests) 483 def test_create_destroy_multiple(self): 484 """Create/Delete multiple volumes.""" 485 self.driver.create_volume({'name': 'test_volume_01', 'size': 1}) 486 self.driver.create_volume({'name': 'test_volume_02', 'size': 2}) 487 self.driver.create_volume({'name': 'test_volume_03', 'size': 3}) 488 self.driver.delete_volume({'name': 'test_volume_02'}) 489 self.driver.delete_volume({'name': 'test_volume_03'}) 490 self.driver.delete_volume({'name': 'test_volume_01'}) 491 self.driver.delete_volume({'name': 'test_volume_04'}) 492 493 @mock.patch.object(requests, 'request', FakeRequests) 494 def test_destroy_non_existent(self): 495 """Delete non-existent volume.""" 496 volume = {'name': 'test_volume_02', 'size': 1} 497 self.driver.delete_volume(volume) 498 499 @mock.patch.object(requests, 'request', FakeRequests) 500 def test_empty_apis(self): 501 """Test empty func (for coverage only).""" 502 context = None 503 volume = {'name': 'test_volume_01', 'size': 1} 504 self.driver.create_export(context, volume) 505 self.driver.ensure_export(context, volume) 506 self.driver.remove_export(context, volume) 507 self.assertRaises(NotImplementedError, 508 self.driver.local_path, 509 None) 510 self.driver.check_for_setup_error() 511 512 @mock.patch.object(requests, 'request', FakeRequests) 513 def test_volume_attach_detach(self): 514 """Test volume attachment and detach.""" 515 volume = {'name': 'test_volume_01', 'size': 1, 'id': 123} 516 connector = dict(initiator='test_iqn.1') 517 self.driver.create_volume(volume) 518 props = self.driver.initialize_connection(volume, connector) 519 self.assertEqual('iser', props['driver_volume_type']) 520 data = props['data'] 521 self.assertEqual('1.1.1.1:3260', data['target_portal']) 522 self.assertEqual('iqn.2011-04.com.zadarastorage:vsa-xxx:1', 523 data['target_iqn']) 524 self.assertEqual(int('0'), data['target_lun']) 525 self.assertEqual(123, data['volume_id']) 526 self.assertEqual('CHAP', data['auth_method']) 527 self.assertEqual('test_chap_user', data['auth_username']) 528 self.assertEqual('test_chap_secret', data['auth_password']) 529 self.driver.terminate_connection(volume, connector) 530 self.driver.delete_volume(volume) 531 532 @mock.patch.object(requests, 'request', FakeRequests) 533 def test_volume_attach_multiple_detach(self): 534 """Test multiple volume attachment and detach.""" 535 volume = {'name': 'test_volume_01', 'size': 1, 'id': 123} 536 connector1 = dict(initiator='test_iqn.1') 537 connector2 = dict(initiator='test_iqn.2') 538 connector3 = dict(initiator='test_iqn.3') 539 540 self.driver.create_volume(volume) 541 self.driver.initialize_connection(volume, connector1) 542 self.driver.initialize_connection(volume, connector2) 543 self.driver.initialize_connection(volume, connector3) 544 545 self.driver.terminate_connection(volume, connector1) 546 self.driver.terminate_connection(volume, connector3) 547 self.driver.terminate_connection(volume, connector2) 548 self.driver.delete_volume(volume) 549 550 @mock.patch.object(requests, 'request', FakeRequests) 551 def test_wrong_attach_params(self): 552 """Test different wrong attach scenarios.""" 553 volume1 = {'name': 'test_volume_01', 'size': 1, 'id': 101} 554 connector1 = dict(initiator='test_iqn.1') 555 self.assertRaises(exception.VolumeNotFound, 556 self.driver.initialize_connection, 557 volume1, connector1) 558 559 @mock.patch.object(requests, 'request', FakeRequests) 560 def test_wrong_detach_params(self): 561 """Test different wrong detachment scenarios.""" 562 volume1 = {'name': 'test_volume_01', 'size': 1, 'id': 101} 563 volume2 = {'name': 'test_volume_02', 'size': 1, 'id': 102} 564 volume3 = {'name': 'test_volume_03', 'size': 1, 'id': 103} 565 connector1 = dict(initiator='test_iqn.1') 566 connector2 = dict(initiator='test_iqn.2') 567 connector3 = dict(initiator='test_iqn.3') 568 self.driver.create_volume(volume1) 569 self.driver.create_volume(volume2) 570 self.driver.initialize_connection(volume1, connector1) 571 self.driver.initialize_connection(volume2, connector2) 572 self.assertRaises(exception.ZadaraServerNotFound, 573 self.driver.terminate_connection, 574 volume1, connector3) 575 self.assertRaises(exception.VolumeNotFound, 576 self.driver.terminate_connection, 577 volume3, connector1) 578 self.assertRaises(exception.FailedCmdWithDump, 579 self.driver.terminate_connection, 580 volume1, connector2) 581 582 @mock.patch.object(requests, 'request', FakeRequests) 583 def test_wrong_login_reply(self): 584 """Test wrong login reply.""" 585 586 RUNTIME_VARS['login'] = """<hash> 587 <access-key>%s</access-key> 588 <status type="integer">0</status> 589 </hash>""" 590 self.assertRaises(exception.MalformedResponse, 591 self.driver.do_setup, None) 592 593 RUNTIME_VARS['login'] = """ 594 <hash> 595 <user> 596 <updated-at type="datetime">2012-04-30...</updated-at> 597 <id type="integer">1</id> 598 <created-at type="datetime">2012-02-21...</created-at> 599 <email>jsmith@example.com</email> 600 <username>jsmith</username> 601 </user> 602 <access-key>%s</access-key> 603 <status type="integer">0</status> 604 </hash>""" 605 self.assertRaises(exception.MalformedResponse, 606 self.driver.do_setup, None) 607 608 @mock.patch.object(requests, 'request') 609 def test_ssl_use(self, request): 610 """Coverage test for SSL connection.""" 611 self.configuration.zadara_ssl_cert_verify = True 612 self.configuration.zadara_vpsa_use_ssl = True 613 self.configuration.driver_ssl_cert_path = '/path/to/cert' 614 615 good_response = mock.MagicMock() 616 good_response.status_code = RUNTIME_VARS['status'] 617 good_response.content = RUNTIME_VARS['login'] 618 619 def request_verify_cert(*args, **kwargs): 620 self.assertEqual(kwargs['verify'], '/path/to/cert') 621 return good_response 622 623 request.side_effect = request_verify_cert 624 self.driver.do_setup(None) 625 626 @mock.patch.object(requests, 'request', FakeRequests) 627 def test_bad_http_response(self): 628 """Coverage test for non-good HTTP response.""" 629 RUNTIME_VARS['status'] = 400 630 631 volume = {'name': 'test_volume_01', 'size': 1} 632 self.assertRaises(exception.BadHTTPResponseStatus, 633 self.driver.create_volume, volume) 634 635 @mock.patch.object(requests, 'request', FakeRequests) 636 def test_delete_without_detach(self): 637 """Test volume deletion without detach.""" 638 639 volume1 = {'name': 'test_volume_01', 'size': 1, 'id': 101} 640 connector1 = dict(initiator='test_iqn.1') 641 connector2 = dict(initiator='test_iqn.2') 642 connector3 = dict(initiator='test_iqn.3') 643 self.driver.create_volume(volume1) 644 self.driver.initialize_connection(volume1, connector1) 645 self.driver.initialize_connection(volume1, connector2) 646 self.driver.initialize_connection(volume1, connector3) 647 self.driver.delete_volume(volume1) 648 649 @mock.patch.object(requests, 'request', FakeRequests) 650 def test_no_active_ctrl(self): 651 652 RUNTIME_VARS['controllers'] = [] 653 volume = {'name': 'test_volume_01', 'size': 1, 'id': 123} 654 connector = dict(initiator='test_iqn.1') 655 self.driver.create_volume(volume) 656 self.assertRaises(exception.ZadaraVPSANoActiveController, 657 self.driver.initialize_connection, 658 volume, connector) 659 660 @mock.patch.object(requests, 'request', FakeRequests) 661 def test_create_destroy_snapshot(self): 662 """Create/Delete snapshot test.""" 663 volume = {'name': 'test_volume_01', 'size': 1} 664 snapshot = {'name': 'snap_01', 665 'volume_name': volume['name']} 666 667 self.driver.create_volume(volume) 668 self.assertRaises(exception.VolumeDriverException, 669 self.driver.create_snapshot, 670 {'name': snapshot['name'], 671 'volume_name': 'wrong_vol'}) 672 673 self.driver.create_snapshot(snapshot) 674 675 # Deleted should succeed for missing volume 676 self.driver.delete_snapshot({'name': snapshot['name'], 677 'volume_name': 'wrong_vol'}) 678 # Deleted should succeed for missing snap 679 self.driver.delete_snapshot({'name': 'wrong_snap', 680 'volume_name': volume['name']}) 681 682 self.driver.delete_snapshot(snapshot) 683 self.driver.delete_volume(volume) 684 685 @mock.patch.object(requests, 'request', FakeRequests) 686 def test_expand_volume(self): 687 """Expand volume test.""" 688 volume = {'name': 'test_volume_01', 'size': 10} 689 volume2 = {'name': 'test_volume_02', 'size': 10} 690 691 self.driver.create_volume(volume) 692 693 self.assertRaises(exception.ZadaraVolumeNotFound, 694 self.driver.extend_volume, 695 volume2, 15) 696 self.assertRaises(exception.InvalidInput, 697 self.driver.extend_volume, 698 volume, 5) 699 700 self.driver.extend_volume(volume, 15) 701 self.driver.delete_volume(volume) 702 703 @mock.patch.object(requests, 'request', FakeRequests) 704 def test_create_destroy_clones(self): 705 """Create/Delete clones test.""" 706 volume1 = {'name': 'test_volume_01', 'id': '01', 'size': 1} 707 volume2 = {'name': 'test_volume_02', 'id': '02', 'size': 2} 708 volume3 = {'name': 'test_volume_03', 'id': '03', 'size': 1} 709 snapshot = {'name': 'snap_01', 710 'id': '01', 711 'volume_name': volume1['name'], 712 'volume_size': 1} 713 714 self.driver.create_volume(volume1) 715 self.driver.create_snapshot(snapshot) 716 717 # Test invalid vol reference 718 self.assertRaises(exception.VolumeNotFound, 719 self.driver.create_volume_from_snapshot, 720 volume2, 721 {'name': snapshot['name'], 722 'id': snapshot['id'], 723 'volume_name': 'wrong_vol'}) 724 # Test invalid snap reference 725 self.assertRaises(exception.SnapshotNotFound, 726 self.driver.create_volume_from_snapshot, 727 volume2, 728 {'name': 'wrong_snap', 729 'id': 'wrong_id', 730 'volume_name': snapshot['volume_name']}) 731 # Test invalid src_vref for volume clone 732 self.assertRaises(exception.VolumeNotFound, 733 self.driver.create_cloned_volume, 734 volume3, volume2) 735 self.driver.create_volume_from_snapshot(volume2, snapshot) 736 self.driver.create_cloned_volume(volume3, volume1) 737 self.driver.delete_volume(volume3) 738 self.driver.delete_volume(volume2) 739 self.driver.delete_snapshot(snapshot) 740 self.driver.delete_volume(volume1) 741 742 @mock.patch.object(requests, 'request', FakeRequests) 743 def test_get_volume_stats(self): 744 """Get stats test.""" 745 self.configuration.safe_get.return_value = 'ZadaraVPSAISCSIDriver' 746 data = self.driver.get_volume_stats(True) 747 self.assertEqual('Zadara Storage', data['vendor_name']) 748 self.assertEqual('unknown', data['total_capacity_gb']) 749 self.assertEqual('unknown', data['free_capacity_gb']) 750 self.assertEqual({'total_capacity_gb': 'unknown', 751 'free_capacity_gb': 'unknown', 752 'reserved_percentage': 753 self.configuration.reserved_percentage, 754 'QoS_support': False, 755 'vendor_name': 'Zadara Storage', 756 'driver_version': self.driver.VERSION, 757 'storage_protocol': 'iSER', 758 'volume_backend_name': 'ZadaraVPSAISCSIDriver'}, 759 data) 760